2022-05-23 10:36:52 -07:00
#!/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
2023-09-29 08:11:43 -07:00
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
2022-05-23 10:36:52 -07:00
EOF
2023-09-29 08:11:43 -07:00
exit 1
2022-05-23 10:36:52 -07:00
}
2022-08-08 16:50:05 -07:00
# 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
2022-05-23 10:36:52 -07:00
2023-09-29 08:11:43 -07:00
# 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
2022-05-23 10:36:52 -07:00
### Parse command line options
address = ""
module = ""
chain_name = ""
multi_mode = false
out_dir = ""
2023-09-29 08:11:43 -07:00
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
; ;
2022-05-23 10:36:52 -07:00
esac
done
[ -z " $address " ] && usage
[ -z " $chain_name " ] && usage
[ -z " $module " ] && usage
2023-09-29 08:11:43 -07:00
# 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
2022-05-23 10:36:52 -07:00
### 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
2022-06-30 04:53:43 -07:00
# TODO: move to CLI
2022-05-23 10:36:52 -07:00
case " $chain_name " in
solana)
chain = 1
explorer = "https://explorer.solana.com/address/"
2023-02-10 08:15:05 -08:00
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"
2022-05-23 10:36:52 -07:00
; ;
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://lcd.terra.dev/terra/wasm/v1beta1/codes/"
; ;
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
; ;
2022-09-19 07:08:55 -07:00
algorand)
chain = 8
explorer = "https://algoexplorer.io/address/"
; ;
2022-05-23 10:36:52 -07:00
fantom)
chain = 10
explorer = "https://ftmscan.com/address/"
evm = true
; ;
karura)
chain = 11
explorer = "https://blockscout.karura.network/address/"
evm = true
; ;
2022-06-02 09:18:44 -07:00
acala)
2022-06-02 09:25:21 -07:00
chain = 12
2022-06-02 09:18:44 -07:00
explorer = "https://blockscout.acala.network/address/"
evm = true
2022-08-08 16:50:05 -07:00
; ;
2022-05-23 10:36:52 -07:00
klaytn)
chain = 13
explorer = "https://scope.klaytn.com/account/"
evm = true
; ;
celo)
chain = 14
explorer = "https://celoscan.xyz/address/"
evm = true
; ;
2022-09-12 13:23:35 -07:00
near)
chain = 15
explorer = "https://explorer.near.org/accounts/"
; ;
2022-11-15 07:19:02 -08:00
arbitrum)
chain = 23
explorer = "https://arbiscan.io/address/"
evm = true
; ;
optimism)
chain = 24
explorer = "https://optimistic.etherscan.io/address/"
evm = true
; ;
2023-09-29 08:11:43 -07:00
aptos)
chain = 22
explorer = "https://explorer.aptoslabs.com/account/"
; ;
2023-02-23 12:42:30 -08:00
base)
echo "Need to specify the base explorer URL!"
2023-09-29 08:11:43 -07:00
exit 1
2023-02-23 12:42:30 -08:00
chain = 30
explorer = "??/address/"
evm = true
2023-09-29 08:11:43 -07:00
; ;
2022-05-23 10:36:52 -07:00
*)
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
2022-08-08 16:50:05 -07:00
# Generate the command to create the governance prototxt
function create_governance( ) {
case " $module " in
2022-06-30 04:53:43 -07:00
bridge| core)
2022-08-08 16:50:05 -07:00
echo " \
2022-05-23 10:36:52 -07:00
guardiand template contract-upgrade \\
--chain-id $chain \\
--new-address $address "
; ;
token_bridge)
2022-08-08 16:50:05 -07:00
echo " \
2022-05-23 10:36:52 -07:00
guardiand template token-bridge-upgrade-contract \\
--chain-id $chain --module \" TokenBridge\" \\
--new-address $address "
; ;
nft_bridge)
2022-08-08 16:50:05 -07:00
echo " \
2022-05-23 10:36:52 -07:00
guardiand template token-bridge-upgrade-contract \\
--chain-id $chain --module \" NFTBridge\" \\
2023-06-14 07:27:00 -07:00
--new-address $address "
; ;
wormhole_relayer)
echo " \
guardiand template token-bridge-upgrade-contract \\
--chain-id $chain --module \" WormholeRelayer\" \\
2022-05-23 10:36:52 -07:00
--new-address $address "
2022-08-08 16:50:05 -07:00
; ;
*) echo " unknown module $module " >& 2
usage
; ;
esac
}
2022-05-23 10:36:52 -07:00
2022-08-08 16:50:05 -07:00
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"
2022-05-23 10:36:52 -07:00
; ;
*) echo " unknown module $module " >& 2
usage
; ;
2022-08-08 16:50:05 -07:00
esac
}
function solana_artifact( ) {
case " $module " in
bridge| core)
echo "artifacts-mainnet/bridge.so"
; ;
token_bridge)
echo "artifacts-mainnet/token_bridge.so"
; ;
nft_bridge)
echo "artifacts-mainnet/nft_bridge.so"
; ;
*) echo " unknown module $module " >& 2
usage
; ;
esac
}
2022-09-12 13:23:35 -07:00
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
}
2022-09-19 07:08:55 -07:00
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
}
2022-08-08 16:50:05 -07:00
function terra_artifact( ) {
case " $module " in
bridge| core)
echo "artifacts/wormhole.wasm"
; ;
token_bridge)
echo "artifacts/token_bridge_terra.wasm"
; ;
nft_bridge)
echo "artifacts/nft_bridge.wasm"
; ;
*) echo " unknown module $module " >& 2
usage
; ;
esac
}
2022-05-23 10:36:52 -07:00
################################################################################
# Construct the governance proto
echo " # $module upgrade on $chain_name " >> " $gov_msg_file "
# Append the new governance message to the gov file
2022-08-08 16:50:05 -07:00
eval " $( create_governance) " >> " $gov_msg_file "
2022-05-23 10:36:52 -07:00
# 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' )
2022-10-17 16:32:52 -07:00
# 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)
2022-05-23 10:36:52 -07:00
################################################################################
# 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
2022-10-17 16:32:52 -07:00
cat << EOF > governance-$gov_id .prototxt
2022-05-23 10:36:52 -07:00
$( cat " $gov_msg_file " )
EOF
2022-10-17 16:32:52 -07:00
guardiand admin governance-vaa-inject --socket /path/to/admin.sock governance-$gov_id .prototxt
2022-05-23 10:36:52 -07:00
\` \` \`
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)
2022-06-30 04:53:43 -07:00
echo " # Verification steps ( $chain_name $module )
2022-05-23 10:36:52 -07:00
" >> " $instructions_file "
2022-08-08 16:50:05 -07:00
# 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 "
2022-05-23 10:36:52 -07:00
# Verification steps depend on the chain.
if [ " $evm " = true ] ; then
cat <<-EOF >> " $instructions_file "
## Build
\` \` \` shell
2022-06-30 04:53:43 -07:00
wormhole/ethereum $ make
2022-05-23 10:36:52 -07:00
\` \` \`
## Verify
Contract at [ $explorer $address ] ( $explorer $address )
2022-06-30 04:53:43 -07:00
Next, use the \` verify\` script to verify that the deployed bytecodes we are upgrading to match the build artifacts:
2022-05-23 10:36:52 -07:00
\` \` \` shell
2023-05-06 02:02:05 -07:00
wormhole/ethereum $ ./verify -r $( worm info rpc mainnet $chain_name ) -c $chain_name $( evm_artifact) $address
2022-05-23 10:36:52 -07:00
\` \` \`
EOF
2023-02-10 08:15:05 -08:00
elif [ " $chain_name " = "solana" ] || [ " $chain_name " = "pythnet" ] ; then
2022-05-23 10:36:52 -07:00
cat <<-EOF >> " $instructions_file "
## Build
\` \` \` shell
wormhole/solana $ make clean
wormhole/solana $ make NETWORK = mainnet artifacts
\` \` \`
This command will compile all the contracts into the \` artifacts-mainnet\` directory using Docker to ensure that the build artifacts are deterministic.
## Verify
Contract at [ $explorer $address ] ( $explorer $address )
2023-09-29 08:11:43 -07:00
2023-02-10 08:15:05 -08:00
$extra
2022-05-23 10:36:52 -07:00
Next, use the \` verify\` script to verify that the deployed bytecodes we are upgrading to match the build artifacts:
\` \` \` shell
# $module
2022-08-08 16:50:05 -07:00
wormhole/solana$ ./verify -n mainnet $( solana_artifact) $address
2022-05-23 10:36:52 -07:00
\` \` \`
EOF
2022-09-12 13:23:35 -07:00
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
2022-09-19 07:08:55 -07:00
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
2022-05-23 10:36:52 -07:00
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
2022-11-28 13:24:25 -08:00
wormhole/terra$ ./verify -n mainnet -c $chain_name -w $( terra_artifact) -i $terra_code_id
2022-05-23 10:36:52 -07:00
\` \` \`
EOF
2023-09-29 08:11:43 -07:00
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
2022-05-23 10:36:52 -07:00
else
echo " ERROR: no verification instructions for chain $chain_name " >& 2
exit 1
fi
cat <<-EOF >> " $instructions_file "
## Create governance
\` \` \` shell
2022-08-08 16:50:05 -07:00
$( create_governance)
2022-05-23 10:36:52 -07:00
\` \` \`
EOF
# Finally print instructions to stdout
cat " $instructions_file "