simulate_upgrade 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. #!/bin/bash
  2. set -euo pipefail
  3. # This script ensures that the EVM contracts can be safely upgraded to without
  4. # bricking the contracts. It does this by simulating contract upgrades against
  5. # the mainnet state, and checks that the state is consistent after the upgrade.
  6. #
  7. # By default, the script will compile the contracts and run the upgrade. It's
  8. # possible to simulate an upgrade against an already deployed implementation
  9. # contract (which is useful for independent verification of a governance
  10. # proposal) -- see the usage instructions below.
  11. function usage() {
  12. cat <<EOF >&2
  13. Usage:
  14. $(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.
  15. where:
  16. -h show this help text
  17. -m module (bridge, token_bridge, nft_bridge)
  18. -c chain name
  19. -x run anvil
  20. -d don't compile contract first
  21. -k keep anvil alive
  22. -l file to log to (by default creates a new tmp file)
  23. -a new code address (by default it builds the most recent contract in the repository)
  24. -s shutdown
  25. EOF
  26. exit 1
  27. }
  28. before=$(mktemp)
  29. after=$(mktemp)
  30. ### Parse command line options
  31. address=""
  32. module=""
  33. chain_name=""
  34. run_anvil=false
  35. skip_compile=false
  36. keepalive_anvil=false
  37. shutdown=false
  38. anvil_out=$(mktemp)
  39. while getopts ':hm:c:a:xkdl:s' option; do
  40. case "$option" in
  41. h) usage
  42. ;;
  43. m) module=$OPTARG
  44. ;;
  45. a) address=$OPTARG
  46. ;;
  47. c) chain_name=$OPTARG
  48. ;;
  49. x) run_anvil=true
  50. ;;
  51. d) skip_compile=true
  52. ;;
  53. l) anvil_out=$OPTARG
  54. ;;
  55. k) keepalive_anvil=true
  56. run_anvil=true
  57. ;;
  58. s) shutdown=true
  59. ;;
  60. :) printf "missing argument for -%s\n" "$OPTARG" >&2
  61. usage
  62. ;;
  63. \?) printf "illegal option: -%s\n" "$OPTARG" >&2
  64. usage
  65. ;;
  66. esac
  67. done
  68. shift $((OPTIND - 1))
  69. # Check that we have the required arguments
  70. [ -z "$chain_name" ] && usage
  71. [ -z "$module" ] && usage
  72. # Get core contract address
  73. CORE=$(worm info contract mainnet "$chain_name" Core)
  74. echo "core: $CORE"
  75. # Use the local devnet guardian key (this is not a production key)
  76. GUARDIAN_ADDRESS=0xbeFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe
  77. GUARDIAN_SECRET=cfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0
  78. ANVIL_PID=""
  79. function clean_up () {
  80. ARG=$?
  81. [ -n "$ANVIL_PID" ] && kill "$ANVIL_PID"
  82. exit $ARG
  83. }
  84. trap clean_up SIGINT SIGTERM EXIT
  85. #TODO: make RPC an optional argument
  86. HOST="http://0.0.0.0"
  87. PORT="8545"
  88. RPC="$HOST:$PORT"
  89. if [[ $run_anvil = true ]]; then
  90. ./anvil_fork "$chain_name"
  91. ANVIL_PID=$!
  92. echo "🍴 Forking mainnet..."
  93. echo "Anvil logs in $anvil_out"
  94. sleep 5
  95. # ps | grep "$ANVIL_PID"
  96. fi
  97. MODULE=""
  98. SCRIPT=""
  99. case "$module" in
  100. bridge|core)
  101. MODULE=Core
  102. if [[ $shutdown = true ]]; then
  103. SCRIPT="DeployCoreShutdown.s.sol:DeployCoreShutdown"
  104. SOLFILE="DeployCoreShutdown.s.sol"
  105. else
  106. SCRIPT="DeployCoreImplementationOnly.s.sol:DeployCoreImplementationOnly"
  107. SOLFILE="DeployCoreImplementationOnly.s.sol"
  108. fi
  109. ;;
  110. token_bridge)
  111. MODULE=TokenBridge
  112. if [[ $shutdown = true ]]; then
  113. SCRIPT="DeployTokenBridgeShutdown.s.sol:DeployTokenBridgeShutdown"
  114. SOLFILE="DeployTokenBridgeShutdown.s.sol"
  115. else
  116. SCRIPT="DeployTokenBridgeImplementationOnly.s.sol:DeployTokenBridgeImplementationOnly"
  117. SOLFILE="DeployTokenBridgeImplementationOnly.s.sol"
  118. fi
  119. ;;
  120. nft_bridge)
  121. MODULE=NFTBridge
  122. if [[ $shutdown = true ]]; then
  123. SCRIPT="DeployNFTBridgeShutdown.s.sol:DeployNFTBridgeShutdown"
  124. SOLFILE="DeployNFTBridgeShutdown.s.sol"
  125. else
  126. SCRIPT="DeployNFTBridgeImplementationOnly.s.sol:DeployNFTBridgeImplementationOnly"
  127. SOLFILE="DeployNFTBridgeImplementationOnly.s.sol"
  128. fi
  129. ;;
  130. *) echo "unknown module $module" >&2
  131. usage
  132. ;;
  133. esac
  134. CONTRACT=$(worm info contract mainnet "$chain_name" "$MODULE")
  135. EVM_CHAIN_ID=$(printf "%d" $(curl http://localhost:8545/ -X POST -H "Content-Type: application/json" --data '{"method":"eth_chainId","params":[],"id":1,"jsonrpc":"2.0"}' -s | jq -r .result))
  136. # Step 1) Figure out the contract address depending on the flags -- either use
  137. # an address passed in as an argument, or use the most recent contract in the repo.
  138. if [[ -n "$address" ]]; then
  139. new_implementation="$address"
  140. else
  141. if [[ $skip_compile = false ]]; then
  142. echo "🛠 Compiling contract..."
  143. build_output=$(npm run build) || ( echo "$build_output" && exit 1 )
  144. fi
  145. printf "⬆️ Deploying implementation..."
  146. forge script ./forge-scripts/${SCRIPT} \
  147. --rpc-url "$RPC" \
  148. --private-key "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d" \
  149. --broadcast \
  150. --silent
  151. returnInfo=$(cat ./broadcast/${SOLFILE}/$EVM_CHAIN_ID/run-latest.json)
  152. # Extract the address values from 'returnInfo'
  153. new_implementation=$(jq -r '.returns.deployedAddress.value' <<< "$returnInfo")
  154. fi
  155. printf " %s\n" "$new_implementation"
  156. # Step 2) generate upgrade VAA using the local guardian key
  157. vaa=$(worm generate upgrade -c "$chain_name" -a "$new_implementation" -m $MODULE -g "$GUARDIAN_SECRET")
  158. # Step 3) the VAA we just signed in Step 2) is not compatible with the guardian
  159. # set on mainnet (since that corresponds to a mainnet guardian network). We need
  160. # to thus locally replace the guardian set with the local guardian key.
  161. echo "💂 Overriding guardian set with $GUARDIAN_ADDRESS"
  162. worm evm hijack -g "$GUARDIAN_ADDRESS" -i 0 -a "$CORE" --rpc "$RPC"> /dev/null
  163. # Step 4) query state before upgrade
  164. echo "🔍 Querying old contract state"
  165. worm evm info -c "$chain_name" -m $MODULE -n devnet -a "$CONTRACT" --rpc "$RPC" | grep -v '"implementation":' > "$before"
  166. # Step 5) upgrade contract
  167. echo "🤝 Submitting VAA"
  168. worm submit "$vaa" -n devnet -a "$CONTRACT" --rpc "$RPC" > /dev/null
  169. # Step 6) query state after upgrade
  170. echo "🔍 Querying new contract state"
  171. worm evm info -c "$chain_name" -m $MODULE -n devnet -a "$CONTRACT" --rpc "$RPC" | grep -v '"implementation":' > "$after"
  172. # Step 7) compare old and new state and exit with error if they differ
  173. git diff --no-index "$before" "$after" --exit-code && echo "✅ Upgrade simulation successful" || exit 1
  174. # Anvil can be kept alive by setting the -k flag. This is useful for interacting
  175. # with the contract after it has been upgraded.
  176. if [[ $keepalive_anvil = true ]]; then
  177. echo "Listening on $RPC"
  178. # tail -f "$anvil_out"
  179. wait "$ANVIL_PID"
  180. fi