Skip to content

Instantly share code, notes, and snippets.

@quicklearnpro
Forked from emo-eth/chain_funcs.sh
Created January 3, 2023 10:35
Show Gist options
  • Select an option

  • Save quicklearnpro/59e9cd5893e3d98d287b79e810fbd3ac to your computer and use it in GitHub Desktop.

Select an option

Save quicklearnpro/59e9cd5893e3d98d287b79e810fbd3ac to your computer and use it in GitHub Desktop.
Helper functions for interacting with chains and Foundry tests. Source from .zshrc etc
###########
# Imports #
###########
# the RPCs file should include RPC URLs and Etherscan API Keys for relevant networks
source "$(dirname "$0")/rpcs.sh"
# any useful addresses for various networks for easy reference
source "$(dirname "$0")/addresses.sh"
# any useful functions and definitions for interacting with Seaport
source "$(dirname "$0")/seaport.sh"
export ECRECOVER=0x0000000000000000000000000000000000000001
###############
# RPC Helpers #
###############
# block explorer urls for the explore() helper
export ETHEREUM_BLOCK_EXPLORER=https://etherscan.io
export GOERLI_BLOCK_EXPLORER=https://goerli.etherscan.io
export POLYGON_BLOCK_EXPLORER=https://polygonscan.com
export MUMBAI_BLOCK_EXPLORER=https://mumbai.polygonscan.com
export OPTIMISM_BLOCK_EXPLORER=https://optimistic.etherscan.io
export OPTIMISM_GOERLI_BLOCK_EXPLORER=https://goerli-optimism.etherscan.io/
export ARBITRUM_BLOCK_EXPLORER=https://arbiscan.io
export ARBITRUM_NOVA_BLOCK_EXPLORER=https://nova.arbiscan.io
export ARBITRUM_GOERLI_BLOCK_EXPLORER=https://goerli.arbiscan.io
export AVALANCHE_BLOCK_EXPLORER=https://snowtrace.io
export FUJI_BLOCK_EXPLORER=https://testnet.snowtrace.io
export BSC_BLOCK_EXPLORER=https://bscscan.com
export BSC_TEST_BLOCK_EXPLORER=https://testnet.bscscan.com
export GNOSIS_BLOCK_EXPLORER=https://gnosisscan.io
export KLAYTN_BLOCK_EXPLORER=https://scope.klaytn.com
export BAOBAB_BLOCK_EXPLORER=https://baobab.scope.klaytn.com
# Set ETH_RPC_URL and ETHERSCAN_API_KEY (the defaults that forge + cast read) based on chain name
# Uses values sourced from rpcs.sh
chain() {
chain_name=$1
if [[ "$1" == "polygon" ]]
then
export ETH_RPC_URL=$POLYGON_RPC_URL
export ETHERSCAN_API_KEY=$POLYGON_ETHERSCAN_API_KEY
export BLOCK_EXPLORER=$POLYGON_BLOCK_EXPLORER
elif [[ "$1" == "mumbai" ]]
then
export ETH_RPC_URL=$MUMBAI_RPC_URL
export ETHERSCAN_API_KEY=$POLYGON_ETHERSCAN_API_KEY
export BLOCK_EXPLORER=$MUMBAI_BLOCK_EXPLORER
elif [[ "$1" == "goerli" ]]
then
export ETH_RPC_URL=$GOERLI_RPC_URL
export ETHERSCAN_API_KEY=$ETHEREUM_ETHERSCAN_API_KEY
export BLOCK_EXPLORER=$GOERLI_BLOCK_EXPLORER
elif [[ "$1" == "arbitrum" ]]
then
export ETH_RPC_URL=$ARBITRUM_RPC_URL
export ETHERSCAN_API_KEY=$ARBITRUM_ETHERSCAN_API_KEY
export BLOCK_EXPLORER=$ARBITRUM_BLOCK_EXPLORER
elif [[ "$1" == "arbitrum-nova" ]]
then
export ETH_RPC_URL=$ARBITRUM_NOVA_RPC_URL
export ETHERSCAN_API_KEY=$ARBITRUM_ETHERSCAN_API_KEY
export BLOCK_EXPLORER=$ARBITRUM_NOVA_BLOCK_EXPLORER
elif [[ "$1" == "arbitrum-goerli" ]]
then
export ETH_RPC_URL=$ARBITRUM_GOERLI_RPC_URL
export ETHERSCAN_API_KEY=$ARBITRUM_ETHERSCAN_API_KEY
export BLOCK_EXPLORER=$ARBITRUM_GOERLI_BLOCK_EXPLORER
elif [[ "$1" == "optimism" ]]
then
export ETH_RPC_URL=$OPTIMISM_RPC_URL
export ETHERSCAN_API_KEY=$OPTIMISM_ETHERSCAN_API_KEY
export BLOCK_EXPLORER=$OPTIMISM_BLOCK_EXPLORER
elif [[ "$1" == "optimism-goerli" ]]
then
export ETH_RPC_URL=$OPTIMISM_GOERLI_RPC_URL
export ETHERSCAN_API_KEY=$OPTIMISM_ETHERSCAN_API_KEY
export BLOCK_EXPLORER=$OPTIMISM_GOERLI_BLOCK_EXPLORER
elif [[ "$1" == "klaytn" ]]
then
export ETH_RPC_URL=$KLAYTN_RPC_URL
export BLOCK_EXPLORER=$KLAYTN_BLOCK_EXPLORER
elif [[ "$1" == "baobab" ]]
then
export ETH_RPC_URL=$BAOBAB_RPC_URL
export BLOCK_EXPLORER=$BAOBAB_BLOCK_EXPLORER
elif [[ "$1" == "bsc" ]]
then
export ETH_RPC_URL=$BSC_RPC_URL
export BLOCK_EXPLORER=$BSC_BLOCK_EXPLORER
elif [[ "$1" == "bsc-test" ]]
then
export ETH_RPC_URL=$BSC_TEST_RPC_URL
export BLOCK_EXPLORER=$BSC_TEST_BLOCK_EXPLORER
elif [[ "$1" == "anvil" ]]
then
export ETH_RPC_URL=$ANVIL_RPC_URL
elif [[ "$1" == "avalanche" ]]
then
export ETH_RPC_URL=$AVALANCHE_RPC_URL
export BLOCK_EXPLORER=$AVALANCHE_BLOCK_EXPLORER
elif [[ "$1" == "fuji" ]]
then
export ETH_RPC_URL=$FUJI_RPC_URL
export BLOCK_EXPLORER=$FUJI_BLOCK_EXPLORER
else
# fallback is mainnet
export chain_name="mainnet"
export ETHERSCAN_API_KEY=$ETHEREUM_ETHERSCAN_API_KEY
export ETH_RPC_URL=$ETHEREUM_RPC_URL
export BLOCK_EXPLORER=$ETHEREUM_BLOCK_EXPLORER
fi
}
# View an address or transaction hash on the block explorer of the current active chain (configured with chain() command)
# macOS only
explore() {
if [[ ${#1} -eq 42 ]]; then
arg="${BLOCK_EXPLORER}/address/$1"
else
arg="${BLOCK_EXPLORER}/tx/$1"
fi
open -n $arg
}
################
# Cast Helpers #
################
ecrecover() {
cast call $ECRECOVER $(cast abi-encode "ecrecover(bytes32,uint8,bytes32,bytes32)(address)" $1 $2 $3 $4)
}
# "Decimal to Hex"
d2h() {
cast --to-base $1 16
}
# "Hex to Decimal"
h2d() {
cast --to-base $1 10
}
# balanceOf(address)
balanceof() {
cast call $1 "balanceOf(address)(uint256)" $2
}
# ERC721::ownerOf(uint256)
ownerof() {
cast call $1 "ownerOf(uint256)(address)" $2
}
# ERC1155::balanceOf(address, uint256)
balanceof11() {
cast call $1 "balanceOf(address, uint256)(uint256)" $2 $3
}
# ERC721:tokenURI(uint256)
uri() {
cast call $1 "tokenURI(uint256)(string)" $2
}
# ERC1155::uri(uint256)
uri11() {
cast call $1 "uri(uint256)(string)" $2
}
# look up ens name (minus .eth suffix)
ens() {
cast resolve-name $1.eth
}
# look up the admin slot of a proxy
admin() {
cast --abi-decode "sig()(address)" $(cast storage $1 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103)
}
# look up the implementation slot of a proxy
impl() {
cast --abi-decode "sig()(address)" $(cast storage $1 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc)
}
# look up the owner of an ownable contract
owner() {
cast call $1 "owner()(address)"
}
# abi-encode an address
encodeaddr() {
cast abi-encode "sig(address)" $1
}
#################
# Forge Helpers #
#################
# The verbosity config value in foundry.toml normally takes preference over FOUNDRY_VERBOSITY, but defaults to 0
# The helper functions inline this variable to override the verbosity level set in foundry.toml
FOUNDRY_VERBOSITY=3
# "Forge contract" - Run forge tests that match a specific contract
fcon() {
FOUNDRY_VERBOSITY=$FOUNDRY_VERBOSITY forge test --match-contract $1
}
# "Forge contract watch" Run forge tests that match a specific contract and watch for changes
fconw() {
FOUNDRY_VERBOSITY=$FOUNDRY_VERBOSITY forge test --match-contract $1 --watch
}
# "Forge test <test>" - Run forge tests that match a specific test
ftest() {
if [ $# -eq 1 ]; then
FOUNDRY_VERBOSITY=$FOUNDRY_VERBOSITY forge test --match-test $1
elif [ $# -eq 0 ]; then
FOUNDRY_VERBOSITY=$FOUNDRY_VERBOSITY forge test
fi
}
# "Forge test <test> watch" - Run Forge tests that match a specific test and watch for changes
ftestw() {
FOUNDRY_VERBOSITY=$FOUNDRY_VERBOSITY forge test --match-test $1 --watch
}
# "Forge test" - Run all Forge tests for the current active Foundry profile
ft() {
FOUNDRY_VERBOSITY=$FOUNDRY_VERBOSITY forge test
}
# "Forge script" - Run a specific Forge script
fs() {
FOUNDRY_VERBOSITY=$FOUNDRY_VERBOSITY forge snapshot $1
}
# "Forge snapshot" - Run a gas snapshot
snap() {
forge snapshot
}
# "Forge gas" - Run all tests and generate a gas report
fg() {
FOUNDRY_VERBOSITY=$FOUNDRY_VERBOSITY forge test --gas-report
}
# "Forge watch" - Run all tests and watch for changes
fw() {
FOUNDRY_VERBOSITY=$FOUNDRY_VERBOSITY forge test --watch
}
# "Forge build" - Build the project with the current active Foundry profile
fb() {
forge build
}
# "Forge coverage" - Generate a coverage summary report as well as an lcov.info, and generate an HTML report from the lcov.info
# Requires the lcov package to be installed
fcov() {
forge coverage --report summary --report lcov && genhtml lcov.info -o html --branch
}
# "Forge debug" - Debug a specific test
fdebug() {
SEAPORT_COVERAGE=true forge test --debug $1
}
# "Checksum address" - Generate a checksum address from a hex address and copy it to the clipboard
caddr() {
new_addr=$(cast --to-checksum-address $1)
echo $new_addr
echo $new_addr | pbcopy
}
# Generate standard JSON input for a contract. Useful for verifying contracts on Etherscan
stdjson() {
mkdir -p stdjson
forge verify-contract $(cast --address-zero) $1 --show-standard-json-input > stdjson/$1.json
echo "Standard JSON for $1 written to stdjson/$1.json"
}
# "Forge new file" - create a new contract file that includes the SPDX license header, pragma, and empty contract with the specified name
fnf() {
echo "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.17;\n\ncontract $2 {\n\n}" > ./$1/$2.sol
}
# "Forge new base test" - create a new base test file that imports the Forge Test contract and includes an empty virtual setUp() function
fnbt() {
echo "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.17;\n\nimport {Test} from \"forge-std/Test.sol\";\n\ncontract Base$1Test is Test {\n function setUp() public virtual { }\n}" > ./test/Base$1Test.sol
}
# "Forge new test" - create a new test file that imports the base test contract and includes an empty test function
# Note: Assumes there is a BaseTest contract and that it is located in the same directory as the test contract
fnt() {
echo "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.17;\n\nimport {BaseTest} from \"test/BaseTest.sol\";\n\ncontract $2Test is BaseTest {\n\n}" > ./test/$1/$2.t.sol
}
# "Forge new script" - create a new script file that imports the base Script contract and console2, and includes an empty run() function
fns() {
echo "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.17;\n\nimport {Script, console2} from \"forge-std/Script.sol\";\n\ncontract $2 is Script {\n function run() public { }\n}" > ./script/$1/$2.s.sol
}
# "Forge new CREATE2 script" - create a new script file that imports the base Create2Script contract and console2, and includes an empty run() function
fncs() {
echo "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.17;\n\nimport {BaseCreate2Script, console2} from \"create2-scripts/BaseCreate2Script.s.sol\";\n\ncontract $2 is BaseCreate2Script {\n function run() public { }\n}" > ./script/$1/$2.s.sol
}
# for easily testing forge scripts
# eg: in script: `address deployer = vm.addr(vm.envUint('anvilPk'))`, or
# `address deployer = vm.envAddress("anvilAddr")` + in bash command: `--private-key $anvilPk`, etc
export anvilAddr=0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
export anvilPk=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
###########
# Aliases #
###########
# "good morning" - update Foundry and Huff
alias gm="foundryup;huffup;"
# Use forge remappings to generate a remappings file
alias remap="forge remappings > remappings.txt"
# "edit chain functions" - open the chain_funcs.sh file in VS Code
alias ecf="code \"$(dirname "$0")/chain_funcs.sh\""
# "edit rpcs" - open the rpcs.sh file in VS Code
alias erpc="code \"$(dirname "$0")/rpcs.sh\""
# "edit addresses" - open the addresses.sh file in VS Code
alias eaddr="code \"$(dirname "$0")/addresses.sh\""
# "edit seaport" - open the seaport.sh file in VS Code
alias esp="code \"$(dirname "$0")/seaport.sh\""
###############
# Boilerplate #
###############
# automatically configure mainnet when opening a new shell, if ETH_RPC_URL is not already configured
if [[ -z "$ETH_RPC_URL" ]] then
chain
fi
# optional - add current active chain name to your bash prompt
# if using powerlevel10k theme, add this function to your p10k.zsh
# and add `chain` to either your POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS or POWERLEVEL9K_LEFT_PROMPT_ELEMENTS
# function prompt_chain() {
# p10k segment -b 14 -i '🔗' -t "$chain_name"
# }
# if not using a fancy terminal theme - uncomment to prepend active chain to prompt, ie [mainnet]
# export PS1='[$chain_name]'$PS1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment