Files
smom-dbis-138/scripts/deployment/cw-enforce-bridge-only-roles.sh
defiQUG f3d2961b97
Some checks failed
CI/CD Pipeline / Lint and Format (push) Failing after 46s
CI/CD Pipeline / Terraform Validation (push) Failing after 35s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 37s
Deploy ChainID 138 / Deploy ChainID 138 (push) Failing after 1m50s
HYBX OMNL TypeScript & anchor / token-aggregation build + reconcile artifact (push) Failing after 2m19s
Validation / validate-genesis (push) Successful in 51s
Validation / validate-terraform (push) Failing after 39s
Validation / validate-kubernetes (push) Failing after 10s
CI/CD Pipeline / Solidity Contracts (push) Failing after 12m56s
Validation / validate-smart-contracts (push) Failing after 12s
CI/CD Pipeline / Security Scanning (push) Failing after 15m52s
Validation / validate-security (push) Failing after 10m59s
Validation / validate-documentation (push) Failing after 17s
Validate Token List / validate (push) Failing after 30s
OMNL reconcile anchor / Run omnl:reconcile and upload artifacts (push) Failing after 26s
Verify Deployment / Verify Deployment (push) Failing after 56s
feat: add hybx omnl stack and gas pmm tooling
2026-04-24 12:56:40 -07:00

300 lines
9.0 KiB
Bash
Executable File

