| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566 |
- #!/bin/bash
- # This tool automates the process of writing contract upgrade governance
- # proposals in markdown format.
- #
- # There are two ways to run this script: either in "one-shot" mode, where a
- # single governance VAA is generated:
- #
- # ./contract-upgrade-governance.sh -m token_bridge -c solana -a Hp1YjsMbapQ75qpLaHQHuAv5Q8QwPoXs63zQrrcgg2HL > governance.md
- #
- # or in "multi" mode, where multiple VAAs are created in the same proposal:
- #
- # ./contract-upgrade-governance.sh -m token_bridge -c solana -a Hp1YjsMbapQ75qpLaHQHuAv5Q8QwPoXs63zQrrcgg2HL -o my_proposal > governance.md
- # ./contract-upgrade-governance.sh -m token_bridge -c avalanche -a 0x45fC4b6DD26097F0E51B1C91bcc331E469Ca73c2 -o my_proposal > governance.md
- # ... -o my_proposal > governance.md
- #
- # In multi mode, there's an additional "-o" flag, which takes a directory name,
- # where intermediate progress is saved between runs. If the directory doesn't
- # exist, the tool will create it.
- #
- # In both one-shot and multi modes, the script outputs the markdown-formatted
- # proposal to STDOUT, so it's a good idea to pipe it into a file (as in the above examples).
- #
- # In multi-mode, it always outputs the most recent version, so it's safe to
- # override the previous files.
- #
- # Once a multi-mode run is completed, the directory specified with the -o flag can be deleted.
- set -euo pipefail
- usage() {
- cat <<-EOF >&2
- Usage: $(basename "$0") [OPTIONS]
- Generate governance proposal for a module to be upgraded to a given address.
- Options:
- -h, --help Show this help message
- -m, --module <module> Specify the module (bridge, token_bridge, nft_bridge)
- -c, --chain <chain_name> Specify the chain name
- -a, --address <address> Specify the new code address (e.g., 0x3f1a6729bb27350748f0a0bd85ca641a100bf0a1)
- -o, --output <output_dir> Specify the multi-mode output directory
- -f, --force Force: bypass dirty git repo check
- EOF
- exit 1
- }
- # Check if guardiand command exists. It's needed for generating the protoxt and
- # computing the digest.
- if ! command -v guardiand >/dev/null 2>&1; then
- echo "ERROR: guardiand binary not found" >&2
- exit 1
- fi
- # Check if the worm command exists. It's needed for computing the digest.
- if ! command -v worm >/dev/null 2>&1; then
- echo "ERROR: worm binary not found" >&2
- exit 1
- fi
- ### Parse command line options
- address=""
- module=""
- chain_name=""
- multi_mode=false
- out_dir=""
- allow_dirty=false
- while (( "$#" )); do
- case "$1" in
- -h|--help)
- usage
- ;;
- -m|--module)
- module="$2"
- shift 2
- ;;
- -c|--chain)
- chain_name="$2"
- shift 2
- ;;
- -a|--address)
- address="$2"
- shift 2
- ;;
- -o|--output)
- multi_mode=true
- out_dir="$2"
- shift 2
- ;;
- -f|--force)
- allow_dirty=true
- shift
- ;;
- --) # end of options
- shift
- break
- ;;
- -*)
- echo "Error: Unsupported option $1" >&2
- usage
- ;;
- *) # anything else
- echo "Error: Unsupported argument $1" >&2
- usage
- ;;
- esac
- done
- [ -z "$address" ] && usage
- [ -z "$chain_name" ] && usage
- [ -z "$module" ] && usage
- # Check if the git tree is dirty
- if [ "$allow_dirty" = false ]; then
- if ! git diff-index --quiet HEAD --; then
- echo "ERROR: git tree is dirty. Commit or stash your changes first." >&2
- echo "If you are sure you want to proceed, use the --force flag." >&2
- exit 1
- fi
- fi
- ### The script constructs the governance proposal in two different steps. First,
- ### the governance prototxt (for VAA injection by the guardiand tool), then the voting/verification instructions.
- gov_msg_file=""
- instructions_file=""
- if [ "$multi_mode" = true ]; then
- mkdir -p "$out_dir"
- gov_msg_file="$out_dir/governance.prototxt"
- instructions_file="$out_dir/instructions.md"
- else
- gov_msg_file=$(mktemp)
- instructions_file=$(mktemp)
- fi
- explorer=""
- evm=false
- # TODO: move to CLI
- case "$chain_name" in
- solana)
- chain=1
- explorer="https://explorer.solana.com/address/"
- extra=""
- ;;
- fogo)
- chain=51
- explorer="https://explorer.fogo.io/address/"
- extra=""
- ;;
- pythnet)
- chain=26
- explorer="https://explorer.solana.com/address/"
- extra="Be sure to choose \"Custom RPC\" as the cluster in the explorer and set it to https://pythnet.rpcpool.com"
- ;;
- ethereum)
- chain=2
- explorer="https://etherscan.io/address/"
- evm=true
- ;;
- terra)
- chain=3
- # This is not technically the explorer, but terra finder does not show
- # information about code ids, so this is the best we can do.
- explorer="https://terra-classic-lcd.publicnode.com/cosmwasm/wasm/v1/code/"
- ;;
- bsc)
- chain=4
- explorer="https://bscscan.com/address/"
- evm=true
- ;;
- polygon)
- chain=5
- explorer="https://polygonscan.com/address/"
- evm=true
- ;;
- avalanche)
- chain=6
- explorer="https://snowtrace.io/address/"
- evm=true
- ;;
- oasis)
- chain=7
- explorer="https://explorer.emerald.oasis.dev/address/"
- evm=true
- ;;
- aurora)
- chain=9
- explorer="https://aurorascan.dev/address/"
- evm=true
- ;;
- algorand)
- chain=8
- explorer="https://algoexplorer.io/address/"
- ;;
- fantom)
- chain=10
- explorer="https://ftmscan.com/address/"
- evm=true
- ;;
- karura)
- chain=11
- explorer="https://blockscout.karura.network/address/"
- evm=true
- ;;
- acala)
- chain=12
- explorer="https://blockscout.acala.network/address/"
- evm=true
- ;;
- klaytn)
- chain=13
- explorer="https://scope.klaytn.com/account/"
- evm=true
- ;;
- celo)
- chain=14
- explorer="https://celoscan.xyz/address/"
- evm=true
- ;;
- near)
- chain=15
- explorer="https://explorer.near.org/accounts/"
- ;;
- arbitrum)
- chain=23
- explorer="https://arbiscan.io/address/"
- evm=true
- ;;
- optimism)
- chain=24
- explorer="https://optimistic.etherscan.io/address/"
- evm=true
- ;;
- aptos)
- chain=22
- explorer="https://explorer.aptoslabs.com/account/"
- ;;
- base)
- echo "Need to specify the base explorer URL!"
- exit 1
- chain=30
- explorer="??/address/"
- evm=true
- ;;
- *)
- echo "Unknown chain: $chain_name" >&2
- exit 1
- ;;
- esac
- # On terra, the contract given is a decimal code id. We convert it to a 32 byte
- # hex first. The printf is escaped, which makes no difference when we actually
- # evaluate the governance command later, but shows up unevaluated in the
- # instructions (so it's easier to read)
- terra_code_id=""
- if [ "$chain_name" = "terra" ]; then
- terra_code_id="$address" # save code id for later
- address="\$(printf \"%064x\" $terra_code_id)"
- fi
- # Generate the command to create the governance prototxt
- function create_governance() {
- case "$module" in
- bridge|core)
- echo "\
- guardiand template contract-upgrade \\
- --chain-id $chain \\
- --new-address $address"
- ;;
- token_bridge)
- echo "\
- guardiand template token-bridge-upgrade-contract \\
- --chain-id $chain --module \"TokenBridge\" \\
- --new-address $address"
- ;;
- nft_bridge)
- echo "\
- guardiand template token-bridge-upgrade-contract \\
- --chain-id $chain --module \"NFTBridge\" \\
- --new-address $address"
- ;;
- wormhole_relayer)
- echo "\
- guardiand template token-bridge-upgrade-contract \\
- --chain-id $chain --module \"WormholeRelayer\" \\
- --new-address $address"
- ;;
- *) echo "unknown module $module" >&2
- usage
- ;;
- esac
- }
- function evm_artifact() {
- case "$module" in
- bridge|core)
- echo "build/contracts/Implementation.json"
- ;;
- token_bridge)
- echo "build/contracts/BridgeImplementation.json"
- ;;
- nft_bridge)
- echo "build/contracts/NFTBridgeImplementation.json"
- ;;
- *) echo "unknown module $module" >&2
- usage
- ;;
- esac
- }
- function solana_artifact() {
- case "$module" in
- bridge|core)
- echo "artifacts-$chain_name-mainnet/bridge.so"
- ;;
- token_bridge)
- echo "artifacts-$chain_name-mainnet/token_bridge.so"
- ;;
- nft_bridge)
- echo "artifacts-$chain_name-mainnet/nft_bridge.so"
- ;;
- *) echo "unknown module $module" >&2
- usage
- ;;
- esac
- }
- function near_artifact() {
- case "$module" in
- bridge|core)
- echo "artifacts/near_wormhole.wasm"
- ;;
- token_bridge)
- echo "artifacts/near_token_bridge.wasm"
- ;;
- *) echo "unknown module $module" >&2
- usage
- ;;
- esac
- }
- function algorand_artifact() {
- case "$module" in
- bridge|core)
- echo "artifacts/core_approve.teal.hash"
- ;;
- token_bridge)
- echo "artifacts/token_approve.teal.hash"
- ;;
- *) echo "unknown module $module" >&2
- usage
- ;;
- esac
- }
- function terra_artifact() {
- case "$module" in
- bridge|core)
- echo "artifacts/wormhole.wasm"
- ;;
- token_bridge)
- echo "artifacts/token_bridge_terra.wasm"
- ;;
- *) echo "unknown module $module" >&2
- usage
- ;;
- esac
- }
- ################################################################################
- # Construct the governance proto
- echo "# $module upgrade on $chain_name" >> "$gov_msg_file"
- # Append the new governance message to the gov file
- eval "$(create_governance)" >> "$gov_msg_file"
- # Multiple messages will include multiple 'current_set_index' fields, but the
- # proto format only takes one. This next part cleans up the file so there's only
- # a single 'current_set_index' field.
- # 1. we grab the first one and save it
- current_set_index=$(grep "current_set_index" "$gov_msg_file" | head -n 1)
- # 2. remove all 'current_set_index' fields
- rest=$(grep -v "current_set_index" "$gov_msg_file")
- # 3. write the set index
- echo "$current_set_index" > "$gov_msg_file"
- # 4. then the rest of the file
- echo "$rest" >> "$gov_msg_file"
- ################################################################################
- # Compute expected digests
- # just use the 'guardiand' command, which spits out a bunch of text to
- # stderr. We grab that output and pick out the VAA hashes
- verify=$(guardiand admin governance-vaa-verify "$gov_msg_file" 2>&1)
- digest=$(echo "$verify" | grep "VAA with digest" | cut -d' ' -f6 | sed 's/://g')
- # massage the digest into the same format that the inject command prints it
- digest=$(echo "$digest" | awk '{print toupper($0)}' | sed 's/^0X//')
- # we use the first 7 characters of the digest as an identifier for the prototxt file
- gov_id=$(echo "$digest" | cut -c1-7)
- ################################################################################
- # Print vote command and expected digests
- # This we only print to stdout, because in multi mode, it gets recomputed each
- # time. The rest of the output gets printed into the instructions file
- cat <<-EOD
- # Governance
- Shell command for voting:
- \`\`\`shell
- cat << EOF > governance-$gov_id.prototxt
- $(cat "$gov_msg_file")
- EOF
- guardiand admin governance-vaa-inject --socket /path/to/admin.sock governance-$gov_id.prototxt
- \`\`\`
- Expected digest(s):
- \`\`\`
- $digest
- \`\`\`
- EOD
- ################################################################################
- # Verification instructions
- # The rest of the output is printed to the instructions file (which then also
- # gets printed to stdout at the end)
- echo "# Verification steps ($chain_name $module)
- " >> "$instructions_file"
- # Print instructions on checking out the current git hash:
- git_hash=$(git rev-parse HEAD)
- echo "
- ## Checkout the current git hash
- \`\`\`shell
- git fetch
- git checkout $git_hash
- \`\`\`" >> "$instructions_file"
- # Verification steps depend on the chain.
- if [ "$evm" = true ]; then
- cat <<-EOF >> "$instructions_file"
- ## Build
- \`\`\`shell
- wormhole/ethereum $ make
- \`\`\`
- ## Verify
- Contract at [$explorer$address]($explorer$address)
- Next, use the \`verify\` script to verify that the deployed bytecodes we are upgrading to match the build artifacts:
- \`\`\`shell
- wormhole/ethereum $ ./verify -r $(worm info rpc mainnet $chain_name) -c $chain_name $(evm_artifact) $address
- \`\`\`
- EOF
- elif [ "$chain_name" = "solana" ] || [ "$chain_name" = "fogo" ] || [ "$chain_name" = "pythnet" ]; then
- cat <<-EOF >> "$instructions_file"
- ## Build
- \`\`\`shell
- wormhole/solana $ make clean
- wormhole/solana $ make NETWORK=mainnet SVM=$chain_name artifacts
- \`\`\`
- This command will compile all the contracts into the \`artifacts-$chain_name-mainnet\` directory using Docker to ensure that the build artifacts are deterministic.
- ## Verify
- Contract at [$explorer$address]($explorer$address)
- $extra
- Next, use the \`verify\` script to verify that the deployed bytecodes we are upgrading to match the build artifacts:
- \`\`\`shell
- # $module
- wormhole/solana$ ./verify -n mainnet -s $chain_name $(solana_artifact) $address
- \`\`\`
- EOF
- elif [ "$chain_name" = "near" ]; then
- cat <<-EOF >> "$instructions_file"
- ## Build
- \`\`\`shell
- wormhole/near $ make artifacts
- \`\`\`
- This command will compile all the contracts into the \`artifacts\` directory using Docker to ensure that the build artifacts are deterministic.
- Next, you can look at the checksums of the built .wasm files
- \`\`\`shell
- # $module
- wormhole/near$ sha256sum $(near_artifact)
- \`\`\`
- EOF
- elif [ "$chain_name" = "algorand" ]; then
- cat <<-EOF >> "$instructions_file"
- ## Build
- \`\`\`shell
- wormhole/algorand $ make artifacts
- \`\`\`
- This command will compile all the contracts into the \`artifacts\` directory using Docker to ensure that the build artifacts are deterministic.
- You can then review $(algorand_artifact) to confirm the supplied hash value
- EOF
- elif [ "$chain_name" = "terra" ]; then
- cat <<-EOF >> "$instructions_file"
- ## Build
- \`\`\`shell
- wormhole/terra $ make clean
- wormhole/terra $ make artifacts
- \`\`\`
- This command will compile all the contracts into the \`artifacts\` directory using Docker to ensure that the build artifacts are deterministic.
- ## Verify
- Contract at [$explorer$terra_code_id]($explorer$terra_code_id)
- Next, use the \`verify\` script to verify that the deployed bytecodes we are upgrading to match the build artifacts:
- \`\`\`shell
- # $module
- wormhole/terra$ ./verify -n mainnet $(terra_artifact) $terra_code_id
- \`\`\`
- EOF
- elif [ "$chain_name" = "aptos" ]; then
- cat <<-EOF >> "$instructions_file"
- ## Build
- \`\`\`shell
- wormhole/aptos $ docker build -f Dockerfile --target aptos -t aptos-build .
- \`\`\`
- This command will build a docker image that can compile the contracts reproducibly.
- ## Verify
- Next, run the following command to check that the contract hash matches the expected value ($address):
- \`\`\`shell
- # $module
- wormhole/aptos$ docker run -it aptos-build
- wormhole/aptos$ worm aptos hash-contracts /tmp/$module --named-addresses wormhole=0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625,deployer=0x0108bc32f7de18a5f6e1e7d6ee7aff9f5fc858d0d87ac0da94dd8d2a5d267d6b,token_bridge=0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f,nft_bridge=0x1bdffae984043833ed7fe223f7af7a3f8902d04129b14f801823e64827da7130
- wormhole/aptos$ exit
- \`\`\`
- EOF
- else
- echo "ERROR: no verification instructions for chain $chain_name" >&2
- exit 1
- fi
- cat <<-EOF >> "$instructions_file"
- ## Create governance
- \`\`\`shell
- $(create_governance)
- \`\`\`
- EOF
- # Finally print instructions to stdout
- cat "$instructions_file"
|