From b357ad706141b6efd70bcac0f4ba84426c8cc44f Mon Sep 17 00:00:00 2001 From: bruce-riley <96066700+bruce-riley@users.noreply.github.com> Date: Tue, 6 Sep 2022 08:20:51 -0500 Subject: [PATCH] Script to generate change registration governance (#1508) * Script to generate change registration governance Change-Id: If99e78b031dcd4049a9ee40b09914eeb93e7518a * Rework from code review Change-Id: I93cf3166c1ab961add2c524084de1981a44b194e * Change chain_id to chain-id Change-Id: Iae6f9854a64d8559d03c56949d03043a94dbf7f6 --- clients/js/main.ts | 55 ++++++++- node/cmd/guardiand/adminverify.go | 5 +- scripts/register-all-chains.sh | 35 ++++++ scripts/register-chain-governance.sh | 178 +++++++++++++++++++++++++++ 4 files changed, 267 insertions(+), 6 deletions(-) create mode 100755 scripts/register-all-chains.sh create mode 100755 scripts/register-chain-governance.sh diff --git a/clients/js/main.ts b/clients/js/main.ts index 515438cb3..be69284bf 100644 --- a/clients/js/main.ts +++ b/clients/js/main.ts @@ -10,6 +10,9 @@ import { CONTRACTS, setDefaultWasm, hexToUint8Array, + getEmitterAddressSolana, + getEmitterAddressTerra, + getEmitterAddressEth, } from "@certusone/wormhole-sdk"; import { execute_solana } from "./solana"; import { @@ -254,11 +257,17 @@ yargs(hideBin(process.argv)) describe: "Module to query", type: "string", choices: ["Core", "NFTBridge", "TokenBridge"], + }) + .option("emitter", { + alias: "e", + describe: "Print in emitter address format", + type: "boolean", + default: false, + required: false, }); }, async (argv) => { assertChain(argv["chain"]); - assertEVMChain(argv["chain"]); const network = argv.network.toUpperCase(); if ( network !== "MAINNET" && @@ -267,20 +276,58 @@ yargs(hideBin(process.argv)) ) { throw Error(`Unknown network: ${network}`); } + let chain = argv["chain"] let module = argv["module"] as "Core" | "NFTBridge" | "TokenBridge"; + let addr = "" switch (module) { case "Core": - console.log(CONTRACTS[network][argv["chain"]]["core"]); + addr = CONTRACTS[network][chain]["core"]; break; case "NFTBridge": - console.log(CONTRACTS[network][argv["chain"]]["nft_bridge"]); + addr = CONTRACTS[network][chain]["nft_bridge"]; break; case "TokenBridge": - console.log(CONTRACTS[network][argv["chain"]]["token_bridge"]); + addr = CONTRACTS[network][chain]["token_bridge"]; break; default: impossible(module); } + if (argv["emitter"]) { + if (chain === "solana" || chain === "pythnet") { // TODO: Create an isSolanaChain() + addr = await getEmitterAddressSolana(addr); + } else if (isTerraChain(chain)) { + addr = await getEmitterAddressTerra(addr); + } else if (chain === "algorand") { + if (network !== "MAINNET") { + throw Error(`unable to look up algorand emitter address for ${network}`); + } + addr = "25e716e0618d9f38b603a97cc42db659069c0f5185230e5e61679fa876191ec4"; + } else if (chain === "near") { + if (network !== "MAINNET") { + throw Error(`unable to look up near emitter address for ${network}`); + } + addr = "148410499d3fcda4dcfd68a1ebfcdddda16ab28326448d4aae4d2f0465cdfcb7"; + } else { + addr = getEmitterAddressEth(addr); + } + } + console.log(addr); + } + ) + .command( + "chain-id ", + "Print the wormhole chain ID integer associated with the specified chain name", + (yargs) => { + return yargs + .positional("chain", { + describe: "Chain to query", + type: "string", + choices: Object.keys(CHAINS), + }); + }, + async (argv) => { + assertChain(argv["chain"]); + console.log(toChainId(argv["chain"])); } ) .command( diff --git a/node/cmd/guardiand/adminverify.go b/node/cmd/guardiand/adminverify.go index 8850d2323..a7f5321ff 100644 --- a/node/cmd/guardiand/adminverify.go +++ b/node/cmd/guardiand/adminverify.go @@ -14,6 +14,7 @@ import ( "google.golang.org/protobuf/encoding/prototext" nodev1 "github.com/certusone/wormhole/node/pkg/proto/node/v1" + "github.com/status-im/keycard-go/hexutils" ) var AdminClientGovernanceVAAVerifyCmd = &cobra.Command{ @@ -59,7 +60,7 @@ func runGovernanceVAAVerify(cmd *cobra.Command, args []string) { log.Fatalf("invalid update: %v", err) } - digest := v.SigningMsg() + digest := v.SigningMsg().Bytes() if err != nil { panic(err) } @@ -71,6 +72,6 @@ func runGovernanceVAAVerify(cmd *cobra.Command, args []string) { log.Printf("Serialized: %v", hex.EncodeToString(b)) - log.Printf("VAA with digest %s: %+v", digest.Hex(), spew.Sdump(v)) + log.Printf("VAA with digest %s: %+v", hexutils.BytesToHex(digest), spew.Sdump(v)) } } diff --git a/scripts/register-all-chains.sh b/scripts/register-all-chains.sh new file mode 100755 index 000000000..bc49e8d63 --- /dev/null +++ b/scripts/register-all-chains.sh @@ -0,0 +1,35 @@ +rm -rf my_proposal + +./register-chain-governance.sh -m TokenBridge -c solana -o my_proposal > governance.md +./register-chain-governance.sh -m TokenBridge -c ethereum -o my_proposal > governance.md +./register-chain-governance.sh -m TokenBridge -c terra -o my_proposal > governance.md +./register-chain-governance.sh -m TokenBridge -c bsc -o my_proposal > governance.md +./register-chain-governance.sh -m TokenBridge -c polygon -o my_proposal > governance.md +./register-chain-governance.sh -m TokenBridge -c avalanche -o my_proposal > governance.md +./register-chain-governance.sh -m TokenBridge -c oasis -o my_proposal > governance.md +./register-chain-governance.sh -m TokenBridge -c fantom -o my_proposal > governance.md +./register-chain-governance.sh -m TokenBridge -c aurora -o my_proposal > governance.md + +# These are already on the current guardian set. +# ./register-chain-governance.sh -m TokenBridge -c karura -o my_proposal > governance.md +# ./register-chain-governance.sh -m TokenBridge -c klaytn -o my_proposal > governance.md +# ./register-chain-governance.sh -m TokenBridge -c celo -o my_proposal > governance.md +# ./register-chain-governance.sh -m TokenBridge -c acala -o my_proposal > governance.md +# ./register-chain-governance.sh -m TokenBridge -c terra2 -o my_proposal > governance.md +# ./register-chain-governance.sh -m TokenBridge -c algorand -o my_proposal > governance.md +# ./register-chain-governance.sh -m TokenBridge -c near -o my_proposal > governance.md + +./register-chain-governance.sh -m NFTBridge -c solana -o my_proposal > governance.md +./register-chain-governance.sh -m NFTBridge -c ethereum -o my_proposal > governance.md +./register-chain-governance.sh -m NFTBridge -c bsc -o my_proposal > governance.md +./register-chain-governance.sh -m NFTBridge -c polygon -o my_proposal > governance.md +./register-chain-governance.sh -m NFTBridge -c avalanche -o my_proposal > governance.md +./register-chain-governance.sh -m NFTBridge -c oasis -o my_proposal > governance.md +./register-chain-governance.sh -m NFTBridge -c fantom -o my_proposal > governance.md +./register-chain-governance.sh -m NFTBridge -c aurora -o my_proposal > governance.md + +# These are already on the current guardian set. +# ./register-chain-governance.sh -m NFTBridge -c karura -o my_proposal > governance.md +# ./register-chain-governance.sh -m NFTBridge -c klaytn -o my_proposal > governance.md +# ./register-chain-governance.sh -m NFTBridge -c celo -o my_proposal > governance.md +# ./register-chain-governance.sh -m NFTBridge -c acala -o my_proposal > governance.md \ No newline at end of file diff --git a/scripts/register-chain-governance.sh b/scripts/register-chain-governance.sh new file mode 100755 index 000000000..5e96d80e8 --- /dev/null +++ b/scripts/register-chain-governance.sh @@ -0,0 +1,178 @@ +#!/bin/bash + +# This tool automates the process of writing bridge registration 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: +# +# ./register-chain-governance.sh -m TokenBridge -c solana > governance.md +# +# or in "multi" mode, where multiple VAAs are created in the same proposal: +# +# ./register-chain-governance.sh -m TokenBridge -c solana -o my_proposal > governance.md +# ./register-chain-governance.sh -m TokenBridge -c avalanche -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 + +function usage() { +cat <&2 +Usage: + + $(basename "$0") [-h] [-m s] [-c s] [-o d] -- Generate bridge registration governance proposal for a given module + + where: + -h show this help text + -m module (TokenBridge, NFTBridge) + -c chain name + -o multi-mode output directory +EOF +exit 1 +} + +# Check if guardiand and worm commands exist. They 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 + +if ! command -v worm >/dev/null 2>&1; then + echo "ERROR: worm binary not found" >&2 + exit 1 +fi + +### Parse command line options +address="" +module="" +chain_name="" +multi_mode=false +out_dir="" +while getopts ':hm:c:a:o:' option; do + case "$option" in + h) usage + ;; + m) module=$OPTARG + ;; + c) chain_name=$OPTARG + ;; + o) multi_mode=true + out_dir=$OPTARG + ;; + :) printf "missing argument for -%s\n" "$OPTARG" >&2 + usage + ;; + \?) printf "illegal option: -%s\n" "$OPTARG" >&2 + usage + ;; + esac +done +shift $((OPTIND - 1)) + +[ -z "$chain_name" ] && usage +[ -z "$module" ] && usage + +# Use the worm client to get the emitter address and wormhole chain ID. +address=`worm contract --emitter mainnet $chain_name $module` +[ -z "$address" ] && usage + +chain=`worm chain-id $chain_name` +[ -z "$chain" ] && usage + +### 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 + +# Generate the command to create the governance prototxt +function create_governance() { + case "$module" in + TokenBridge) + echo "\ +guardiand template token-bridge-register-chain \\ + --chain-id $chain --module \"TokenBridge\" \\ + --new-address $address" + ;; + NFTBridge) + echo "\ +guardiand template token-bridge-register-chain \\ + --chain-id $chain --module \"NFTBridge\" \\ + --new-address $address" + ;; + *) echo "unknown module $module" >&2 + usage + ;; + esac +} + +################################################################################ +# Construct the governance proto + +echo "# Registration $chain_name $module" >> "$gov_msg_file" +# Append the new governance message to the gov file +eval "$(create_governance)" >> "$gov_msg_file" + +# 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') + +################################################################################ +# 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 + cat << EOF > governance.prototxt + $(cat "$gov_msg_file") + + EOF + + guardiand admin governance-vaa-inject --socket /path/to/admin.sock governance.prototxt + \`\`\` + + Expected digest(s): + \`\`\` + $digest + \`\`\` +EOD