Files
proxmox/scripts/deployment/run-cw-remaining-steps.sh

241 lines
10 KiB
Bash
Raw Permalink Normal View History

#!/usr/bin/env bash
# Run remaining cW* steps: deploy (or dry-run), update token-mapping from .env, optional verify.
# See docs/07-ccip/CW_DEPLOY_AND_WIRE_RUNBOOK.md and docs/00-meta/CW_BRIDGE_TASK_LIST.md.
#
# Usage:
# ./scripts/deployment/run-cw-remaining-steps.sh [--dry-run] [--deploy] [--update-mapping] [--verify] [--verify-hard-peg]
# --dry-run Run deploy-cw in dry-run mode (print commands only).
# --deploy Run deploy-cw on all chains (requires RPC/PRIVATE_KEY in smom-dbis-138/.env).
# --update-mapping Update config/token-mapping-multichain.json from CWUSDT_*/CWUSDC_* in .env.
# --verify For each chain with configured cW* tokens, check MINTER_ROLE/BURNER_ROLE on those tokens for CW_BRIDGE_*.
# --verify-hard-peg Check Avalanche hard-peg bridge controls for cWUSDT/cWUSDC.
# With no options, runs --dry-run then --update-mapping (if any CWUSDT_* in .env).
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
SMOM="${PROJECT_ROOT}/smom-dbis-138"
CONFIG="${PROJECT_ROOT}/config/token-mapping-multichain.json"
DRY_RUN=false
DO_DEPLOY=false
DO_UPDATE_MAPPING=false
DO_VERIFY=false
DO_VERIFY_HARD_PEG=false
for a in "$@"; do
case "$a" in
--dry-run) DRY_RUN=true ;;
--deploy) DO_DEPLOY=true ;;
--update-mapping) DO_UPDATE_MAPPING=true ;;
--verify) DO_VERIFY=true ;;
--verify-hard-peg) DO_VERIFY_HARD_PEG=true ;;
esac
done
if ! $DRY_RUN && ! $DO_DEPLOY && ! $DO_UPDATE_MAPPING && ! $DO_VERIFY && ! $DO_VERIFY_HARD_PEG; then
DRY_RUN=true
DO_UPDATE_MAPPING=true
fi
if [[ ! -f "$SMOM/.env" ]]; then
echo "Missing $SMOM/.env" >&2
exit 1
fi
if [[ -f "$SMOM/scripts/lib/deployment/dotenv.sh" ]]; then
# shellcheck disable=SC1090
source "$SMOM/scripts/lib/deployment/dotenv.sh"
load_deployment_env --repo-root "$SMOM"
else
set -a
set +u
source "$SMOM/.env"
set -u
set +a
if [[ -z "${PRIVATE_KEY:-}" && -n "${DEPLOYER_PRIVATE_KEY:-}" ]]; then
export PRIVATE_KEY="$DEPLOYER_PRIVATE_KEY"
fi
fi
# Chain name (env suffix) -> chainId for 138 -> chain pairs
declare -A CHAIN_NAME_TO_ID=(
[MAINNET]=1 [CRONOS]=25 [BSC]=56 [POLYGON]=137 [GNOSIS]=100
[AVALANCHE]=43114 [BASE]=8453 [ARBITRUM]=42161 [OPTIMISM]=10 [CELO]=42220 [WEMIX]=1111 [ALL]=651940
)
if $DO_DEPLOY; then
echo "=== Deploy cW* (DeployCWTokens) on all chains ==="
(cd "$SMOM" && ./scripts/deployment/deploy-tokens-and-weth-all-chains-skip-canonical.sh --deploy-cw)
echo "→ Set CWUSDT_<CHAIN> and CWUSDC_<CHAIN> in $SMOM/.env from script output; then run with --update-mapping"
fi
if $DRY_RUN; then
echo "=== Dry-run: deploy cW* (commands only) ==="
(cd "$SMOM" && ./scripts/deployment/deploy-tokens-and-weth-all-chains-skip-canonical.sh --deploy-cw --dry-run)
fi
update_mapping() {
local env_file="$SMOM/.env"
local config_file="$CONFIG"
[[ -f "$config_file" ]] || { echo "Missing $config_file" >&2; return 1; }
node -e "
const fs = require('fs');
const path = require('path');
function loadEnv(f) {
const c = fs.readFileSync(f, 'utf8');
const out = {};
c.split('\n').forEach(line => {
const m = line.match(/^([A-Za-z_0-9]+)=(.*)$/);
if (m) out[m[1]] = m[2].replace(/^[\"']|[\"']\$/g, '').trim();
});
return out;
}
const env = loadEnv('$env_file');
const chainToId = { MAINNET: 1, CRONOS: 25, BSC: 56, POLYGON: 137, GNOSIS: 100, AVALANCHE: 43114, BASE: 8453, ARBITRUM: 42161, OPTIMISM: 10, CELO: 42220, WEMIX: 1111, ALL: 651940 };
const keyToEnv = { Compliant_USDT_cW: 'CWUSDT', Compliant_USDC_cW: 'CWUSDC', Compliant_AUSDT_cW: 'CWAUSDT', Compliant_EURC_cW: 'CWEURC', Compliant_EURT_cW: 'CWEURT', Compliant_GBPC_cW: 'CWGBPC', Compliant_GBPT_cW: 'CWGBPT', Compliant_AUDC_cW: 'CWAUDC', Compliant_JPYC_cW: 'CWJPYC', Compliant_CHFC_cW: 'CWCHFC', Compliant_CADC_cW: 'CWCADC', Compliant_XAUC_cW: 'CWXAUC', Compliant_XAUT_cW: 'CWXAUT' };
function getEnvAddress(prefix, chainName, chainId) {
return env[prefix + '_' + chainName] || env[prefix + '_ADDRESS_' + chainId] || '';
}
const j = JSON.parse(fs.readFileSync('$config_file', 'utf8'));
let updated = 0;
for (const [name, chainId] of Object.entries(chainToId)) {
const pair = j.pairs.find(p => p.fromChainId === 138 && p.toChainId === chainId);
if (!pair) continue;
for (const t of pair.tokens) {
const envKey = keyToEnv[t.key];
if (!envKey) continue;
const addr = getEnvAddress(envKey, name, chainId);
if (addr && t.addressTo !== addr) { t.addressTo = addr; updated++; }
}
}
if (updated) {
fs.writeFileSync('$config_file', JSON.stringify(j, null, 2) + '\n');
console.log('Updated', updated, 'addressTo entries in token-mapping-multichain.json');
} else {
console.log('No cW* addresses set in .env for mapped chains, or already up to date');
}
"
}
if $DO_UPDATE_MAPPING; then
echo "=== Update token-mapping from .env ==="
update_mapping
fi
if $DO_VERIFY; then
echo "=== Verify MINTER/BURNER roles on cW* for each chain ==="
MINTER_ROLE=$(cast keccak "MINTER_ROLE" 2>/dev/null || echo "0x")
BURNER_ROLE=$(cast keccak "BURNER_ROLE" 2>/dev/null || echo "0x")
CW_TOKEN_PREFIXES=(CWUSDT CWUSDC CWAUSDT CWEURC CWEURT CWGBPC CWGBPT CWAUDC CWJPYC CWCHFC CWCADC CWXAUC CWXAUT)
resolve_cw_token_value() {
local prefix="$1"
local name="$2"
local chain_id="${CHAIN_NAME_TO_ID[$name]:-}"
local chain_var="${prefix}_${name}"
local address_var=""
if [[ -n "$chain_id" ]]; then
address_var="${prefix}_ADDRESS_${chain_id}"
fi
if [[ -n "${!chain_var:-}" ]]; then
printf '%s' "${!chain_var}"
return
fi
if [[ -n "$address_var" && -n "${!address_var:-}" ]]; then
printf '%s' "${!address_var}"
return
fi
printf ''
}
for name in MAINNET CRONOS BSC POLYGON GNOSIS AVALANCHE BASE ARBITRUM OPTIMISM CELO WEMIX; do
bridge_var="CW_BRIDGE_${name}"
bridge="${!bridge_var:-}"
rpc_var="${name}_RPC_URL"
[[ -z "$rpc_var" ]] && rpc_var="${name}_RPC"
rpc="${!rpc_var:-}"
if [[ -z "$bridge" ]]; then continue; fi
if [[ -z "$rpc" ]]; then
case "$name" in
MAINNET) rpc="${ETH_MAINNET_RPC_URL:-${ETHEREUM_MAINNET_RPC:-}}";;
CRONOS) rpc="${CRONOS_RPC_URL:-${CRONOS_RPC:-}}";;
BSC) rpc="${BSC_RPC_URL:-}";;
POLYGON) rpc="${POLYGON_MAINNET_RPC:-${POLYGON_RPC_URL:-}}";;
GNOSIS) rpc="${GNOSIS_RPC:-}";;
AVALANCHE) rpc="${AVALANCHE_RPC_URL:-}";;
BASE) rpc="${BASE_MAINNET_RPC:-}";;
ARBITRUM) rpc="${ARBITRUM_MAINNET_RPC:-}";;
OPTIMISM) rpc="${OPTIMISM_MAINNET_RPC:-}";;
CELO) rpc="${CELO_RPC:-${CELO_MAINNET_RPC:-}}";;
WEMIX) rpc="${WEMIX_RPC:-${WEMIX_MAINNET_RPC:-}}";;
esac
fi
if [[ -z "$rpc" ]]; then echo " Skip $name: no RPC"; continue; fi
found_any=false
for prefix in "${CW_TOKEN_PREFIXES[@]}"; do
token="$(resolve_cw_token_value "$prefix" "$name")"
if [[ -z "$token" ]]; then
continue
fi
found_any=true
m=$(cast call "$token" "hasRole(bytes32,address)(bool)" "$MINTER_ROLE" "$bridge" --rpc-url "$rpc" 2>/dev/null || echo "false")
b=$(cast call "$token" "hasRole(bytes32,address)(bool)" "$BURNER_ROLE" "$bridge" --rpc-url "$rpc" 2>/dev/null || echo "false")
echo " $name $prefix: MINTER=$m BURNER=$b (token=$token bridge=$bridge)"
done
if ! $found_any; then
echo " Skip $name: no cW* token addresses configured"
fi
done
fi
call_or_unavailable() {
local rpc="$1"
local address="$2"
local signature="$3"
shift 3
if [[ -z "$rpc" || -z "$address" ]]; then
printf 'unavailable\n'
return
fi
cast call "$address" "$signature" "$@" --rpc-url "$rpc" 2>/dev/null || printf 'legacy-or-unavailable\n'
}
if $DO_VERIFY_HARD_PEG; then
echo "=== Verify Avalanche hard-peg bridge state ==="
CHAIN138_L1_BRIDGE="${CW_L1_BRIDGE_CHAIN138:-}"
AVAX_CW_BRIDGE="${CW_BRIDGE_AVALANCHE:-}"
RESERVE_VERIFIER="${CW_RESERVE_VERIFIER_CHAIN138:-}"
AVALANCHE_SELECTOR_VALUE="${AVALANCHE_SELECTOR:-6433500567565415381}"
CW_CANONICAL_USDT_ADDR="${CW_CANONICAL_USDT:-${COMPLIANT_USDT_ADDRESS:-${CUSDT_ADDRESS_138:-}}}"
CW_CANONICAL_USDC_ADDR="${CW_CANONICAL_USDC:-${COMPLIANT_USDC_ADDRESS:-${CUSDC_ADDRESS_138:-}}}"
echo " Chain 138 L1 bridge: $CHAIN138_L1_BRIDGE"
echo " Avalanche cW bridge: $AVAX_CW_BRIDGE"
echo " Reserve verifier: ${RESERVE_VERIFIER:-unconfigured}"
echo " Avalanche selector: $AVALANCHE_SELECTOR_VALUE"
if [[ -n "$CHAIN138_L1_BRIDGE" ]]; then
echo " L1 bridge reserveVerifier(): $(call_or_unavailable "$RPC_URL_138" "$CHAIN138_L1_BRIDGE" "reserveVerifier()(address)")"
fi
for entry in "cUSDT:$CW_CANONICAL_USDT_ADDR:$CW_MAX_OUTSTANDING_USDT_AVALANCHE" "cUSDC:$CW_CANONICAL_USDC_ADDR:$CW_MAX_OUTSTANDING_USDC_AVALANCHE"; do
IFS=":" read -r label token desired_cap <<<"$entry"
if [[ -z "$token" ]]; then
echo " $label: canonical token not set"
continue
fi
echo " $label supportedCanonicalToken(): $(call_or_unavailable "$RPC_URL_138" "$CHAIN138_L1_BRIDGE" "supportedCanonicalToken(address)(bool)" "$token")"
echo " $label maxOutstanding(): $(call_or_unavailable "$RPC_URL_138" "$CHAIN138_L1_BRIDGE" "maxOutstanding(address,uint64)(uint256)" "$token" "$AVALANCHE_SELECTOR_VALUE")"
if [[ -n "$desired_cap" ]]; then
echo " $label desired maxOutstanding env: $desired_cap"
fi
echo " $label tokenPairFrozen(): $(call_or_unavailable "$AVALANCHE_RPC_URL" "$AVAX_CW_BRIDGE" "tokenPairFrozen(address)(bool)" "$token")"
if [[ -n "$RESERVE_VERIFIER" ]]; then
echo " $label verifier tokenConfigs(): $(call_or_unavailable "$RPC_URL_138" "$RESERVE_VERIFIER" "tokenConfigs(address)(bool,address,bool,bool,bool)" "$token" | tr '\n' ' ' | xargs)"
echo " $label verifier getVerificationStatus(): $(call_or_unavailable "$RPC_URL_138" "$RESERVE_VERIFIER" "getVerificationStatus(address,uint64)((bool,bool,bool,bool,bool,bool,bool,bool,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256))" "$token" "$AVALANCHE_SELECTOR_VALUE" | tr '\n' ' ' | xargs)"
fi
done
echo " Avalanche destinationFrozen(138): $(call_or_unavailable "$AVALANCHE_RPC_URL" "$AVAX_CW_BRIDGE" "destinationFrozen(uint64)(bool)" 138)"
fi
echo "Done. See docs/07-ccip/CW_DEPLOY_AND_WIRE_RUNBOOK.md for Phase E (relay and E2E)."