#!/usr/bin/env bash
# Reconcile cW* token roles so only the configured per-network bridge keeps MINTER/BURNER.
#
# The script is intentionally conservative:
# - It grants MINTER/BURNER to the configured bridge if missing
# - It revokes MINTER/BURNER from the deployer/admin by default
# - It can revoke MINTER/BURNER from an explicit denylist of extra addresses
# - It can optionally freeze future operational role changes on newer cW* contracts
#
# Usage:
# bash scripts/deployment/cw-enforce-bridge-only-roles.sh --dry-run
# bash scripts/deployment/cw-enforce-bridge-only-roles.sh
#
# Optional env:
# CW_ROLE_CHAINS="1 10 25 56 100 137 42161 42220 43114 8453"
# CW_ROLE_TOKENS="CWUSDT CWUSDC CWEURC"
# CW_ROLE_EXTRA_REVOKE="0xabc...,0xdef..."
# CW_ROLE_KEEP_ALLOWLIST="0xabc...,0xdef..."
# CW_ROLE_REVOKE_DEPLOYER=1 # default 1
# CW_ROLE_FREEZE_IF_SUPPORTED=1 # default 1
# CW_ROLE_FROM_BLOCK=earliest # optional log scan start block for role event discovery
# PRIVATE_KEY # required unless --dry-run
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SMOM_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
PROXMOX_ROOT="$(cd "$SMOM_ROOT/.." && pwd)"
cd "$SMOM_ROOT"
DRY_RUN=0
if [[ "${1:-}" == "--dry-run" ]]; then
DRY_RUN=1
fi
if [[ -f "$PROXMOX_ROOT/scripts/lib/load-project-env.sh" ]]; then
# shellcheck disable=SC1090
PROJECT_ROOT="$PROXMOX_ROOT" source "$PROXMOX_ROOT/scripts/lib/load-project-env.sh"
elif [[ -f .env ]]; then
set -a && source .env && set +a
fi
if ! command -v cast >/dev/null 2>&1; then
echo "cast is required" >&2
exit 1
fi
if ! command -v jq >/dev/null 2>&1; then
echo "jq is required" >&2
exit 1
fi
CHAIN_ROWS=(
"1|MAINNET|ETHEREUM_MAINNET_RPC|CW_BRIDGE_MAINNET"
"10|OPTIMISM|OPTIMISM_MAINNET_RPC|CW_BRIDGE_OPTIMISM"
"25|CRONOS|CRONOS_MAINNET_RPC|CW_BRIDGE_CRONOS"
"56|BSC|BSC_MAINNET_RPC|CW_BRIDGE_BSC"
"100|GNOSIS|GNOSIS_MAINNET_RPC|CW_BRIDGE_GNOSIS"
"137|POLYGON|POLYGON_MAINNET_RPC|CW_BRIDGE_POLYGON"
"42161|ARBITRUM|ARBITRUM_MAINNET_RPC|CW_BRIDGE_ARBITRUM"
"42220|CELO|CELO_MAINNET_RPC|CW_BRIDGE_CELO"
"43114|AVALANCHE|AVALANCHE_MAINNET_RPC|CW_BRIDGE_AVALANCHE"
"8453|BASE|BASE_MAINNET_RPC|CW_BRIDGE_BASE"
)
TOKENS=(
"CWUSDT"
"CWUSDC"
"CWAUSDT"
"CWUSDW"
"CWEURC"
"CWEURT"
"CWGBPC"
"CWGBPT"
"CWAUDC"
"CWJPYC"
"CWCHFC"
"CWCADC"
"CWXAUC"
"CWXAUT"
)
CHAIN_FILTER="${CW_ROLE_CHAINS:-1 10 25 56 100 137 42161 42220 43114 8453}"
TOKEN_FILTER="${CW_ROLE_TOKENS:-}"
REVOKE_DEPLOYER="${CW_ROLE_REVOKE_DEPLOYER:-1}"
FREEZE_IF_SUPPORTED="${CW_ROLE_FREEZE_IF_SUPPORTED:-1}"
EXTRA_REVOKE_RAW="${CW_ROLE_EXTRA_REVOKE:-}"
KEEP_ALLOWLIST_RAW="${CW_ROLE_KEEP_ALLOWLIST:-}"
ROLE_FROM_BLOCK="${CW_ROLE_FROM_BLOCK:-earliest}"
MINTER_ROLE="$(cast keccak "MINTER_ROLE")"
BURNER_ROLE="$(cast keccak "BURNER_ROLE")"
DEFAULT_ADMIN_ROLE="0x0000000000000000000000000000000000000000000000000000000000000000"
send_cmd() {
if [[ "$DRY_RUN" -eq 1 ]]; then
local rendered=()
local redact_next=0
local arg
for arg in "$@"; do
if [[ "$redact_next" -eq 1 ]]; then
rendered+=("<redacted>")
redact_next=0
continue
fi
if [[ "$arg" == "--private-key" ]]; then
rendered+=("$arg")
redact_next=1
continue
fi
rendered+=("$arg")
done
echo "[dry-run] ${rendered[*]}"
return 0
fi
"$@"
}
bool_call() {
local rpc="$1"
local contract="$2"
local sig="$3"
shift 3
cast call "$contract" "$sig" "$@" --rpc-url "$rpc" 2>/dev/null
}
normalize_address() {
local value="${1,,}"
value="${value#0x}"
if [[ ${#value} -lt 40 ]]; then
return 1
fi
echo "0x${value: -40}"
}
append_unique_address() {
local __var_name="$1"
local candidate="${2,,}"
[[ -n "$candidate" ]] || return 0
local current="${!__var_name:-}"
if [[ " $current " != *" $candidate "* ]]; then
printf -v "$__var_name" '%s%s ' "$current" "$candidate"
fi
}
load_csv_addresses() {
local raw="$1"
local __out_var="$2"
local item normalized
IFS=',' read -r -a _addr_items <<< "$raw"
for item in "${_addr_items[@]}"; do
item="${item//[[:space:]]/}"
[[ -n "$item" ]] || continue
normalized="$(normalize_address "$item" || true)"
[[ -n "$normalized" ]] || continue
append_unique_address "$__out_var" "$normalized"
done
}
discover_role_holders() {
local rpc="$1"
local token="$2"
local role="$3"
cast logs "RoleGranted(bytes32,address,address)" "$role" \
--from-block "$ROLE_FROM_BLOCK" --to-block latest --address "$token" --rpc-url "$rpc" --json 2>/dev/null |
jq -r '.[].topics[2] // empty' |
while read -r topic; do
normalize_address "$topic" || true
done
}
function_exists() {
local rpc="$1"
local contract="$2"
local sig="$3"
cast call "$contract" "$sig" --rpc-url "$rpc" >/dev/null 2>&1
}
grant_role_if_supported() {
local rpc="$1"
local token="$2"
local role="$3"
local target="$4"
local current
current="$(bool_call "$rpc" "$token" "hasRole(bytes32,address)(bool)" "$role" "$target" || true)"
if [[ "$current" != "true" ]]; then
send_cmd cast send "$token" "grantRole(bytes32,address)" "$role" "$target" \
--rpc-url "$rpc" --private-key "$PRIVATE_KEY"
fi
}
revoke_role_if_present() {
local rpc="$1"
local token="$2"
local role="$3"
local target="$4"
local current
current="$(bool_call "$rpc" "$token" "hasRole(bytes32,address)(bool)" "$role" "$target" || true)"
if [[ "$current" == "true" ]]; then
send_cmd cast send "$token" "revokeRole(bytes32,address)" "$role" "$target" \
--rpc-url "$rpc" --private-key "$PRIVATE_KEY"
fi
}
role_label() {
case "$1" in
"$DEFAULT_ADMIN_ROLE") echo "DEFAULT_ADMIN_ROLE" ;;
"$MINTER_ROLE") echo "MINTER_ROLE" ;;
"$BURNER_ROLE") echo "BURNER_ROLE" ;;
*) echo "$1" ;;
esac
}
for row in "${CHAIN_ROWS[@]}"; do
IFS='|' read -r chain_id chain_key rpc_var bridge_var <<< "$row"
if [[ " $CHAIN_FILTER " != *" $chain_id "* ]]; then
continue
fi
rpc="${!rpc_var:-}"
bridge="${!bridge_var:-}"
deployer="${DEPLOYER_ADDRESS:-${DEPLOYER_WALLET:-${DEPLOYER_EOA:-${DEFAULT_FROM_ADDRESS:-}}}}"
if [[ -z "$deployer" && -n "${PRIVATE_KEY:-}" ]]; then
deployer="$(cast wallet address --private-key "$PRIVATE_KEY" 2>/dev/null || true)"
fi
bridge="$(normalize_address "$bridge" || true)"
deployer="$(normalize_address "$deployer" || true)"
if [[ -z "$rpc" || -z "$bridge" ]]; then
echo "Skip chain $chain_id ($chain_key): missing rpc or bridge env"
continue
fi
echo "=== Chain $chain_id ($chain_key) ==="
echo "Bridge: $bridge"
for token_prefix in "${TOKENS[@]}"; do
if [[ -n "$TOKEN_FILTER" && " $TOKEN_FILTER " != *" $token_prefix "* ]]; then
continue
fi
token_var="${token_prefix}_${chain_key}"
token="${!token_var:-}"
if [[ -z "$token" || "$token" == "0x0000000000000000000000000000000000000000" ]]; then
continue
fi
echo "-- $token_var $token"
grant_role_if_supported "$rpc" "$token" "$MINTER_ROLE" "$bridge"
grant_role_if_supported "$rpc" "$token" "$BURNER_ROLE" "$bridge"
keep_allowlist=""
append_unique_address keep_allowlist "$bridge"
load_csv_addresses "$KEEP_ALLOWLIST_RAW" keep_allowlist
revoke_candidates=""
while read -r discovered; do
[[ -n "$discovered" ]] || continue
append_unique_address revoke_candidates "$discovered"
done < <(discover_role_holders "$rpc" "$token" "$MINTER_ROLE")
while read -r discovered; do
[[ -n "$discovered" ]] || continue
append_unique_address revoke_candidates "$discovered"
done < <(discover_role_holders "$rpc" "$token" "$BURNER_ROLE")
if [[ "$REVOKE_DEPLOYER" == "1" && -n "$deployer" ]]; then
append_unique_address revoke_candidates "$deployer"
fi
load_csv_addresses "$EXTRA_REVOKE_RAW" revoke_candidates
for addr in $revoke_candidates; do
[[ " $keep_allowlist " == *" ${addr,,} "* ]] && continue
revoke_role_if_present "$rpc" "$token" "$MINTER_ROLE" "$addr"
revoke_role_if_present "$rpc" "$token" "$BURNER_ROLE" "$addr"
done
if [[ "$FREEZE_IF_SUPPORTED" == "1" ]]; then
if function_exists "$rpc" "$token" "operationalRolesFrozen()(bool)"; then
frozen="$(cast call "$token" "operationalRolesFrozen()(bool)" --rpc-url "$rpc" 2>/dev/null || true)"
if [[ "$frozen" != "true" ]]; then
send_cmd cast send "$token" "freezeOperationalRoles()" \
--rpc-url "$rpc" --private-key "$PRIVATE_KEY" --gas-limit 80000
fi
fi
fi
for role in "$DEFAULT_ADMIN_ROLE" "$MINTER_ROLE" "$BURNER_ROLE"; do
summary_holders=""
while read -r discovered; do
[[ -n "$discovered" ]] || continue
current="$(bool_call "$rpc" "$token" "hasRole(bytes32,address)(bool)" "$role" "$discovered" || true)"
if [[ "$current" == "true" ]]; then
append_unique_address summary_holders "$discovered"
fi
done < <(discover_role_holders "$rpc" "$token" "$role")
echo " $(role_label "$role"): ${summary_holders:-<none discovered>}"
done
done
done