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
300 lines
9.0 KiB
Bash
Executable File
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
|