Add optional Cosmos/Engine-X/act-runner templates, CWUSDC/EI-matrix tooling, non-EVM route planner in multi-chain-execution (tests passing), token list and extraction updates, and documentation (MetaMask matrix, GRU/CWUSDC packets). Ignore institutional evidence tarballs/sha256 under reports/status. Validated with: bash scripts/verify/run-all-validation.sh --skip-genesis Co-authored-by: Cursor <cursoragent@cursor.com>
392 lines
14 KiB
Bash
Executable File
392 lines
14 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Deploy/configure CWMultiTokenBridgeL2 receivers for active public cW chains.
|
|
# Defaults to dry-run. Use --apply to broadcast.
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
SMOM_ROOT="$PROJECT_ROOT/smom-dbis-138"
|
|
ENV_FILE="$SMOM_ROOT/.env"
|
|
REPORT_DIR="$PROJECT_ROOT/reports/status"
|
|
APPLY=false
|
|
FULL_FAMILY=false
|
|
CHAIN_FILTER=""
|
|
TS="$(date -u +%Y%m%dT%H%M%SZ)"
|
|
MANIFEST="$REPORT_DIR/cw-multitoken-l2-remediation-${TS}.jsonl"
|
|
|
|
usage() {
|
|
cat <<USAGE
|
|
Usage: $0 [--apply] [--full-family] [--chain <chainId>]
|
|
|
|
Without --apply this prints actions only. With --apply it broadcasts deployments
|
|
and configuration transactions, then updates smom-dbis-138/.env CW_BRIDGE_*.
|
|
USAGE
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--apply) APPLY=true ;;
|
|
--full-family) FULL_FAMILY=true ;;
|
|
--chain) CHAIN_FILTER="${2:-}"; shift ;;
|
|
--chain=*) CHAIN_FILTER="${1#*=}" ;;
|
|
-h|--help) usage; exit 0 ;;
|
|
*) echo "Unknown arg: $1" >&2; usage >&2; exit 1 ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
# shellcheck disable=SC1091
|
|
source "$PROJECT_ROOT/scripts/lib/load-project-env.sh" >/dev/null 2>&1 || true
|
|
|
|
[[ -n "${PRIVATE_KEY:-}" ]] || { echo "PRIVATE_KEY is required" >&2; exit 1; }
|
|
command -v cast >/dev/null 2>&1 || { echo "cast is required" >&2; exit 1; }
|
|
command -v forge >/dev/null 2>&1 || { echo "forge is required" >&2; exit 1; }
|
|
command -v jq >/dev/null 2>&1 || { echo "jq is required" >&2; exit 1; }
|
|
|
|
mkdir -p "$REPORT_DIR"
|
|
: > "$MANIFEST"
|
|
|
|
DEPLOYER="${DEPLOYER_ADDRESS:-$(cast wallet address "$PRIVATE_KEY")}"
|
|
L1_BRIDGE="${CW_L1_BRIDGE_CHAIN138:-}"
|
|
RPC_138="${RPC_URL_138:-${CHAIN138_RPC:-${CHAIN138_RPC_URL:-}}}"
|
|
[[ -n "$L1_BRIDGE" && "$L1_BRIDGE" != "0x0000000000000000000000000000000000000000" ]] || { echo "CW_L1_BRIDGE_CHAIN138 is required" >&2; exit 1; }
|
|
[[ -n "$RPC_138" ]] || { echo "RPC_URL_138/CHAIN138_RPC is required" >&2; exit 1; }
|
|
|
|
MINTER_ROLE="$(cast keccak "MINTER_ROLE")"
|
|
BURNER_ROLE="$(cast keccak "BURNER_ROLE")"
|
|
|
|
declare -A CHAIN_NAME=(
|
|
[10]="Optimism"
|
|
[25]="Cronos"
|
|
[56]="BSC"
|
|
[100]="Gnosis"
|
|
[137]="Polygon"
|
|
[8453]="Base"
|
|
[42161]="Arbitrum"
|
|
[42220]="Celo"
|
|
)
|
|
declare -A CHAIN_SUFFIX=(
|
|
[10]="OPTIMISM"
|
|
[25]="CRONOS"
|
|
[56]="BSC"
|
|
[100]="GNOSIS"
|
|
[137]="POLYGON"
|
|
[8453]="BASE"
|
|
[42161]="ARBITRUM"
|
|
[42220]="CELO"
|
|
)
|
|
declare -A CHAIN_SELECTOR=(
|
|
[10]="3734403246176062136"
|
|
[25]="1456215246176062136"
|
|
[56]="11344663589394136015"
|
|
[100]="465200170687744372"
|
|
[137]="4051577828743386545"
|
|
[8453]="15971525489660198786"
|
|
[42161]="4949039107694359620"
|
|
[42220]="1346049177634351622"
|
|
)
|
|
|
|
set_env_value() {
|
|
local file="$1" key="$2" value="$3"
|
|
if grep -qE "^${key}=" "$file"; then
|
|
sed -i "s|^${key}=.*|${key}=${value}|" "$file"
|
|
else
|
|
printf '\n%s=%s\n' "$key" "$value" >> "$file"
|
|
fi
|
|
}
|
|
|
|
var_value() {
|
|
local key="$1"
|
|
printf '%s' "${!key:-}"
|
|
}
|
|
|
|
first_env() {
|
|
local key value
|
|
for key in "$@"; do
|
|
value="$(var_value "$key")"
|
|
if [[ -n "$value" ]]; then
|
|
printf '%s\n' "$value"
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
rpc_for_chain() {
|
|
local suffix="$1"
|
|
case "$suffix" in
|
|
OPTIMISM) first_env OPTIMISM_RPC_URL OPTIMISM_MAINNET_RPC ;;
|
|
CRONOS) first_env CRONOS_RPC_URL CRONOS_RPC CRONOS_MAINNET_RPC ;;
|
|
BSC) first_env BSC_RPC_URL BSC_MAINNET_RPC ;;
|
|
GNOSIS) first_env GNOSIS_RPC_URL GNOSIS_MAINNET_RPC GNOSIS_RPC ;;
|
|
POLYGON) first_env POLYGON_RPC_URL POLYGON_MAINNET_RPC ;;
|
|
BASE) first_env BASE_RPC_URL BASE_MAINNET_RPC ;;
|
|
ARBITRUM) first_env ARBITRUM_RPC_URL ARBITRUM_MAINNET_RPC ;;
|
|
CELO) first_env CELO_RPC_URL CELO_MAINNET_RPC CELO_RPC ;;
|
|
esac
|
|
}
|
|
|
|
old_bridge_for_suffix() {
|
|
local suffix="$1" key="CW_BRIDGE_${suffix}"
|
|
var_value "$key"
|
|
}
|
|
|
|
old_bridge_call() {
|
|
local bridge="$1" rpc="$2" sig="$3"
|
|
[[ -n "$bridge" && -n "$rpc" ]] || return 1
|
|
cast call "$bridge" "$sig" --rpc-url "$rpc" 2>/dev/null | awk 'NF{print $1; exit}'
|
|
}
|
|
|
|
send_router_for_chain() {
|
|
local suffix="$1" old_bridge="$2" rpc="$3" value
|
|
value="$(first_env "CCIP_${suffix}_ROUTER" "CCIP_ROUTER_${suffix}" 2>/dev/null || true)"
|
|
if [[ -n "$value" ]]; then printf '%s\n' "$value"; return; fi
|
|
old_bridge_call "$old_bridge" "$rpc" "ccipRouter()(address)"
|
|
}
|
|
|
|
receive_router_for_chain() {
|
|
local suffix="$1" send_router="$2" value
|
|
value="$(first_env "CCIP_RELAY_ROUTER_${suffix}_CW" "CCIP_RELAY_ROUTER_${suffix}" 2>/dev/null || true)"
|
|
if [[ -n "$value" ]]; then printf '%s\n' "$value"; return; fi
|
|
printf '%s\n' "$send_router"
|
|
}
|
|
|
|
fee_token_for_chain() {
|
|
local suffix="$1" old_bridge="$2" rpc="$3" value
|
|
value="$(first_env "CCIP_${suffix}_LINK_TOKEN" "LINK_TOKEN_${suffix}" "LINK_${suffix}" 2>/dev/null || true)"
|
|
if [[ -n "$value" ]]; then printf '%s\n' "$value"; return; fi
|
|
old_bridge_call "$old_bridge" "$rpc" "feeToken()(address)" || printf '0x0000000000000000000000000000000000000000\n'
|
|
}
|
|
|
|
json_log() {
|
|
python3 - "$MANIFEST" "$@" <<'PY'
|
|
import json, sys
|
|
path = sys.argv[1]
|
|
items = dict(arg.split("=", 1) for arg in sys.argv[2:])
|
|
with open(path, "a", encoding="utf-8") as fh:
|
|
fh.write(json.dumps(items, sort_keys=True) + "\n")
|
|
PY
|
|
}
|
|
|
|
token_rows_for_chain() {
|
|
local chain_id="$1"
|
|
jq -r --argjson cid "$chain_id" --argjson full "$($FULL_FAMILY && echo true || echo false)" '
|
|
.pairs[]
|
|
| select(.fromChainId == 138 and .toChainId == $cid)
|
|
| .tokens[]
|
|
| select((.key == "Compliant_USDT_cW") or (.key == "Compliant_USDC_cW") or ($full and (.key | endswith("_cW"))))
|
|
| select(.addressFrom and .addressTo and .addressTo != "0x0000000000000000000000000000000000000000")
|
|
| [.key, .addressFrom, .addressTo] | @tsv
|
|
' "$PROJECT_ROOT/config/token-mapping-multichain.json"
|
|
}
|
|
|
|
send_tx() {
|
|
local rpc="$1"; shift
|
|
local base_cmd=(cast send --rpc-url "$rpc" --private-key "$PRIVATE_KEY" --gas-limit 500000 "$@")
|
|
if ! $APPLY; then
|
|
printf '[dry-run] '
|
|
printf '%q ' "${base_cmd[@]/$PRIVATE_KEY/\$PRIVATE_KEY}"
|
|
printf '\n'
|
|
return 0
|
|
fi
|
|
local attempt mode out rc
|
|
for mode in legacy eip1559; do
|
|
local cmd=("${base_cmd[@]}")
|
|
if [[ "$mode" == "legacy" ]]; then
|
|
cmd=(cast send --rpc-url "$rpc" --private-key "$PRIVATE_KEY" --legacy --gas-limit 500000 "$@")
|
|
fi
|
|
for attempt in 1 2 3; do
|
|
set +e
|
|
out="$("${cmd[@]}" 2>&1)"
|
|
rc=$?
|
|
set -e
|
|
printf '%s\n' "$out"
|
|
if [[ "$rc" -eq 0 ]]; then
|
|
sleep 3
|
|
return 0
|
|
fi
|
|
if [[ "$mode" == "legacy" && ( "$out" == *"Invalid params"* || "$out" == *"legacy"* || "$out" == *"transaction type"* ) ]]; then
|
|
echo " legacy tx rejected; retrying with EIP-1559 tx params"
|
|
break
|
|
fi
|
|
if [[ "$out" == *"max fee per gas less than block base fee"* ]]; then
|
|
local gas_price
|
|
gas_price="$(cast gas-price --rpc-url "$rpc" 2>/dev/null || echo 25000000)"
|
|
gas_price="$((gas_price * 2))"
|
|
echo " gas price below base fee; retrying with --gas-price $gas_price"
|
|
cmd=(cast send --rpc-url "$rpc" --private-key "$PRIVATE_KEY" --gas-limit 500000 --gas-price "$gas_price" "$@")
|
|
sleep $((attempt * 3))
|
|
continue
|
|
fi
|
|
if [[ "$out" == *"replacement transaction underpriced"* || "$out" == *"nonce too low"* || "$out" == *"invalid nonce"* || "$out" == *"invalid sequence"* || "$out" == *"already known"* ]]; then
|
|
echo " retryable tx error (attempt $attempt); waiting before retry"
|
|
sleep $((attempt * 10))
|
|
continue
|
|
fi
|
|
return "$rc"
|
|
done
|
|
done
|
|
return "$rc"
|
|
}
|
|
|
|
forge_create_l2() {
|
|
local rpc="$1" chain_id="$2" send_router="$3" receive_router="$4" fee_token="$5"
|
|
local mode out rc
|
|
for mode in legacy eip1559; do
|
|
set +e
|
|
if [[ "$mode" == "legacy" ]]; then
|
|
out="$(
|
|
cd "$SMOM_ROOT" &&
|
|
forge create contracts/bridge/CWMultiTokenBridgeL2.sol:CWMultiTokenBridgeL2 \
|
|
--rpc-url "$rpc" --chain-id "$chain_id" --broadcast --private-key "$PRIVATE_KEY" --legacy \
|
|
--constructor-args "$send_router" "$receive_router" "$fee_token" 2>&1
|
|
)"
|
|
else
|
|
out="$(
|
|
cd "$SMOM_ROOT" &&
|
|
forge create contracts/bridge/CWMultiTokenBridgeL2.sol:CWMultiTokenBridgeL2 \
|
|
--rpc-url "$rpc" --chain-id "$chain_id" --broadcast --private-key "$PRIVATE_KEY" \
|
|
--constructor-args "$send_router" "$receive_router" "$fee_token" 2>&1
|
|
)"
|
|
fi
|
|
rc=$?
|
|
set -e
|
|
printf '%s\n' "$out"
|
|
if [[ "$rc" -eq 0 ]]; then
|
|
return 0
|
|
fi
|
|
if [[ "$mode" == "legacy" && ( "$out" == *"Invalid params"* || "$out" == *"legacy"* || "$out" == *"transaction type"* ) ]]; then
|
|
echo " legacy deploy rejected; retrying with EIP-1559 tx params"
|
|
continue
|
|
fi
|
|
return "$rc"
|
|
done
|
|
return "$rc"
|
|
}
|
|
|
|
ensure_token_pair() {
|
|
local rpc="$1" bridge="$2" key="$3" canonical="$4" mirrored="$5"
|
|
local current
|
|
current="$(cast call "$bridge" "canonicalToMirrored(address)(address)" "$canonical" --rpc-url "$rpc" 2>/dev/null || true)"
|
|
if [[ "${current,,}" == "${mirrored,,}" ]]; then
|
|
echo " pair ok $key"
|
|
return
|
|
fi
|
|
echo " configure pair $key"
|
|
send_tx "$rpc" "$bridge" "configureTokenPair(address,address)" "$canonical" "$mirrored"
|
|
}
|
|
|
|
ensure_l2_destination() {
|
|
local rpc="$1" bridge="$2"
|
|
local current
|
|
current="$(cast call "$bridge" "destinations(uint64)((address,bool))" 138 --rpc-url "$rpc" 2>/dev/null || true)"
|
|
if [[ "${current,,}" == *"${L1_BRIDGE,,}"* && "${current,,}" == *"true"* ]]; then
|
|
echo " L2 destination 138 ok"
|
|
return
|
|
fi
|
|
echo " configure L2 destination 138"
|
|
send_tx "$rpc" "$bridge" "configureDestination(uint64,address,bool)" 138 "$L1_BRIDGE" true
|
|
}
|
|
|
|
ensure_l1_destination() {
|
|
local selector="$1" key="$2" canonical="$3" bridge="$4"
|
|
local current
|
|
current="$(cast call "$L1_BRIDGE" "destinations(address,uint64)((address,bool))" "$canonical" "$selector" --rpc-url "$RPC_138" 2>/dev/null || true)"
|
|
if [[ "${current,,}" == *"${bridge,,}"* && "${current,,}" == *"true"* ]]; then
|
|
echo " L1 destination ok $key"
|
|
return
|
|
fi
|
|
echo " configure L1 destination $key selector=$selector"
|
|
send_tx "$RPC_138" "$L1_BRIDGE" "configureDestination(address,uint64,address,bool)" "$canonical" "$selector" "$bridge" true
|
|
}
|
|
|
|
ensure_role() {
|
|
local rpc="$1" token="$2" role_name="$3" role="$4" holder="$5"
|
|
local current
|
|
current="$(cast call "$token" "hasRole(bytes32,address)(bool)" "$role" "$holder" --rpc-url "$rpc" 2>/dev/null || echo false)"
|
|
if [[ "$current" == "true" ]]; then
|
|
echo " role ok $role_name $token"
|
|
return
|
|
fi
|
|
echo " grant $role_name on $token"
|
|
send_tx "$rpc" "$token" "grantRole(bytes32,address)" "$role" "$holder"
|
|
}
|
|
|
|
deploy_l2() {
|
|
local chain_id="$1" suffix="$2" rpc="$3" send_router="$4" receive_router="$5" fee_token="$6"
|
|
echo " deploying CWMultiTokenBridgeL2 send=$send_router receive=$receive_router fee=$fee_token"
|
|
if ! $APPLY; then
|
|
echo " [dry-run] forge script DeployCWMultiTokenBridgeL2"
|
|
printf '0x000000000000000000000000000000000000%04x\n' "$chain_id"
|
|
return
|
|
fi
|
|
local out addr
|
|
out="$(forge_create_l2 "$rpc" "$chain_id" "$send_router" "$receive_router" "$fee_token")"
|
|
printf '%s\n' "$out"
|
|
addr="$(printf '%s\n' "$out" | sed -nE 's/.*Deployed to:[[:space:]]*(0x[a-fA-F0-9]{40}).*/\1/p; s/.*CWMultiTokenBridgeL2:[[:space:]]*(0x[a-fA-F0-9]{40}).*/\1/p' | tail -1)"
|
|
[[ -n "$addr" ]] || { echo "Could not parse deployed bridge address for $suffix" >&2; return 1; }
|
|
printf '%s\n' "$addr"
|
|
}
|
|
|
|
is_cw_multitoken_l2() {
|
|
local rpc="$1" bridge="$2"
|
|
[[ -n "$bridge" ]] || return 1
|
|
cast call "$bridge" "sendRouter()(address)" --rpc-url "$rpc" >/dev/null 2>&1 &&
|
|
cast call "$bridge" "receiveRouter()(address)" --rpc-url "$rpc" >/dev/null 2>&1 &&
|
|
cast call "$bridge" "canonicalToMirrored(address)(address)" "0x93E66202A11B1772E55407B32B44e5Cd8eda7f22" --rpc-url "$rpc" >/dev/null 2>&1
|
|
}
|
|
|
|
echo "CWMultiToken L2 remediation"
|
|
echo "Apply: $APPLY"
|
|
echo "Full family: $FULL_FAMILY"
|
|
echo "Deployer: $DEPLOYER"
|
|
echo "Manifest: $MANIFEST"
|
|
echo
|
|
|
|
for chain_id in 10 25 56 100 137 8453 42161 42220; do
|
|
[[ -n "$CHAIN_FILTER" && "$CHAIN_FILTER" != "$chain_id" ]] && continue
|
|
suffix="${CHAIN_SUFFIX[$chain_id]}"
|
|
rpc="$(rpc_for_chain "$suffix" || true)"
|
|
old_bridge="$(old_bridge_for_suffix "$suffix")"
|
|
selector="${CHAIN_SELECTOR[$chain_id]}"
|
|
echo "=== $chain_id ${CHAIN_NAME[$chain_id]} ($suffix) ==="
|
|
if [[ -z "$rpc" ]]; then
|
|
echo " skip: missing RPC"
|
|
json_log chainId="$chain_id" suffix="$suffix" status="skipped" reason="missing_rpc"
|
|
continue
|
|
fi
|
|
send_router="$(send_router_for_chain "$suffix" "$old_bridge" "$rpc" || true)"
|
|
receive_router="$(receive_router_for_chain "$suffix" "$send_router")"
|
|
fee_token="$(fee_token_for_chain "$suffix" "$old_bridge" "$rpc")"
|
|
if [[ -z "$send_router" || -z "$receive_router" || -z "$fee_token" ]]; then
|
|
echo " skip: missing router or fee token"
|
|
json_log chainId="$chain_id" suffix="$suffix" status="skipped" reason="missing_router_or_fee"
|
|
continue
|
|
fi
|
|
|
|
current_bridge="$(old_bridge_for_suffix "$suffix")"
|
|
if is_cw_multitoken_l2 "$rpc" "$current_bridge"; then
|
|
bridge="$current_bridge"
|
|
echo " using existing CWMultiTokenBridgeL2: $bridge"
|
|
else
|
|
bridge="$(deploy_l2 "$chain_id" "$suffix" "$rpc" "$send_router" "$receive_router" "$fee_token" | tail -1)"
|
|
echo " new bridge: $bridge"
|
|
fi
|
|
if $APPLY; then
|
|
set_env_value "$ENV_FILE" "CW_BRIDGE_${suffix}" "$bridge"
|
|
fi
|
|
|
|
ensure_l2_destination "$rpc" "$bridge"
|
|
while IFS=$'\t' read -r key canonical mirrored; do
|
|
ensure_token_pair "$rpc" "$bridge" "$key" "$canonical" "$mirrored"
|
|
ensure_l1_destination "$selector" "$key" "$canonical" "$bridge"
|
|
ensure_role "$rpc" "$mirrored" "MINTER_ROLE" "$MINTER_ROLE" "$bridge"
|
|
ensure_role "$rpc" "$mirrored" "BURNER_ROLE" "$BURNER_ROLE" "$bridge"
|
|
done < <(token_rows_for_chain "$chain_id")
|
|
json_log chainId="$chain_id" suffix="$suffix" status="configured" bridge="$bridge" sendRouter="$send_router" receiveRouter="$receive_router" feeToken="$fee_token"
|
|
done
|
|
|
|
echo
|
|
echo "Done. Manifest: $MANIFEST"
|
|
echo "Next: pnpm cw:bridge-e2e-readiness && pnpm cw:full-readiness"
|