|
| 1 | +#!/usr/bin/env bash |
| 2 | +# |
| 3 | +# upgrade-dev-contract.sh — Step 2 of a dev-cluster upgrade: get the contract |
| 4 | +# WASM (published release or a local build), borsh-serialize it, propose the |
| 5 | +# update and vote it in with the cluster's member accounts. Run only after the |
| 6 | +# nodes are on the new version. |
| 7 | +# |
| 8 | +# Usage: ./scripts/ops/dev-cluster/upgrade-dev-contract.sh <VERSION> <testnet|mainnet> |
| 9 | +# Env: MPC_WASM_SOURCE=release|build skips the source prompt; |
| 10 | +# MPC_SIGN_WITH (default sign-with-keychain — use |
| 11 | +# sign-with-legacy-keychain if the keychain can't find the key); |
| 12 | +# MPC_OPS_CACHE (default ~/.cache/mpc-ops) holds the artifacts. |
| 13 | +# |
| 14 | + |
| 15 | +set -euo pipefail |
| 16 | + |
| 17 | +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
| 18 | +# shellcheck source=../common.sh |
| 19 | +source "${SCRIPT_DIR}/../common.sh" |
| 20 | +# shellcheck source=dev-common.sh |
| 21 | +source "${SCRIPT_DIR}/dev-common.sh" |
| 22 | + |
| 23 | +write_u32_le() { |
| 24 | + local n=$1 i |
| 25 | + for i in 0 8 16 24; do |
| 26 | + # shellcheck disable=SC2059 |
| 27 | + printf "\\x$(printf '%02x' $(( (n >> i) & 0xFF )))" |
| 28 | + done |
| 29 | +} |
| 30 | + |
| 31 | +# Echoes the wasm path; progress goes to stderr so it stays capturable. |
| 32 | +fetch_wasm() { |
| 33 | + local version=$1 dir=$2 source=${MPC_WASM_SOURCE:-} |
| 34 | + local wasm="${dir}/mpc-contract-v${version}.wasm" |
| 35 | + |
| 36 | + if [[ -z "$source" ]]; then |
| 37 | + local choice |
| 38 | + read -rp "WASM source — (r)eleased ${version} or local (b)uild? [r] " choice >&2 |
| 39 | + case "${choice:-r}" in |
| 40 | + r|R) source=release ;; |
| 41 | + b|B) source=build ;; |
| 42 | + *) die "Unknown source '${choice}'." ;; |
| 43 | + esac |
| 44 | + fi |
| 45 | + |
| 46 | + if [[ "$source" == release ]]; then |
| 47 | + if [[ -f "$wasm" ]]; then |
| 48 | + step "==> Reusing ${wasm}" >&2 |
| 49 | + else |
| 50 | + step "==> Downloading contract WASM from release ${version}..." >&2 |
| 51 | + require_cmds gh tar |
| 52 | + run_cmd gh release download "$version" --repo near/mpc \ |
| 53 | + --pattern "mpc-contract-v${version}.tar.gz" --dir "$dir" --clobber >&2 |
| 54 | + run_cmd tar xzf "${dir}/mpc-contract-v${version}.tar.gz" -C "$dir" >&2 |
| 55 | + [[ -f "$wasm" ]] || die "Expected ${wasm} after extracting the tarball." |
| 56 | + fi |
| 57 | + else |
| 58 | + require_cmds cargo git |
| 59 | + local root built |
| 60 | + root=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel) |
| 61 | + step "==> Building the contract from ${root} (local build — not a released artifact)..." >&2 |
| 62 | + ( cd "$root" && run_cmd cargo near build non-reproducible-wasm --features abi \ |
| 63 | + --profile=release-contract --manifest-path crates/contract/Cargo.toml --locked >&2 ) |
| 64 | + # Named, not globbed: target/near also holds tee_verifier and |
| 65 | + # test_parallel_contract. |
| 66 | + built="${root}/target/near/mpc_contract/mpc_contract.wasm" |
| 67 | + [[ -f "$built" ]] || die "Expected ${built} after the cargo-near build." |
| 68 | + cp "$built" "$wasm" |
| 69 | + fi |
| 70 | + echo "$wasm" |
| 71 | +} |
| 72 | + |
| 73 | +[[ $# -eq 2 ]] || die "Usage: $0 <VERSION> <testnet|mainnet>" |
| 74 | +VERSION=$1 |
| 75 | +check_version "$VERSION" |
| 76 | +resolve_dev_cluster "$2" |
| 77 | +require_cmds near |
| 78 | + |
| 79 | +CACHE="${MPC_OPS_CACHE:-$HOME/.cache/mpc-ops}/${VERSION}" |
| 80 | +mkdir -p "$CACHE" |
| 81 | + |
| 82 | +WASM=$(fetch_wasm "$VERSION" "$CACHE") |
| 83 | +echo " wasm sha256: $(sha256_of "$WASM")" |
| 84 | + |
| 85 | +SERIALIZED="${CACHE}/serialized.bin" |
| 86 | +WASM_SIZE=$(wc -c < "$WASM") |
| 87 | +# borsh ProposeUpdateArgs { code: Some(wasm), config: None } |
| 88 | +{ |
| 89 | + printf '\x01' |
| 90 | + write_u32_le "$WASM_SIZE" |
| 91 | + cat "$WASM" |
| 92 | + printf '\x00' |
| 93 | +} > "$SERIALIZED" |
| 94 | +[[ "$(wc -c < "$SERIALIZED")" -eq $((WASM_SIZE + 6)) ]] \ |
| 95 | + || die "serialized.bin has an unexpected length." |
| 96 | +step "==> ${SERIALIZED} ready ($(wc -c < "$SERIALIZED") bytes)" |
| 97 | + |
| 98 | +PROPOSER=${MEMBER_ACCOUNTS%% *} |
| 99 | +PROPOSE_CMD=(near contract call-function as-transaction "$CONTRACT" propose_update |
| 100 | + file-args "$SERIALIZED" prepaid-gas '100.0 Tgas' attached-deposit "$PROPOSE_DEPOSIT" |
| 101 | + sign-as "$PROPOSER" network-config "$NEAR_NET" "$SIGN_WITH" send) |
| 102 | + |
| 103 | +step "About to propose the ${VERSION} contract on ${CONTRACT} (${NEAR_NET})" |
| 104 | +echo " proposer: ${PROPOSER}, deposit ${PROPOSE_DEPOSIT}" |
| 105 | +show_cmd "${PROPOSE_CMD[@]}" |
| 106 | +confirm "Send propose_update?" || { echo "Aborted before proposing."; exit 0; } |
| 107 | + |
| 108 | +"${PROPOSE_CMD[@]}" \ |
| 109 | + || die "propose_update failed (an account low on NEAR is the usual cause — top it up)." |
| 110 | + |
| 111 | +step "==> Pending proposals:" |
| 112 | +near_view proposed_updates || true |
| 113 | + |
| 114 | +# near-cli's result format is too unstable to parse an id out of. |
| 115 | +read -rp "UpdateId to vote on: " UPDATE_ID |
| 116 | +[[ "$UPDATE_ID" =~ ^[0-9]+$ ]] || die "'${UPDATE_ID}' is not a numeric UpdateId." |
| 117 | + |
| 118 | +# The deciding vote deploys + migrates inline, hence 300 Tgas. |
| 119 | +for account in $MEMBER_ACCOUNTS; do |
| 120 | + vote_cmd=(near contract call-function as-transaction "$CONTRACT" vote_update |
| 121 | + json-args "{\"id\": ${UPDATE_ID}}" prepaid-gas '300.0 Tgas' attached-deposit '0 NEAR' |
| 122 | + sign-as "$account" network-config "$NEAR_NET" "$SIGN_WITH" send) |
| 123 | + show_cmd "${vote_cmd[@]}" |
| 124 | + confirm "Vote for update ${UPDATE_ID} as ${account}?" || { echo " skipped."; continue; } |
| 125 | + "${vote_cmd[@]}" || echo " vote failed for ${account}." |
| 126 | +done |
| 127 | + |
| 128 | +step "==> Contract version (expect ${VERSION} once threshold was reached):" |
| 129 | +near_view version || true |
0 commit comments