There’s a large learning curve to learning blockchain development because it’s so different than web2 programming paradigms. Instead of writing code that runs on one computer, you’re writing and interacting with code that runs on a distributed network
To deeply understand blockchain development, we’ll want to look at the virtual machine (VM) that’s used. There’s a couple different virtual machines that different blockchain’s use including Sealevel (Solana), CosmWasm (Cosmos), Wasm (Polkadot), Move VM (created by Facebook for Diem and will be used by Sui and Aptos).
The most popular virtual machine, though, is the Ethereum Virtual Machine or EVM. It has the largest number of applications and support from some of the largest blockchains including Ethereum, Polygon, Avalanche, Near, Optimism, and Arbitrum to name a few.
This guide is designed to get you up to speed on advanced concepts extremely quickly. It should take about 10 hours of deeply focused work to get through but you’ll come out of it with a great knowledge of the EVM.
Some of the basics to understand on Ethereum
For a deeper dive into most of these topics you can go to https://ethereum.org/en/developers/docs/intro-to-ethereum/
Public Key Cryptography: Blockchains rely on public key cryptography to prove ownership of assets and to create transactions. Public key cryptography uses a key pair which consists of a public key and a private key. The private key is kept secret and is used to digital signatures and can derive the public key.
Hash Function: a cryptographic function which creates a unique digital identifier (hash) for a piece of data.
ether (ETH): The native token on Ethereum which is required to pay for transaction fees.
wei: the smallest denomination of ETH.
1 ETH = 10e18 weigwei.
1 ETH = 10e9 gwei.
Ethereum Account: An entity that can send transactions. Accounts are either external owned (EOA) and controlled by a private key or they have code associated with them (i.e. it is a smart contract)
Ethereum Address: The public identifier of an ethereum account is it's address. The address is the keccak256 hash of an account
Wallet: a wallet is a piece of software that controls a user's private key. More advance wallets like MetaMask provide an easy way to connect to dapps and send transactions.
Transaction: A signed instruction from an account
Block: A batch of transactions that includes a hash of the previous block in the chain.
Gas: a unit of measure for how computationally intensive an operation is on Ethereum. The amount of gas a transaction requires is used to calculate the transaction fee a user must pay to execute a transaction.
Gas Price: The amount of ETH a user pays per unit of gas to execute a transaction. Gas Price is typically measured in gwei.
Ethereum Node: a computer running the software for the Ethereum protocol
Json-RPC: A lightweight remote procedure protocol (RPC). Ethereum nodes an expose their json rpc api so that clients can query the state of the blockchain.
Token Decimals: https://docs.openzeppelin.com/contracts/2.x/erc20#a-note-on-decimals
We’re going to use Foundry to create your first smart contract. Foundry is a rust-based smart contract development toolchain created by Georgios Konstantopoulos that’s new but very well liked. There’s other smart contract development environments which I’ll split into 2 categories:
Pure Solidity: Tests are written in Solidity
Foundry
Javascript Based: Tests are written in JS/TS
For your first smart contract, we’re going to create an ERC20 token which is the standard interface for fungible tokens on EVM blockchains. You won’t be expected to understand a lot of what’s happening under the hood, which is fine because we’re just trying to quickly expose you to a bunch of different concepts in EVM development.
Go to the Foundry installation page and follow its directions to install Foundry. If you’re successful you should be able to run the following command.
$ forge --version
# Output should be something like
# -> forge 0.2.0 (60b1919 2022-07-24T00:08:10.658215Z)
forge and cast are the two binaries that foundryup installs and running forge version only returns the version of forge if forge is successfully installed.
Follow the instructions in the Foundry docs to set up your first project.
We’re going to use transmissions11’s Solmate repo as a dependency. Solmate is a collection of common smart contracts that are helpful for using as building blocks. Solmate is gas-optimized (a.k.a. it’s written so people spend as little as possible on transaction fees) and already audited which reduces the surface area for a vulnerability and the cost of an audit for your project.
$ forge install transmissions11/solmate
Rename src/Counter.sol to src/FirstToken.sol. Open up src/FirstToken.sol in your editor and copy in:
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.13;
import { ERC20 } from "solmate/tokens/ERC20.sol";
contract FirstToken is ERC20 {
constructor(uint256 totalSupply) ERC20("First Token", "FT", 18) {
_mint(msg.sender, totalSupply);
}
}
Let’s quickly go over what is happening in the contract.
Line 2: The pragma statement specifies which version of the Solidity compiler we need to use. In this case we’ll only accept the compiler version 0.8.13
Line 4: We import the Solmate ERC20 token contract. This contract is what’s doing all the work under the hood. Refer to that contract if you’re interested in how the token is implemented.
Line 6: We define our contract
FirstTokenand that it inherits from the SolmateERC20contract. Contracts in Solidity are similar to classes in object-oriented languages and can inherit from one another.Line 7: We define the constructor of the contract and call the constructor of the ERC20 contract.
Solidity is unique in that a contract can extend multiple other contracts, and thus the atypical syntax of calling the ERC20 constructor in the constructor definition, not in a
supercall.Our constructor accepts 1 argument
totalSupply. The ERC20 constructor accepts 3 arguments:name,symbolanddecimals.nameandsymbolare straightforward.decimalsis the number of decimals to use in the numbers representation. Ethereum and all other blockchains I know of have no concept of floating point numbers so for more granular accuracy they just stick a bunch of zeros behind each number and refer to those as decimal points. ETH and most ERC20 tokens have 18 decimals. USDC and USDT both have 6 decimals (because fiat?) and Wrapped Bitcoin (WBTC) has 8 decimals because Bitcoin has 8 decimals.
Line 8: We mint the
totalSupplyof tokens to themsg.sender, which is the account that is sending the message to create the contract.
Now that we’ve created the smart contract, we need to deploy it.
Before we can deploy the smart contract, we’ll have to create a wallet that we can deploy the contract from.
The simple way to create a wallet with foundry is running:
cast wallet new
But if you want to get creative you can run:
cast wallet vanity --starts-with <prefix>
This command will search for a private key that matches an address that starts with <prefix>. Not that <prefix> must be valid hex (i.e. valid characters include 0-9 and a-f only). Note that the longer the prefix is, the more attempts it will take the script to find a valid address that fulfills your vanity requirements. From my experience, anything under 4 characters will return instantly, 5 characters will take about 20-40s and each successive character will take an order of magnitude longer (it’s 16x harder to be exact). The exact times you’ll see will depend on the specs of the machine you run this on.
💡 Keep your private key safe. Anyone with access to this private key can access any funds in your wallet. It’s recommended to not keep a lot of funds in a hot wallet like this.
We’ll need to fund the wallet before we can send a transaction to it. To accomplish this, we’ll use a “faucet” which see send us testnet tokens. Go to https://faucet.paradigm.xyz/ and claim tokens from the faucet to the wallet you created in the previous step.
Check the wallet is funded
We’re going to deploy the contract on Ethereum’s Goerli testnet so go to https://goerli.etherscan.io/ and search for the wallet address you previously created. After claiming tokens from the faucet you should see that it has an Ether balance.
You should also run:
cast balance --rpc-url https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161 \
<your_address>
You should notice that there are a lot more 0s behind your ETH balance when running this CLI command.
Now let’s deploy the contract. Run the following command:
forge create --rpc-url <your_rpc_url> --private-key <your_private_key> \
src/FirstToken.sol:FirstToken \
--constructor-args 1000000000000000000000000
Replace <your_rpc_url> with https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161 and with the private key you previously created. This will create a token with a total supply of 100,000 (assuming I counted correctly). If you go to the etherscan link for your account that deployed the contract, you’ll notice your balance is shown under the “Erc20 Token Txns” Tabs (e.g. https://goerli.etherscan.io/address/0x8404a542298827022bf4441afef0a96766be7201#tokentxns for account 0x8404A542298827022bf4441Afef0a96766Be7201 on Goerli) Peeking Under the Hood Let’s take a look at what’s happening under the hood. Go back to Goerli etherscan and look up the transaction you just sent. Find the parameter on the transaction called “Input Data” (often just called “data” or “input”). You may have to expand more properties on the transaction to see it. Now open up the out/FirstToken.sol/FirstToken.json file in your repo. The “Input Data” on the transaction should look similar to the bytecode.object field in the file. Next, look up the address of the contract you just created. Click on the “Contract” tab and you should see the deployed bytecode of the contract. This value should look similar to the deployedBytecode.object property within the FirstToken.json file. Interacting with your Contract Now that you’ve deployed your first contract, let’s interact with it. Checking Your Balance First we need to get the calldata to check your balance cast calldata "balanceOf(address)" <your_eoa_address>
If you look closely at the calldata, you’ll notice that there’s 8 digits after the “0x” prefix at the start of it and the last 40 characters are from your address. Now that we have the calldata run: cast call --rpc-url https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161 \
<your_contract_address> <call_data>
The output of the previous command is a bunch of hex jargon so let’s make it a little easier to read: cast --to-dec <hex_output>
You should see the same number you passed into the constructor when creating the contract. Transferring the Token Let’s now transfer some of your token to another address. Run the following command: cast send --rpc-url https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161 \
--private-key <your_private_key> \
<your_contract_address> \
"transfer(address,uint)" 0x6666664c7d32A5577070EB846f7dFa5f962e5e6a 1000000000000000
This will transfer tokens to my address 0x6666664c7d32A5577070EB846f7dFa5f962e5e6a. Replace this address with an address you control if you want to keep all the tokens for yourself. 3. Read Up on EVM Read the Ethereum Yellow Paper. The Ethereum Yellow Paper is the technical specification for Ethereum and is probably the quickest way to understand how the EVM works at a granular level. The yellow paper is super dense but understanding sections 4-7 are super useful to understand. Feel free to skim/skip all the proofs. 4. Analyzing ERC20 Contracts Lastly, we’ll analyze 2 different implementations of the ERC20 standard. The first is the Solmate contract that we used in part 2. The second is a contract written entirely in Yul, which is a low-level EVM language. Yul is often used for highly optimized contracts in terms of gas efficiency. Analyzing the contract will help us understand what’s happening under the hood when looking at a Solidity contract. Solmate: https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol ERC20 in Yul: https://docs.soliditylang.org/en/v0.8.15/yul.html#complete-erc20-example
