contract-upgrade-governance.sh 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. #!/bin/bash
  2. # This tool automates the process of writing contract upgrade governance
  3. # proposals in markdown format.
  4. #
  5. # There are two ways to run this script: either in "one-shot" mode, where a
  6. # single governance VAA is generated:
  7. #
  8. # ./contract-upgrade-governance.sh -m token_bridge -c solana -a Hp1YjsMbapQ75qpLaHQHuAv5Q8QwPoXs63zQrrcgg2HL > governance.md
  9. #
  10. # or in "multi" mode, where multiple VAAs are created in the same proposal:
  11. #
  12. # ./contract-upgrade-governance.sh -m token_bridge -c solana -a Hp1YjsMbapQ75qpLaHQHuAv5Q8QwPoXs63zQrrcgg2HL -o my_proposal > governance.md
  13. # ./contract-upgrade-governance.sh -m token_bridge -c avalanche -a 0x45fC4b6DD26097F0E51B1C91bcc331E469Ca73c2 -o my_proposal > governance.md
  14. # ... -o my_proposal > governance.md
  15. #
  16. # In multi mode, there's an additional "-o" flag, which takes a directory name,
  17. # where intermediate progress is saved between runs. If the directory doesn't
  18. # exist, the tool will create it.
  19. #
  20. # In both one-shot and multi modes, the script outputs the markdown-formatted
  21. # proposal to STDOUT, so it's a good idea to pipe it into a file (as in the above examples).
  22. #
  23. # In multi-mode, it always outputs the most recent version, so it's safe to
  24. # override the previous files.
  25. #
  26. # Once a multi-mode run is completed, the directory specified with the -o flag can be deleted.
  27. set -euo pipefail
  28. usage() {
  29. cat <<-EOF >&2
  30. Usage: $(basename "$0") [OPTIONS]
  31. Generate governance proposal for a module to be upgraded to a given address.
  32. Options:
  33. -h, --help Show this help message
  34. -m, --module <module> Specify the module (bridge, token_bridge, nft_bridge)
  35. -c, --chain <chain_name> Specify the chain name
  36. -a, --address <address> Specify the new code address (e.g., 0x3f1a6729bb27350748f0a0bd85ca641a100bf0a1)
  37. -o, --output <output_dir> Specify the multi-mode output directory
  38. -f, --force Force: bypass dirty git repo check
  39. EOF
  40. exit 1
  41. }
  42. # Check if guardiand command exists. It's needed for generating the protoxt and
  43. # computing the digest.
  44. if ! command -v guardiand >/dev/null 2>&1; then
  45. echo "ERROR: guardiand binary not found" >&2
  46. exit 1
  47. fi
  48. # Check if the worm command exists. It's needed for computing the digest.
  49. if ! command -v worm >/dev/null 2>&1; then
  50. echo "ERROR: worm binary not found" >&2
  51. exit 1
  52. fi
  53. ### Parse command line options
  54. address=""
  55. module=""
  56. chain_name=""
  57. multi_mode=false
  58. out_dir=""
  59. allow_dirty=false
  60. while (( "$#" )); do
  61. case "$1" in
  62. -h|--help)
  63. usage
  64. ;;
  65. -m|--module)
  66. module="$2"
  67. shift 2
  68. ;;
  69. -c|--chain)
  70. chain_name="$2"
  71. shift 2
  72. ;;
  73. -a|--address)
  74. address="$2"
  75. shift 2
  76. ;;
  77. -o|--output)
  78. multi_mode=true
  79. out_dir="$2"
  80. shift 2
  81. ;;
  82. -f|--force)
  83. allow_dirty=true
  84. shift
  85. ;;
  86. --) # end of options
  87. shift
  88. break
  89. ;;
  90. -*)
  91. echo "Error: Unsupported option $1" >&2
  92. usage
  93. ;;
  94. *) # anything else
  95. echo "Error: Unsupported argument $1" >&2
  96. usage
  97. ;;
  98. esac
  99. done
  100. [ -z "$address" ] && usage
  101. [ -z "$chain_name" ] && usage
  102. [ -z "$module" ] && usage
  103. # Check if the git tree is dirty
  104. if [ "$allow_dirty" = false ]; then
  105. if ! git diff-index --quiet HEAD --; then
  106. echo "ERROR: git tree is dirty. Commit or stash your changes first." >&2
  107. echo "If you are sure you want to proceed, use the --force flag." >&2
  108. exit 1
  109. fi
  110. fi
  111. ### The script constructs the governance proposal in two different steps. First,
  112. ### the governance prototxt (for VAA injection by the guardiand tool), then the voting/verification instructions.
  113. gov_msg_file=""
  114. instructions_file=""
  115. if [ "$multi_mode" = true ]; then
  116. mkdir -p "$out_dir"
  117. gov_msg_file="$out_dir/governance.prototxt"
  118. instructions_file="$out_dir/instructions.md"
  119. else
  120. gov_msg_file=$(mktemp)
  121. instructions_file=$(mktemp)
  122. fi
  123. explorer=""
  124. evm=false
  125. # TODO: move to CLI
  126. case "$chain_name" in
  127. solana)
  128. chain=1
  129. explorer="https://explorer.solana.com/address/"
  130. extra=""
  131. ;;
  132. fogo)
  133. chain=51
  134. explorer="https://explorer.fogo.io/address/"
  135. extra=""
  136. ;;
  137. pythnet)
  138. chain=26
  139. explorer="https://explorer.solana.com/address/"
  140. extra="Be sure to choose \"Custom RPC\" as the cluster in the explorer and set it to https://pythnet.rpcpool.com"
  141. ;;
  142. ethereum)
  143. chain=2
  144. explorer="https://etherscan.io/address/"
  145. evm=true
  146. ;;
  147. terra)
  148. chain=3
  149. # This is not technically the explorer, but terra finder does not show
  150. # information about code ids, so this is the best we can do.
  151. explorer="https://terra-classic-lcd.publicnode.com/cosmwasm/wasm/v1/code/"
  152. ;;
  153. bsc)
  154. chain=4
  155. explorer="https://bscscan.com/address/"
  156. evm=true
  157. ;;
  158. polygon)
  159. chain=5
  160. explorer="https://polygonscan.com/address/"
  161. evm=true
  162. ;;
  163. avalanche)
  164. chain=6
  165. explorer="https://snowtrace.io/address/"
  166. evm=true
  167. ;;
  168. oasis)
  169. chain=7
  170. explorer="https://explorer.emerald.oasis.dev/address/"
  171. evm=true
  172. ;;
  173. aurora)
  174. chain=9
  175. explorer="https://aurorascan.dev/address/"
  176. evm=true
  177. ;;
  178. algorand)
  179. chain=8
  180. explorer="https://algoexplorer.io/address/"
  181. ;;
  182. fantom)
  183. chain=10
  184. explorer="https://ftmscan.com/address/"
  185. evm=true
  186. ;;
  187. karura)
  188. chain=11
  189. explorer="https://blockscout.karura.network/address/"
  190. evm=true
  191. ;;
  192. acala)
  193. chain=12
  194. explorer="https://blockscout.acala.network/address/"
  195. evm=true
  196. ;;
  197. klaytn)
  198. chain=13
  199. explorer="https://scope.klaytn.com/account/"
  200. evm=true
  201. ;;
  202. celo)
  203. chain=14
  204. explorer="https://celoscan.xyz/address/"
  205. evm=true
  206. ;;
  207. near)
  208. chain=15
  209. explorer="https://explorer.near.org/accounts/"
  210. ;;
  211. arbitrum)
  212. chain=23
  213. explorer="https://arbiscan.io/address/"
  214. evm=true
  215. ;;
  216. optimism)
  217. chain=24
  218. explorer="https://optimistic.etherscan.io/address/"
  219. evm=true
  220. ;;
  221. aptos)
  222. chain=22
  223. explorer="https://explorer.aptoslabs.com/account/"
  224. ;;
  225. base)
  226. echo "Need to specify the base explorer URL!"
  227. exit 1
  228. chain=30
  229. explorer="??/address/"
  230. evm=true
  231. ;;
  232. *)
  233. echo "Unknown chain: $chain_name" >&2
  234. exit 1
  235. ;;
  236. esac
  237. # On terra, the contract given is a decimal code id. We convert it to a 32 byte
  238. # hex first. The printf is escaped, which makes no difference when we actually
  239. # evaluate the governance command later, but shows up unevaluated in the
  240. # instructions (so it's easier to read)
  241. terra_code_id=""
  242. if [ "$chain_name" = "terra" ]; then
  243. terra_code_id="$address" # save code id for later
  244. address="\$(printf \"%064x\" $terra_code_id)"
  245. fi
  246. # Generate the command to create the governance prototxt
  247. function create_governance() {
  248. case "$module" in
  249. bridge|core)
  250. echo "\
  251. guardiand template contract-upgrade \\
  252. --chain-id $chain \\
  253. --new-address $address"
  254. ;;
  255. token_bridge)
  256. echo "\
  257. guardiand template token-bridge-upgrade-contract \\
  258. --chain-id $chain --module \"TokenBridge\" \\
  259. --new-address $address"
  260. ;;
  261. nft_bridge)
  262. echo "\
  263. guardiand template token-bridge-upgrade-contract \\
  264. --chain-id $chain --module \"NFTBridge\" \\
  265. --new-address $address"
  266. ;;
  267. wormhole_relayer)
  268. echo "\
  269. guardiand template token-bridge-upgrade-contract \\
  270. --chain-id $chain --module \"WormholeRelayer\" \\
  271. --new-address $address"
  272. ;;
  273. *) echo "unknown module $module" >&2
  274. usage
  275. ;;
  276. esac
  277. }
  278. function evm_artifact() {
  279. case "$module" in
  280. bridge|core)
  281. echo "build/contracts/Implementation.json"
  282. ;;
  283. token_bridge)
  284. echo "build/contracts/BridgeImplementation.json"
  285. ;;
  286. nft_bridge)
  287. echo "build/contracts/NFTBridgeImplementation.json"
  288. ;;
  289. *) echo "unknown module $module" >&2
  290. usage
  291. ;;
  292. esac
  293. }
  294. function solana_artifact() {
  295. case "$module" in
  296. bridge|core)
  297. echo "artifacts-$chain_name-mainnet/bridge.so"
  298. ;;
  299. token_bridge)
  300. echo "artifacts-$chain_name-mainnet/token_bridge.so"
  301. ;;
  302. nft_bridge)
  303. echo "artifacts-$chain_name-mainnet/nft_bridge.so"
  304. ;;
  305. *) echo "unknown module $module" >&2
  306. usage
  307. ;;
  308. esac
  309. }
  310. function near_artifact() {
  311. case "$module" in
  312. bridge|core)
  313. echo "artifacts/near_wormhole.wasm"
  314. ;;
  315. token_bridge)
  316. echo "artifacts/near_token_bridge.wasm"
  317. ;;
  318. *) echo "unknown module $module" >&2
  319. usage
  320. ;;
  321. esac
  322. }
  323. function algorand_artifact() {
  324. case "$module" in
  325. bridge|core)
  326. echo "artifacts/core_approve.teal.hash"
  327. ;;
  328. token_bridge)
  329. echo "artifacts/token_approve.teal.hash"
  330. ;;
  331. *) echo "unknown module $module" >&2
  332. usage
  333. ;;
  334. esac
  335. }
  336. function terra_artifact() {
  337. case "$module" in
  338. bridge|core)
  339. echo "artifacts/wormhole.wasm"
  340. ;;
  341. token_bridge)
  342. echo "artifacts/token_bridge_terra.wasm"
  343. ;;
  344. *) echo "unknown module $module" >&2
  345. usage
  346. ;;
  347. esac
  348. }
  349. ################################################################################
  350. # Construct the governance proto
  351. echo "# $module upgrade on $chain_name" >> "$gov_msg_file"
  352. # Append the new governance message to the gov file
  353. eval "$(create_governance)" >> "$gov_msg_file"
  354. # Multiple messages will include multiple 'current_set_index' fields, but the
  355. # proto format only takes one. This next part cleans up the file so there's only
  356. # a single 'current_set_index' field.
  357. # 1. we grab the first one and save it
  358. current_set_index=$(grep "current_set_index" "$gov_msg_file" | head -n 1)
  359. # 2. remove all 'current_set_index' fields
  360. rest=$(grep -v "current_set_index" "$gov_msg_file")
  361. # 3. write the set index
  362. echo "$current_set_index" > "$gov_msg_file"
  363. # 4. then the rest of the file
  364. echo "$rest" >> "$gov_msg_file"
  365. ################################################################################
  366. # Compute expected digests
  367. # just use the 'guardiand' command, which spits out a bunch of text to
  368. # stderr. We grab that output and pick out the VAA hashes
  369. verify=$(guardiand admin governance-vaa-verify "$gov_msg_file" 2>&1)
  370. digest=$(echo "$verify" | grep "VAA with digest" | cut -d' ' -f6 | sed 's/://g')
  371. # massage the digest into the same format that the inject command prints it
  372. digest=$(echo "$digest" | awk '{print toupper($0)}' | sed 's/^0X//')
  373. # we use the first 7 characters of the digest as an identifier for the prototxt file
  374. gov_id=$(echo "$digest" | cut -c1-7)
  375. ################################################################################
  376. # Print vote command and expected digests
  377. # This we only print to stdout, because in multi mode, it gets recomputed each
  378. # time. The rest of the output gets printed into the instructions file
  379. cat <<-EOD
  380. # Governance
  381. Shell command for voting:
  382. \`\`\`shell
  383. cat << EOF > governance-$gov_id.prototxt
  384. $(cat "$gov_msg_file")
  385. EOF
  386. guardiand admin governance-vaa-inject --socket /path/to/admin.sock governance-$gov_id.prototxt
  387. \`\`\`
  388. Expected digest(s):
  389. \`\`\`
  390. $digest
  391. \`\`\`
  392. EOD
  393. ################################################################################
  394. # Verification instructions
  395. # The rest of the output is printed to the instructions file (which then also
  396. # gets printed to stdout at the end)
  397. echo "# Verification steps ($chain_name $module)
  398. " >> "$instructions_file"
  399. # Print instructions on checking out the current git hash:
  400. git_hash=$(git rev-parse HEAD)
  401. echo "
  402. ## Checkout the current git hash
  403. \`\`\`shell
  404. git fetch
  405. git checkout $git_hash
  406. \`\`\`" >> "$instructions_file"
  407. # Verification steps depend on the chain.
  408. if [ "$evm" = true ]; then
  409. cat <<-EOF >> "$instructions_file"
  410. ## Build
  411. \`\`\`shell
  412. wormhole/ethereum $ make
  413. \`\`\`
  414. ## Verify
  415. Contract at [$explorer$address]($explorer$address)
  416. Next, use the \`verify\` script to verify that the deployed bytecodes we are upgrading to match the build artifacts:
  417. \`\`\`shell
  418. wormhole/ethereum $ ./verify -r $(worm info rpc mainnet $chain_name) -c $chain_name $(evm_artifact) $address
  419. \`\`\`
  420. EOF
  421. elif [ "$chain_name" = "solana" ] || [ "$chain_name" = "fogo" ] || [ "$chain_name" = "pythnet" ]; then
  422. cat <<-EOF >> "$instructions_file"
  423. ## Build
  424. \`\`\`shell
  425. wormhole/solana $ make SVM=$chain_name clean
  426. wormhole/solana $ make NETWORK=mainnet SVM=$chain_name artifacts
  427. \`\`\`
  428. This command will compile all the contracts into the \`artifacts-$chain_name-mainnet\` directory using Docker to ensure that the build artifacts are deterministic.
  429. ## Verify
  430. Contract at [$explorer$address]($explorer$address)
  431. $extra
  432. Next, use the \`verify\` script to verify that the deployed bytecodes we are upgrading to match the build artifacts:
  433. \`\`\`shell
  434. # $module
  435. wormhole/solana$ ./verify -n mainnet -s $chain_name $(solana_artifact) $address
  436. \`\`\`
  437. EOF
  438. elif [ "$chain_name" = "near" ]; then
  439. cat <<-EOF >> "$instructions_file"
  440. ## Build
  441. \`\`\`shell
  442. wormhole/near $ make artifacts
  443. \`\`\`
  444. This command will compile all the contracts into the \`artifacts\` directory using Docker to ensure that the build artifacts are deterministic.
  445. Next, you can look at the checksums of the built .wasm files
  446. \`\`\`shell
  447. # $module
  448. wormhole/near$ sha256sum $(near_artifact)
  449. \`\`\`
  450. EOF
  451. elif [ "$chain_name" = "algorand" ]; then
  452. cat <<-EOF >> "$instructions_file"
  453. ## Build
  454. \`\`\`shell
  455. wormhole/algorand $ make artifacts
  456. \`\`\`
  457. This command will compile all the contracts into the \`artifacts\` directory using Docker to ensure that the build artifacts are deterministic.
  458. You can then review $(algorand_artifact) to confirm the supplied hash value
  459. EOF
  460. elif [ "$chain_name" = "terra" ]; then
  461. cat <<-EOF >> "$instructions_file"
  462. ## Build
  463. \`\`\`shell
  464. wormhole/terra $ make clean
  465. wormhole/terra $ make artifacts
  466. \`\`\`
  467. This command will compile all the contracts into the \`artifacts\` directory using Docker to ensure that the build artifacts are deterministic.
  468. ## Verify
  469. Contract at [$explorer$terra_code_id]($explorer$terra_code_id)
  470. Next, use the \`verify\` script to verify that the deployed bytecodes we are upgrading to match the build artifacts:
  471. \`\`\`shell
  472. # $module
  473. wormhole/terra$ ./verify -n mainnet $(terra_artifact) $terra_code_id
  474. \`\`\`
  475. EOF
  476. elif [ "$chain_name" = "aptos" ]; then
  477. cat <<-EOF >> "$instructions_file"
  478. ## Build
  479. \`\`\`shell
  480. wormhole/aptos $ docker build -f Dockerfile --target aptos -t aptos-build .
  481. \`\`\`
  482. This command will build a docker image that can compile the contracts reproducibly.
  483. ## Verify
  484. Next, run the following command to check that the contract hash matches the expected value ($address):
  485. \`\`\`shell
  486. # $module
  487. wormhole/aptos$ docker run -it aptos-build
  488. wormhole/aptos$ worm aptos hash-contracts /tmp/$module --named-addresses wormhole=0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625,deployer=0x0108bc32f7de18a5f6e1e7d6ee7aff9f5fc858d0d87ac0da94dd8d2a5d267d6b,token_bridge=0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f,nft_bridge=0x1bdffae984043833ed7fe223f7af7a3f8902d04129b14f801823e64827da7130
  489. wormhole/aptos$ exit
  490. \`\`\`
  491. EOF
  492. else
  493. echo "ERROR: no verification instructions for chain $chain_name" >&2
  494. exit 1
  495. fi
  496. cat <<-EOF >> "$instructions_file"
  497. ## Create governance
  498. \`\`\`shell
  499. $(create_governance)
  500. \`\`\`
  501. EOF
  502. # Finally print instructions to stdout
  503. cat "$instructions_file"