#!/bin/bash set -euo pipefail # This script ensures that the EVM contracts can be safely upgraded to without # bricking the contracts. It does this by simulating contract upgrades against # the mainnet state, and checks that the state is consistent after the upgrade. # # By default, the script will compile the contracts and run the upgrade. It's # possible to simulate an upgrade against an already deployed implementation # contract (which is useful for independent verification of a governance # proposal) -- see the usage instructions below. function usage() { cat <&2 Usage: $(basename "$0") [-h] [-m s] [-c s] [-x] [-k] [-d] [-a s] [-l s] [-s] -- Simulate an upgrade on a fork of mainnet, and check for any errors. where: -h show this help text -m module (bridge, token_bridge, nft_bridge) -c chain name -x run anvil -d don't compile contract first -k keep anvil alive -l file to log to (by default creates a new tmp file) -a new code address (by default it builds the most recent contract in the repository) -s shutdown EOF exit 1 } before=$(mktemp) after=$(mktemp) ### Parse command line options address="" module="" chain_name="" run_anvil=false skip_compile=false keepalive_anvil=false shutdown=false anvil_out=$(mktemp) while getopts ':hm:c:a:xkdl:s' option; do case "$option" in h) usage ;; m) module=$OPTARG ;; a) address=$OPTARG ;; c) chain_name=$OPTARG ;; x) run_anvil=true ;; d) skip_compile=true ;; l) anvil_out=$OPTARG ;; k) keepalive_anvil=true run_anvil=true ;; s) shutdown=true ;; :) printf "missing argument for -%s\n" "$OPTARG" >&2 usage ;; \?) printf "illegal option: -%s\n" "$OPTARG" >&2 usage ;; esac done shift $((OPTIND - 1)) # Check that we have the required arguments [ -z "$chain_name" ] && usage [ -z "$module" ] && usage # Get core contract address CORE=$(worm contract mainnet "$chain_name" Core) echo "core: $CORE" # Use the local devnet guardian key (this is not a production key) GUARDIAN_ADDRESS=0xbeFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe GUARDIAN_SECRET=cfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0 ANVIL_PID="" function clean_up () { ARG=$? [ -n "$ANVIL_PID" ] && kill "$ANVIL_PID" exit $ARG } trap clean_up SIGINT SIGTERM EXIT #TODO: make RPC an optional argument HOST="http://0.0.0.0" PORT="8545" RPC="$HOST:$PORT" if [[ $run_anvil = true ]]; then ./anvil_fork "$chain_name" ANVIL_PID=$! echo "🍴 Forking mainnet..." echo "Anvil logs in $anvil_out" sleep 5 # ps | grep "$ANVIL_PID" fi MODULE="" SCRIPT="" case "$module" in bridge|core) MODULE=Core if [[ $shutdown = true ]]; then SCRIPT="scripts/deploy_core_bridge_shutdown.js" else SCRIPT="scripts/deploy_core_bridge.js" fi ;; token_bridge) MODULE=TokenBridge if [[ $shutdown = true ]]; then SCRIPT="scripts/deploy_token_bridge_shutdown.js" else SCRIPT="scripts/deploy_token_bridge.js" fi ;; nft_bridge) MODULE=NFTBridge if [[ $shutdown = true ]]; then SCRIPT="scripts/deploy_nft_bridge_shutdown.js" else SCRIPT="scripts/deploy_nft_bridge.js" fi ;; *) echo "unknown module $module" >&2 usage ;; esac CONTRACT=$(worm contract mainnet "$chain_name" "$MODULE") # Step 1) Figure out the contract address depending on the flags -- either use # an address passed in as an argument, or use the most recent contract in the repo. if [[ -n "$address" ]]; then new_implementation="$address" else if [[ $skip_compile = false ]]; then echo "🛠 Compiling contract..." build_output=$(npm run build) || ( echo "$build_output" && exit 1 ) fi printf "⬆️ Deploying implementation..." deploy_output=$(npx truffle exec $SCRIPT --network development) || ( echo "$deploy_output" && exit 1 ) new_implementation=$(echo "$deploy_output" | grep "address:" | cut -d' ' -f3) fi printf " %s\n" "$new_implementation" # Step 2) generate upgrade VAA using the local guardian key vaa=$(worm generate upgrade -c "$chain_name" -a "$new_implementation" -m $MODULE -g "$GUARDIAN_SECRET") # Step 3) the VAA we just signed in Step 2) is not compatible with the guardian # set on mainnet (since that corresponds to a mainnet guardian network). We need # to thus locally replace the guardian set with the local guardian key. echo "💂 Overriding guardian set with $GUARDIAN_ADDRESS" worm evm hijack -g "$GUARDIAN_ADDRESS" -i 0 -a "$CORE" --rpc "$RPC"> /dev/null # Step 4) query state before upgrade echo "🔍 Querying old contract state" worm evm info -c "$chain_name" -m $MODULE -n devnet -a "$CONTRACT" --rpc "$RPC" | grep -v '"implementation":' > "$before" # Step 5) upgrade contract echo "🤝 Submitting VAA" worm submit "$vaa" -n devnet -a "$CONTRACT" --rpc "$RPC" > /dev/null # Step 6) query state after upgrade echo "🔍 Querying new contract state" worm evm info -c "$chain_name" -m $MODULE -n devnet -a "$CONTRACT" --rpc "$RPC" | grep -v '"implementation":' > "$after" # Step 7) compare old and new state and exit with error if they differ git diff --no-index "$before" "$after" --exit-code && echo "✅ Upgrade simulation successful" || exit 1 # Anvil can be kept alive by setting the -k flag. This is useful for interacting # with the contract after it has been upgraded. if [[ $keepalive_anvil = true ]]; then echo "Listening on $RPC" # tail -f "$anvil_out" wait "$ANVIL_PID" fi