🧮 How to install and use Paradigm Foundry to test and deploy smart contracts
Learn how to deploy and debug easily any smart contract on the Ethereum blockchain.
As they say, Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.
Apart of the speed another difference with Truffle or Hardhat is that the scripts and tests in Foundry are written in Solidity, not Javascript.
How to install foundry
Let’s have a quick look how to install it in our Linux system and get to understand how to run all the tooling:
$ curl -L https://foundry.paradigm.xyz | bash
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
100 1765 100 1765 0 0 4412 0 --:--:-- --:--:-- --:--:-- 4412
Installing foundryup...
######################################################################## 100.0%
Detected your preferred shell is bash and added foundryup to PATH. Run 'source /home/user/.bashrc' or start a new terminal session to use foundryup.
Then, simply run 'foundryup' to install Foundry.
$ . .bashrc
$ foundryup
foundryup: installing foundry (version nightly, tag nightly-1c415857dd7b617190834dbcb361506f6143fed4)
foundryup: downloading latest forge, cast and anvil
################################################################################################################################################# 100.0%
foundryup: downloading manpages
################################################################################################################################################# 100.0%
foundryup: installed - forge 0.2.0 (1c41585 2022-09-04T00:08:27.451724173Z)
foundryup: installed - cast 0.2.0 (1c41585 2022-09-04T00:08:27.451724173Z)
foundryup: installed - anvil 0.1.0 (1c41585 2022-09-04T00:08:27.527236025Z)
foundryup: done
Let’s check the options of forge
, cast
, and anvil
before continue:
Forge is the Ethereum development and testing framework (Like hardhat or truffle):
$ forge
forge 0.2.0 (1c41585 2022-09-04T00:08:27.451724173Z)
Build, test, fuzz, debug and deploy Solidity contracts.
USAGE:
forge <SUBCOMMAND>
OPTIONS:
-h, --help Print help information
-V, --version Print version information
SUBCOMMANDS:
bind Generate Rust bindings for smart contracts.
build Build the project's smart contracts. [aliases: b]
cache Manage the Foundry cache.
clean Remove the build artifacts and cache directories. [aliases: cl]
completions Generate shell completions script. [aliases: com]
config Display the current config. [aliases: co]
coverage Generate coverage reports.
create Deploy a smart contract. [aliases: c]
debug Debugs a single smart contract as a script. [aliases: d]
flatten Flatten a source file and all of its imports into one file. [aliases: f]
fmt Formats Solidity source files.
geiger Detects usage of unsafe cheat codes in a foundry project and its dependencies.
generate-fig-spec Generate Fig autocompletion spec. [aliases: fig]
help Print this message or the help of the given subcommand(s)
init Create a new Forge project.
inspect Get specialized information about a smart contract. [aliases: in]
install Install one or multiple dependencies. [aliases: i]
remappings Get the automatically inferred remappings for the project. [aliases: re]
remove Remove one or multiple dependencies. [aliases: rm]
script Run a smart contract as a script, building transactions that can be sent onchain.
snapshot Create a snapshot of each test's gas usage. [aliases: s]
test Run the project's tests. [aliases: t]
tree Display a tree visualization of the project's dependency graph. [aliases: tr]
update Update one or multiple dependencies. [aliases: u]
upload-selectors Uploads abi of given contract to https://sig.eth.samczsun.com function selector database. [aliases: up]
verify-check Check verification status on Etherscan. [aliases: vc]
verify-contract Verify smart contracts on Etherscan. [aliases: v]
Find more information in the book: http://book.getfoundry.sh/reference/forge/forge.html
Cast is the CLI to interact with smart contracts on a low-level basis:
$ cast
cast 0.2.0 (1c41585 2022-09-04T00:08:27.451724173Z)
Perform Ethereum RPC calls from the comfort of your command line.
USAGE:
cast <SUBCOMMAND>
OPTIONS:
-h, --help Print help information
-V, --version Print version information
SUBCOMMANDS:
--abi-decode Decode ABI-encoded input or output data [aliases: ad]
--address-zero Get zero address [aliases: address-zero, az]
--calldata-decode Decode ABI-encoded input data. [aliases: cdd]
--concat-hex Concatenate hex strings. [aliases: concat-hex, ch]
--format-bytes32-string Formats a string into bytes32 encoding.
--from-bin Convert binary data into hex data. [aliases: from-bin, fb]
--from-fix Convert a fixed point number into an integer. [aliases: from-fix, ff]
--from-rlp Decodes RLP encoded data. Input must be hexadecimal.
--from-utf8 Convert UTF8 text to hex. [aliases: from-utf8, --from-ascii, from-ascii, fu, fa]
--from-wei Convert wei into an ETH amount. Consider using --to-unit. [aliases: from-wei, fw]
--hash-zero Get zero hash [aliases: hash-zero, hz]
--max-int Get the maximum i256 value. [aliases: max-int, maxi]
--max-uint Get the maximum u256 value. [aliases: max-uint, maxu]
--min-int Get the minimum i256 value. [aliases: min-int, mini]
--parse-bytes32-string Parses a string from bytes32 encoding.
--to-ascii Convert hex data to an ASCII string. [aliases: to-ascii, tas, 2as]
--to-bytes32 Right-pads hex data to 32 bytes. [aliases: to-bytes32, tb, 2b]
--to-checksum-address Convert an address to a checksummed format (EIP-55). [aliases: to-checksum-address, --to-checksum, to-checksum, ta,
2a]
--to-dec Convert hex value into a decimal number. [aliases: to-dec, td, 2d]
--to-fix Convert an integer into a fixed point number. [aliases: to-fix, tf, 2f]
--to-hex Convert an integer to hex. [aliases: to-hex, th, 2h]
--to-hexdata Normalize the input to lowercase, 0x-prefixed hex. See --help for more info. [aliases: to-hexdata, thd, 2hd]
--to-int256 Convert a number to a hex-encoded int256. [aliases: to-int256, ti, 2i]
--to-rlp RLP encodes hex data, or an array of hex data
--to-uint256 Convert a number to a hex-encoded uint256. [aliases: to-uint256, tu, 2u]
--to-unit Convert an ETH amount into another unit (ether, gwei or wei). [aliases: to-unit, tun, 2un]
--to-wei Convert an ETH amount to wei. Consider using --to-unit. [aliases: to-wei, tw, 2w]
4byte Get the function signatures for the given selector from https://sig.eth.samczsun.com. [aliases: 4, 4b]
4byte-decode Decode ABI-encoded calldata using https://sig.eth.samczsun.com. [aliases: 4d, 4bd]
4byte-event Get the event signature for a given topic 0 from https://sig.eth.samczsun.com. [aliases: 4e, 4be]
abi-encode ABI encode the given function argument, excluding the selector. [aliases: ae]
access-list Create an access list for a transaction. [aliases: ac, acl]
age Get the timestamp of a block. [aliases: a]
balance Get the balance of an account in wei. [aliases: b]
basefee Get the basefee of a block. [aliases: ba, fee]
block Get information about a block. [aliases: bl]
block-number Get the latest block number. [aliases: bn]
call Perform a call on an account without publishing a transaction. [aliases: c]
calldata ABI-encode a function with arguments. [aliases: cd]
chain Get the symbolic name of the current chain. [aliases: ch]
chain-id Get the Ethereum chain ID. [aliases: ci, cid]
client Get the current client version. [aliases: cl]
code Get the bytecode of a contract. [aliases: co]
completions Generate shell completions script [aliases: com]
compute-address Compute the contract address from a given nonce and deployer address. [aliases: ca]
estimate Estimate the gas cost of a transaction. [aliases: e]
etherscan-source Get the source code of a contract from Etherscan. [aliases: et, src]
find-block Get the block number closest to the provided timestamp. [aliases: f]
gas-price Get the current gas price. [aliases: g]
generate-fig-spec Generate Fig autocompletion spec. [aliases: fig]
help Print this message or the help of the given subcommand(s)
index Compute the storage slot for an entry in a mapping. [aliases: in]
interface Generate a Solidity interface from a given ABI. [aliases: i]
keccak Hash arbitrary data using keccak-256. [aliases: k]
lookup-address Perform an ENS reverse lookup. [aliases: l]
namehash Calculate the ENS namehash of a name. [aliases: na, nh]
nonce Get the nonce for an account. [aliases: n]
pretty-calldata Pretty print calldata. [aliases: pc]
proof Generate a storage proof for a given storage slot. [aliases: pr]
publish Publish a raw transaction to the network. [aliases: p]
receipt Get the transaction receipt for a transaction. [aliases: re]
resolve-name Perform an ENS lookup. [aliases: rn]
rpc Perform a raw JSON-RPC request [aliases: rp]
run Runs a published transaction in a local environment and prints the trace. [aliases: r]
send Sign and publish a transaction. [aliases: s]
shl Perform a left shifting operation
shr Perform a right shifting operation
sig Get the selector for a function. [aliases: si]
storage Get the raw value of a contract's storage slot. [aliases: st]
tx Get information about a transaction. [aliases: t]
upload-signature Upload the given signatures to https://sig.eth.samczsun.com. [aliases: ups]
wallet Wallet management utilities. [aliases: w]
Find more information in the book: http://book.getfoundry.sh/reference/cast/cast.html
And finally, anvil is the local Ethereum node (Like Ganache or Hardhat node):
$ anvil
_ _
(_) | |
__ _ _ __ __ __ _ | |
/ _` | | '_ \ \ \ / / | | | |
| (_| | | | | | \ V / | | | |
\__,_| |_| |_| \_/ |_| |_|
0.1.0 (1c41585 2022-09-04T00:08:27.527236025Z)
https://github.com/foundry-rs/foundry
Available Accounts
==================
(0) 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 (10000 ETH)
(1) 0x70997970c51812dc3a010c7d01b50e0d17dc79c8 (10000 ETH)
(2) 0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc (10000 ETH)
(3) 0x90f79bf6eb2c4f870365e785982e1f101e93b906 (10000 ETH)
(4) 0x15d34aaf54267db7d7c367839aaf71a00a2c6a65 (10000 ETH)
(5) 0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc (10000 ETH)
(6) 0x976ea74026e726554db657fa54763abd0c3a0aa9 (10000 ETH)
(7) 0x14dc79964da2c08b23698b3d3cc7ca32193d9955 (10000 ETH)
(8) 0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f (10000 ETH)
(9) 0xa0ee7a142d267c1f36714e4a8f75612f20a79720 (10000 ETH)
Private Keys
==================
(0) 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
(1) 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
(2) 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a
(3) 0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6
(4) 0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a
(5) 0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba
(6) 0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e
(7) 0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356
(8) 0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97
(9) 0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6
Wallet
==================
Mnemonic: test test test test test test test test test test test junk
Derivation path: m/44'/60'/0'/0/
Base Fee
==================
1000000000
Gas Limit
==================
30000000
Genesis Timestamp
==================
1662332232
Listening on 127.0.0.1:8545
How to compile a smart contract
Now let’s build the template project.
First we have to initialize the forge template in an empty directory, so:
$ mkdir test
$ forge init
Initializing test...
Installing forge-std in "/tmp/test/lib/forge-std" (url: Some("https://github.com/foundry-rs/forge-std"), tag: None)
Installed forge-std
Initialized forge project.
That will create the following structure:
$ ls
foundry.toml lib/ script/ src/ test/
And this is the template contract:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
contract Counter {
uint256 public number;
function setNumber(uint256 newNumber) public {
number = newNumber;
}
function increment() public {
number++;
}
}
Now we can build the default project (src/Counter.sol):
$ forge build
[â Š] Compiling...
[â °] installing solc version "0.8.16"
[â ˜] Successfully installed solc 0.8.16
[â ’] Compiling 10 files with 0.8.16
[â ’] Solc 0.8.16 finished in 3.90s
Compiler run successful
$ forge test
[â †] Compiling...
No files changed, compilation skipped
Running 2 tests for test/Counter.t.sol:CounterTest
[PASS] testIncrement() (gas: 28312)
[PASS] testSetNumber(uint256) (runs: 256, μ: 27531, ~: 28387)
Test result: ok. 2 passed; 0 failed; finished in 35.94ms
To deploy the contract we must call the contract script, in this case under script/Counter.s.sol. One of the main differences between forge and truffle or hardhat is that in forge, the test scripts are written in Solidity instead of JavaScript:
$ cat script/Counter.s.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "forge-std/Script.sol";
contract CounterScript is Script {
function setUp() public {}
function run() public {
vm.broadcast();
}
}
The function run() will be called when the contract is being deployed.
After the compile and test, we can run the scripts locally using one of the private keys given by anvil
before and the local RPC node:
$ forge script script/Counter.s.sol:CounterScript --fork-url http://localhost:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
[â †] Compiling...
No files changed, compilation skipped
Script ran successfully.
And finally, deploy the contract on localnet:
$ forge create Counter --rpc-url http://localhost:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
[â †] Compiling...
No files changed, compilation skipped
Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
Transaction hash: 0xe065e23fd862dfa2283ba4c87a77539dec4e3a972e5d90de265cb3b5d4de7ae6
With the deployed address of the smart contract (0x5FbDB2315678afecb367f032d93F642f64180aa3
), we can retrieve the smart contract bytecode directly from the local blockchain:
$ cast code 0x5FbDB2315678afecb367f032d93F642f64180aa3
0x6080604052348015600f57600080fd5b5060043610603c5760003560e01c80633fb5c1cb1460415780638381f58a146053578063d09de08a14606d575b600080fd5b6051604c3660046083565b600055565b005b605b60005481565b60405190815260200160405180910390f35b6051600080549080607c83609b565b9190505550565b600060208284031215609457600080fd5b5035919050565b60006001820160ba57634e487b7160e01b600052601160045260246000fd5b506001019056fea2646970667358221220f4a9b22e7a2d64c24355b4e7a6f8c62115ca728f26fc2a1e98e364ee91f794fa64736f6c63430008100033
How to call smart contract methods
To make a low-level call to a method of the smart contract, we can use cast call
and cast send
.
To initialize the number:
$ cast send 0x5FbDB2315678afecb367f032d93F642f64180aa3 "setNumber(uint256)" "34" --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
blockHash 0x9bc16ad14fc77a9a295145f69d2aa0ea43c494655620d2facce458f16a2b09cd
blockNumber 3
contractAddress
cumulativeGasUsed 26394
effectiveGasPrice 3671048612
gasUsed 26394
logs []
logsBloom 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
root
status 1
transactionHash 0xd438f5b0dbd78ccbe3935fffc0b52c563cb5a654186b89a6fd1815177ce92e45
transactionIndex 0
type
To call increment():
$ cast send 0x5FbDB2315678afecb367f032d93F642f64180aa3 "increment()" --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
blockHash 0xe94baa8582bf0893a4cd69913f2d50a33eed5c41b7e2403bfa885f88437e89dd
blockNumber 4
contractAddress
cumulativeGasUsed 26304
effectiveGasPrice 3587314629
gasUsed 26304
logs []
logsBloom 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
root
status 1
transactionHash 0x9783814115f16e979f377d3aea559afc5ce4a4dc4afac1161531f9bd1a255b5e
transactionIndex 0
type
As mentioned in the Solidity documentation, the compiler creates getter() functions for all public state variables, so we can now call number()
to validate that all methods worked successfully.
To call cast call
we do not need to specify a private key. Functions to just get data from the blockchain and do not modify them, do not consume gas... so they are free :)
$ cast call 0x5FbDB2315678afecb367f032d93F642f64180aa3 "number()"
0x0000000000000000000000000000000000000000000000000000000000000023
Seems it worked! Lets really check if 0x23 is 35 in decimal notation.
$ python3
Python 3.9.2 (default, Feb 28 2021, 17:03:44)
[GCC 10.2.1 20210110] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 0x23
35
>>>
Mission accomplished!
Bonus track: How to clone Mainnet with Anvil
anvil --fork-url https://mainnet.infura.io/v3/$INFURA_KEY