contract-upgrade-governance.sh 13 KB

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