From e1f4b8e10ba6c114a39ad822e5293928d5999cd2 Mon Sep 17 00:00:00 2001 From: Csongor Kiss Date: Sat, 2 Apr 2022 13:30:40 +0000 Subject: [PATCH] Add scripts and readme to deploy and verify terra contracts --- .gitignore | 3 + Makefile.help | 15 +++++ terra/Dockerfile | 11 +++- terra/Dockerfile.build | 11 ---- terra/Makefile | 59 +++++++++++++++++++ terra/README.md | 121 +++++++++++++++++++++++++++++++------- terra/build.sh | 6 -- terra/generate_governance | 69 ++++++++++++++++++++++ terra/verify | 63 ++++++++++++++++++++ 9 files changed, 316 insertions(+), 42 deletions(-) create mode 100644 Makefile.help delete mode 100644 terra/Dockerfile.build create mode 100644 terra/Makefile delete mode 100755 terra/build.sh create mode 100755 terra/generate_governance create mode 100755 terra/verify diff --git a/.gitignore b/.gitignore index b53c24499..5b455b1b3 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,6 @@ venv bigtable-admin.json bigtable-writer.json **/cert.pem +**/payer-mainnet.json +**/payer-testnet.json +**/payer-devnet.json diff --git a/Makefile.help b/Makefile.help new file mode 100644 index 000000000..1b8e6aa99 --- /dev/null +++ b/Makefile.help @@ -0,0 +1,15 @@ +## This help screen +help: + @printf "Available targets:\n\n" + @awk '/^[a-zA-Z\-\_0-9%:\\]+/ { \ + helpMessage = match(lastLine, /^## (.*)/); \ + if (helpMessage) { \ + helpCommand = $$1; \ + helpMessage = substr(lastLine, RSTART + 3, RLENGTH); \ + gsub("\\\\", "", helpCommand); \ + gsub(":+$$", "", helpCommand); \ + printf " \x1b[32;01m%-35s\x1b[0m %s\n", helpCommand, helpMessage; \ + } \ + } \ + { lastLine = $$0 }' $(MAKEFILE_LIST) + @printf "\n" diff --git a/terra/Dockerfile b/terra/Dockerfile index bc8e7ffac..86bdbde4e 100644 --- a/terra/Dockerfile +++ b/terra/Dockerfile @@ -1,5 +1,7 @@ -# This is a multi-stage docker file, first stage builds contracts -# And the second one creates node.js environment to deploy them +# This is a multi-stage docker file: +# 1. The first stage builds the contracts +# 2. The second is an empty image with only the wasm files (useful for exporting) +# 3. The third creates a node.js environment to deploy the contracts to devnet FROM cosmwasm/workspace-optimizer:0.12.1@sha256:1508cf7545f4b656ecafa34e29c1acf200cdab47fced85c2bc076c0c158b1338 AS builder COPY Cargo.lock /code/ COPY Cargo.toml /code/ @@ -13,6 +15,9 @@ RUN if [ -e /certs/cert.pem ]; then cp /certs/cert.pem /etc/ssl/cert.pem; fi RUN optimize_workspace.sh +FROM scratch as artifacts +COPY --from=builder /code/artifacts / + # Contract deployment stage FROM node:16-buster-slim@sha256:93c9fc3550f5f7d159f282027228e90e3a7f8bf38544758024f005e82607f546 @@ -28,7 +33,7 @@ RUN apt update && apt install netcat curl jq -y WORKDIR /app/tools -COPY --from=builder /code/artifacts /app/artifacts +COPY --from=artifacts / /app/artifacts COPY ./artifacts/cw20_base.wasm /app/artifacts/ COPY ./tools/package.json ./tools/package-lock.json /app/tools/ diff --git a/terra/Dockerfile.build b/terra/Dockerfile.build deleted file mode 100644 index fd3a3931a..000000000 --- a/terra/Dockerfile.build +++ /dev/null @@ -1,11 +0,0 @@ -# Run with: -# docker build -f Dockerfile.build -o artifacts . -FROM cosmwasm/workspace-optimizer:0.12.1@sha256:1508cf7545f4b656ecafa34e29c1acf200cdab47fced85c2bc076c0c158b1338 AS builder -ADD Cargo.lock /code/ -ADD Cargo.toml /code/ -ADD contracts /code/contracts -ADD packages /code/packages -RUN optimize_workspace.sh - -FROM scratch AS export-stage -COPY --from=builder /code/artifacts / diff --git a/terra/Makefile b/terra/Makefile new file mode 100644 index 000000000..8b27c909d --- /dev/null +++ b/terra/Makefile @@ -0,0 +1,59 @@ +bridge_SOURCE=wormhole +token_bridge_SOURCE=token_bridge +nft_bridge_SOURCE=nft_bridge + +SOURCE_FILES=$(shell find . -name "*.rs" -or -name "*.lock" -or -name "*.toml" | grep -v target) + +PACKAGES=$(shell find . -name "Cargo.toml" | grep -E 'packages|contracts' | cut -d/ -f3 | sed s/-/_/g) +WASMS=$(patsubst %, artifacts/%.wasm, $(PACKAGES)) + +-include ../Makefile.help + +.PHONY: artifacts +## Build contracts. +artifacts: artifacts/checksums.txt + +VALID_mainnet=1 +VALID_testnet=1 +VALID_devnet=1 +.PHONY: check-network +check-network: +ifndef VALID_$(NETWORK) + $(error Invalid or missing NETWORK. Please call with `$(MAKE) $(MAKECMDGOALS) NETWORK=[mainnet | testnet | devnet]`) +endif + +$(WASMS) artifacts/checksums.txt: $(SOURCE_FILES) + DOCKER_BUILDKIT=1 docker build --target artifacts -o artifacts . + +payer-$(NETWORK).json: + $(error Missing private key in payer-$(NETWORK).json) + +.PHONY: deploy/bridge +## Deploy core bridge +deploy/bridge: bridge-code-id-$(NETWORK).txt + +.PHONY: deploy/token_bridge +## Deploy token bridge +deploy/token_bridge: token_bridge-code-id-$(NETWORK).txt + +.PHONY: deploy/nft_bridge +## Deploy NFT bridge +deploy/nft_bridge: nft_bridge-code-id-$(NETWORK).txt + +%-code-id-$(NETWORK).txt: check-network tools/node_modules payer-$(NETWORK).json + @echo "Deploying artifacts/$($*_SOURCE).wasm on $(NETWORK)" + @node tools/deploy_single.js \ + --network $(NETWORK) \ + --artifact artifacts/$($*_SOURCE).wasm \ + --mnemonic "$$(cat payer-$(NETWORK).json)" \ + | grep -i "code id" | sed s/[^0-9]//g \ + > $@ + @echo "Deployed at code id $$(cat $@) (stored in $@)" + +tools/node_modules: tools/package-lock.json + cd tools && npm ci + +.PHONY: clean +clean: + rm -f $(WASMS) + rm -f artifacts/checksums.txt diff --git a/terra/README.md b/terra/README.md index 6b5f72c88..2e841e196 100644 --- a/terra/README.md +++ b/terra/README.md @@ -1,45 +1,122 @@ -# Deploy +# Terra Wormhole Contract Deployment -First build the contracts +This readme describes the steps for building, verifying, and deploying Terra smart contracts for Wormhole. +**WARNING**: *This process is only Linux host compatible at this time.* -``` sh -docker build -f Dockerfile.build -o artifacts . +## Verify Tilt + +Before building Terra contracts, ensure that the specific commit you will be +building from passes in tilt. This that ensures basic functionality of the +Terra smart contracts that you are about to build and deploy. + +## Build Contracts + +The following command can be used to build Terra contracts via Docker. + +Build Target Options: [`mainnet`|`testnet`|`devnet`| + +These network names correspond to the naming convention used by wormhole +elsewhere. This means that `mainnet` corresponds to Terra `mainnet`, +`testnet` corresponds to Terra `testnet`, and `devnet` is `localterra`. + +```console +wormhole/terra $ make artifacts ``` -Then, for example, to deploy `token_bridge.wasm`, run in the `tools` directory +Upon completion, the compiled bytecode for the Terra contracts will be placed +into the `artifacts` directory. -``` sh -npm ci -node deploy_single.js --network mainnet --artifact ../artifacts/token_bridge.wasm --mnemonic "..." +## Verify Checksums + +Now that you have built the Terra contracts, you should ask a peer to build +using the same process and compare the equivalent checksums.txt files to make +sure the contract bytecode(s) are deterministic. + +```console +wormhole/terra $ cat artifacts/checksums.txt ``` -which will print something along the lines of +Once you have verified the Terra contracts are deterministic with a peer, you can now move to the deploy step. -``` sh -Storing WASM: ../artifacts/token_bridge.wasm (367689 bytes) -Deploy fee: 88446uluna -Code ID: 2435 +## Deploy Contracts + +Now that you have built and verified checksums, you can now deploy one or more relevant contracts to the Terra blockchain. + +Deploy Target Options: [`mainnet`|`testnet`|`devnet`] + +You will need to define a `payer-DEPLOY_TARGET.json` for the relevant deploy +target (eg. `payer-testnet.json`). This will contain the relevant wallet +private key that you will be using to deploy the contracts. + +```console +wormhole/terra $ make deploy/bridge +wormhole/terra $ make deploy/token_bridge +wormhole/terra $ make deploy/nft_bridge ``` -# Migrate +For each deployed contract, you will get a code id for that relevant +contract for the deployment, make note of these so you can use them in +the next step for on-chain verification. -## Mainnet +## Verify On-Chain -Migrations on mainnet have to go through governance. Once the guardians sign the -upgrade VAA, the contract can be upgraded by submitting the signed VAA to the -appropriate contract. For example, to upgrade the token bridge on mainnet, -in `wormhole/clients/token_bridge/`: +Now that you have deployed one or more contracts on-chain, you can verify the +onchain bytecode and make sure it matches the same checksums you identified +above. + +For each contract you wish to verify on-chain, you will need the following elements: + +- Path to the contracts bytecode (eg. `artifacts-testnet/token_bridge.wasm`) +- Terra code id for the relevant contract (eg. `59614`) +- A network to verify on (`mainnet`, `testnet`, or `devnet`) + +Below is how to verify all three contracts: + +```console +wormhole/terra $ ./verify artifacts/wormhole.wasm NEW_BRIDGE_CODE_ID +wormhole/terra $ ./verify artifacts/token_bridge.wasm NEW_TOKEN_BRIDGE_CODE_ID +wormhole/terra $ ./verify artifacts/nft_bridge.wasm NEW_NFT_BRIDGE_CODE_ID +``` +Example: `./verify artifacts/token_bridge.wasm 59614` + +For each contract, you should expect a `Successfully verified` output message. +If all contracts can be successfully verified, you can engage in Wormhole +protocol governance to obtain an authorized VAA for the contract upgrade(s). + +A verification failure should never happen, and is a sign of some error in the +deployment process. Do not proceed with governance until you can verify the +on-chain bytecode with the locally compiled bytecode. + + +## Governance + +### Mainnet + +Upgrades on mainnet have to go through governance. Once the code is deployed in +the previous step, an unsigned governance VAA can be generated + +```sh +./generate_governance -m token_bridge -c 59614 > token-bridge-upgrade-59614.prototxt +``` + +This will write to the `token-bridge-upgrade-59614.prototxt` file, which can +now be shared with the guardians to vote on. + +Once the guardians have reached quorum, the VAA may be submitted from any +funded wallet: TODO - make this easier and more unified ``` sh node main.js terra execute_governance_vaa --rpc "https://lcd.terra.dev" --chain_id "columbus-5" --mnemonic "..." --token_bridge "terra10nmmwe8r3g99a9newtqa7a75xfgs2e8z87r2sf" ``` -## Testnet +### Testnet +For the contracts on testnet, the deployer wallet retains the upgrade +authority, so these don't have to go through governance. -For example, to migrate the token bridge to 37262, run in `tools/`: +For example, to migrate the token bridge to 59614, run in `tools/`: ``` sh -node migrate_testnet.js --code_id 37262 --contract terra1pseddrv0yfsn76u4zxrjmtf45kdlmalswdv39a --mnemonic "..." +node migrate_testnet.js --code_id 59614 --contract terra1pseddrv0yfsn76u4zxrjmtf45kdlmalswdv39a --mnemonic "..." ``` diff --git a/terra/build.sh b/terra/build.sh deleted file mode 100755 index c564a0414..000000000 --- a/terra/build.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash - -docker run --rm -v "$(pwd)":/code \ - --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ - --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/workspace-optimizer:0.12.1 diff --git a/terra/generate_governance b/terra/generate_governance new file mode 100755 index 000000000..ecf88bb71 --- /dev/null +++ b/terra/generate_governance @@ -0,0 +1,69 @@ +#!/bin/bash + +set -euo pipefail + +usage="Usage: + $(basename "$0") [-h] [-m s] [-c n] -- Generate governance prototxt for a given module to be upgraded to a given code id + + where: + -h show this help text + -m module (bridge, token_bridge, nft_bridge) + -c code id (e.g. 4018)" + +code_id="" +module="" +while getopts ':hm:c:' option; do + case "$option" in + h) echo "$usage" + exit + ;; + m) module=$OPTARG + ;; + c) code_id=$OPTARG + ;; + :) printf "missing argument for -%s\n" "$OPTARG" >&2 + echo "$usage" >&2 + exit 1 + ;; + \?) printf "illegal option: -%s\n" "$OPTARG" >&2 + echo "$usage" >&2 + exit 1 + ;; + esac +done +shift $((OPTIND - 1)) + +[ -z "$code_id" ] && { echo "$usage" >&2; exit 1; } +[ -z "$module" ] && { echo "$usage" >&2; exit 1; } + +address=$(printf "%064x" "$code_id") +TERRA_ID=3 +GUARDIAND=guardiand + +case "$module" in + bridge) + "$GUARDIAND" template contract-upgrade \ + --chain-id $TERRA_ID \ + --new-address "$address" \ + > /tmp/gov.prototxt + ;; + token_bridge) + "$GUARDIAND" template token-bridge-upgrade-contract \ + --chain-id $TERRA_ID --module "TokenBridge" \ + --new-address "$address" \ + > /tmp/gov.prototxt + ;; + nft_bridge) + "$GUARDIAND" template token-bridge-upgrade-contract \ + --chain-id $TERRA_ID --module "NFTBridge" \ + --new-address "$address" \ + > /tmp/gov.prototxt + ;; + *) echo "illegal module $module" + echo "$usage" >&2 + exit 1 + ;; +esac + +cat /tmp/gov.prototxt +"$GUARDIAND" admin governance-vaa-verify /tmp/gov.prototxt >&2 diff --git a/terra/verify b/terra/verify new file mode 100755 index 000000000..54e0a2a1a --- /dev/null +++ b/terra/verify @@ -0,0 +1,63 @@ +#!/bin/bash + +set -euo pipefail + +usage="Usage: + $(basename "$0") [-h] [-n network] <.wasm file> -- Verify that the deployed on-chain bytecode matches the local object file + + where: + -h show this help text + -n set the network (mainnet, testnet, devnet. defaults to \$NETWORK if set)" + +network=$NETWORK +while getopts ':hn:' option; do + case "$option" in + h) echo "$usage" + exit + ;; + n) network=$OPTARG + ;; + :) printf "missing argument for -%s\n" "$OPTARG" >&2 + echo "$usage" >&2 + exit 1 + ;; + \?) printf "illegal option: -%s\n" "$OPTARG" >&2 + echo "$usage" >&2 + exit 1 + ;; + esac +done +shift $((OPTIND - 1)) + + +case "$network" in + mainnet) url="https://lcd.terra.dev";; + testnet) url="https://bombay-lcd.terra.dev";; + devnet) url="http://localhost:1317";; + *) printf "Network not set. Specify with -n\n" >&2 + echo "$usage" >&2 + exit 1 + ;; +esac + +[ $# -ne 2 ] && { echo "$usage" >&2; exit 1; } +obj_file=$1 +code_id=$2 + + +hash1=`curl "$url"/terra/wasm/v1beta1/codes/"$code_id" --silent | jq '.code_info.code_hash' -r | base64 -d | hexdump -v -e '/1 "%02x" '` +hash2=`sha256sum $obj_file | cut -f1 -d' '` + +echo "Deployed bytecode hash (on $network):" +echo $hash1 +echo "$obj_file hash:" +echo $hash2 + +if [ "$hash1" == "$hash2" ]; then + printf "\033[0;32mSuccessfully verified\033[0m\n"; + exit 0; +else + printf "\033[0;31mFailed to verify\033[0m\n"; + exit 1; +fi +