#!/usr/bin/env bash # Verify deployed contracts on Blockscout (Chain 138) # Usage: ./scripts/verify-contracts-blockscout.sh [--only contract1,contract2] [--skip contract3] # Before each verify, uses `cast code` on RPC to skip EOAs / empty code (avoids Blockscout # "not a smart contract" noise for addresses like WETH10 when nothing is deployed there). # Set VERIFY_SKIP_BYTECODE_CHECK=1 to attempt forge verify even when code lookup fails or is empty. # Version: 2026-04-22 (DODO PMM + TransactionMirror: explicit constructor args + deploy profile) set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" # Load env and contract addresses (RPC_URL_138, IP_BLOCKSCOUT, ORACLE_PROXY, CCIP_SENDER, etc.) if [[ -f "${SCRIPT_DIR}/lib/load-project-env.sh" ]]; then source "${SCRIPT_DIR}/lib/load-project-env.sh" fi [[ -f "${PROJECT_ROOT}/config/contract-addresses.conf" ]] && source "${PROJECT_ROOT}/config/contract-addresses.conf" 2>/dev/null || true SMOM="${SMOM_DIR:-${PROJECT_ROOT}/smom-dbis-138}" ALLTRA="${PROJECT_ROOT}/alltra-lifi-settlement" RPC="${RPC_URL_138:-http://192.168.11.211:8545}" IP_BLOCKSCOUT="${IP_BLOCKSCOUT:-192.168.11.140}" # Forge sends POST to this URL; proxy adds module/action and forwards to Blockscout VERIFIER_URL="${FORGE_VERIFIER_URL:-http://127.0.0.1:3080/api}" # Parse --only and --skip ONLY_LIST="" SKIP_LIST="" while [[ $# -gt 0 ]]; do case "$1" in --only) ONLY_LIST="${2:-}"; shift 2 ;; --skip) SKIP_LIST="${2:-}"; shift 2 ;; *) shift ;; esac done should_verify() { local name="$1" [[ -n "$ONLY_LIST" ]] && [[ ",${ONLY_LIST}," != *",${name},"* ]] && return 1 [[ -n "$SKIP_LIST" ]] && [[ ",${SKIP_LIST}," = *",${name},"* ]] && return 1 return 0 } cd "$SMOM" # Returns 0 if address has non-empty runtime bytecode on RPC, 1 otherwise (or if check skipped). has_contract_bytecode() { local addr="$1" local code [[ "${VERIFY_SKIP_BYTECODE_CHECK:-0}" == "1" ]] && return 0 command -v cast >/dev/null 2>&1 || return 0 code=$(cast code "$addr" --rpc-url "$RPC" 2>/dev/null | tr -d '\n\r \t' | tr '[:upper:]' '[:lower:]') || true [[ -n "$code" && "$code" != "0x" && "$code" != "0x0" ]] } verify_one() { local addr="$1" contract="$2" path="$3" echo "Verifying $contract at $addr..." if ! has_contract_bytecode "$addr"; then echo " skip: no contract bytecode at $addr on RPC (EOA or undeployed; verify would fail in Blockscout)" return 0 fi if forge verify-contract "$addr" "$path" \ --chain-id 138 \ --verifier blockscout \ --verifier-url "$VERIFIER_URL" \ --rpc-url "$RPC" \ --flatten 2>&1; then echo " OK" else echo " (skip: may already be verified, or path/Solidity version mismatch - check contract path and compiler version)" fi } echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "Blockscout Contract Verification (Chain 138)" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" # Canonical Chain 138 addresses (prefer smom-dbis-138/.env and config; fallbacks from CONTRACT_ADDRESSES_REFERENCE / reconcile-env) ADDR_MULTICALL="${ADDR_MULTICALL:-0xF4AA429BE277d1a1a1A744C9e5B3aD821a9b96f7}" ADDR_ORACLE_AGGREGATOR="${AGGREGATOR_ADDRESS:-${ADDR_ORACLE_AGGREGATOR:-0x99b3511a2d315a497c8112c1fdd8d508d4b1e506}}" ADDR_ORACLE_PROXY="${ORACLE_PROXY:-${ADDR_ORACLE_PROXY:-0x3304b747e565a97ec8ac220b0b6a1f6ffdb837e6}}" ADDR_MULTISIG="${ADDR_MULTISIG:-0xb9E29cFa1f89d369671E640d0BB3aD94Cab43965}" ADDR_CCIP_RECEIVER="${ADDR_CCIP_RECEIVER:-0xC12236C03b28e675d376774FCE2C2C052488430F}" ADDR_VOTING="${ADDR_VOTING:-0x022267b26400114aF01BaCcb92456Fe36cfccD93}" ADDR_CCIP_SENDER="${CCIP_SENDER:-${ADDR_CCIP_SENDER:-0x105F8A15b819948a89153505762444Ee9f324684}}" ADDR_CCIPWETH10="${CCIPWETH10_BRIDGE_CHAIN138:-${ADDR_CCIPWETH10_BRIDGE:-0xe0E93247376aa097dB308B92e6Ba36bA015535D0}}" ADDR_CCIPWETH9="${CCIPWETH9_BRIDGE_CHAIN138:-${ADDR_CCIPWETH9_BRIDGE:-0x971cD9D156f193df8051E48043C476e53ECd4693}}" ADDR_WETH10="${WETH10_CHAIN138:-0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9f}" # Used early for AccessControl admin resolution (mirror admin often matches DODO admin on Chain 138). ADDR_TX_MIRROR="${TRANSACTION_MIRROR_ADDRESS:-}" should_verify WETH10 && verify_one "$ADDR_WETH10" "WETH10" "contracts/tokens/WETH10.sol:WETH10" should_verify Multicall && verify_one "$ADDR_MULTICALL" "Multicall" "contracts/utils/Multicall.sol:Multicall" should_verify Aggregator && verify_one "$ADDR_ORACLE_AGGREGATOR" "Aggregator" "contracts/oracle/Aggregator.sol:Aggregator" should_verify Proxy && verify_one "$ADDR_ORACLE_PROXY" "Proxy" "contracts/oracle/Proxy.sol:Proxy" should_verify MultiSig && verify_one "$ADDR_MULTISIG" "MultiSig" "contracts/governance/MultiSig.sol:MultiSig" should_verify CCIPReceiver && verify_one "$ADDR_CCIP_RECEIVER" "CCIPReceiver" "contracts/ccip/CCIPReceiver.sol:CCIPReceiver" should_verify Voting && verify_one "$ADDR_VOTING" "Voting" "contracts/governance/Voting.sol:Voting" should_verify CCIPSender && verify_one "$ADDR_CCIP_SENDER" "CCIPSender" "contracts/ccip/CCIPSender.sol:CCIPSender" should_verify CCIPWETH10Bridge && verify_one "$ADDR_CCIPWETH10" "CCIPWETH10Bridge" "contracts/ccip/CCIPWETH10Bridge.sol:CCIPWETH10Bridge" should_verify CCIPWETH9Bridge && verify_one "$ADDR_CCIPWETH9" "CCIPWETH9Bridge" "contracts/ccip/CCIPWETH9Bridge.sol:CCIPWETH9Bridge" if [[ -d "$ALLTRA" ]]; then ADDR_MERCHANT="${ADDR_MERCHANT_SETTLEMENT:-0x16D9A2cB94A0b92721D93db4A6Cd8023D3338800}" ADDR_ESCROW="${ADDR_WITHDRAWAL_ESCROW:-0xe77cb26eA300e2f5304b461b0EC94c8AD6A7E46D}" should_verify MerchantSettlementRegistry && { echo "Verifying MerchantSettlementRegistry at $ADDR_MERCHANT..."; (cd "$ALLTRA" && forge verify-contract "$ADDR_MERCHANT" "contracts/settlement/MerchantSettlementRegistry.sol:MerchantSettlementRegistry" --chain-id 138 --verifier blockscout --verifier-url "$VERIFIER_URL" --rpc-url "$RPC" --flatten 2>&1) && echo " OK" || echo " (skip)"; } should_verify WithdrawalEscrow && { echo "Verifying WithdrawalEscrow at $ADDR_ESCROW..."; (cd "$ALLTRA" && forge verify-contract "$ADDR_ESCROW" "contracts/settlement/WithdrawalEscrow.sol:WithdrawalEscrow" --chain-id 138 --verifier blockscout --verifier-url "$VERIFIER_URL" --rpc-url "$RPC" --flatten 2>&1) && echo " OK" || echo " (skip)"; } fi # Optional DODO PMM + TransactionMirror (addresses from smom-dbis-138/.env). # Uses explicit constructor args read from chain (immutables + AccessControl admin via hasRole). # Bytecode match: FOUNDRY_PROFILE=deploy (optimizer 100, via_ir, cancun) — override with VERIFY_CHAIN138_FOUNDRY_PROFILE=default if your deployment used the default profile instead. DEFAULT_ADMIN_ROLE_ZERO="0x0000000000000000000000000000000000000000000000000000000000000000" # Resolve DEFAULT_ADMIN_ROLE holder for AccessControl (tries env candidates, then TransactionMirror admin()). resolve_access_control_admin() { local contract="$1" local rpc="$2" local role="$DEFAULT_ADMIN_ROLE_ZERO" local cand adm hr local -a cands=() [[ -n "${DODO_INTEGRATION_ADMIN:-}" ]] && cands+=("${DODO_INTEGRATION_ADMIN}") [[ -n "${DODO_PMM_PROVIDER_ADMIN:-}" ]] && cands+=("${DODO_PMM_PROVIDER_ADMIN}") if [[ -n "${ADDR_TX_MIRROR:-}" ]]; then adm=$(cast call "${ADDR_TX_MIRROR}" "admin()(address)" --rpc-url "$rpc" 2>/dev/null | tr -d '\n\r \t') || true [[ -n "$adm" && "$adm" != "0x0000000000000000000000000000000000000000" ]] && cands+=("$adm") fi [[ -n "${GOVERNANCE_CONTROLLER:-}" ]] && cands+=("${GOVERNANCE_CONTROLLER}") [[ -n "${ADDR_MULTISIG:-}" ]] && cands+=("${ADDR_MULTISIG}") for cand in "${cands[@]}"; do [[ -z "$cand" ]] && continue hr=$(cast call "$contract" "hasRole(bytes32,address)(bool)" "$role" "$cand" --rpc-url "$rpc" 2>/dev/null | tr -d '\n\r \t') || continue if [[ "$hr" == "true" ]]; then echo "$cand" return 0 fi done return 1 } dodo_integration_constructor_hex() { local int="$1" rpc="$2" admin="$3" local dvm appr usdt usdc cusdt cusdc dvm=$(cast call "$int" "dodoVendingMachine()(address)" --rpc-url "$rpc" 2>/dev/null | tr -d '\n\r \t') || return 1 appr=$(cast call "$int" "dodoApprove()(address)" --rpc-url "$rpc" 2>/dev/null | tr -d '\n\r \t') || return 1 usdt=$(cast call "$int" "officialUSDT()(address)" --rpc-url "$rpc" 2>/dev/null | tr -d '\n\r \t') || return 1 usdc=$(cast call "$int" "officialUSDC()(address)" --rpc-url "$rpc" 2>/dev/null | tr -d '\n\r \t') || return 1 cusdt=$(cast call "$int" "compliantUSDT()(address)" --rpc-url "$rpc" 2>/dev/null | tr -d '\n\r \t') || return 1 cusdc=$(cast call "$int" "compliantUSDC()(address)" --rpc-url "$rpc" 2>/dev/null | tr -d '\n\r \t') || return 1 cast abi-encode 'constructor(address,address,address,address,address,address,address)' \ "$admin" "$dvm" "$appr" "$usdt" "$usdc" "$cusdt" "$cusdc" 2>/dev/null | tr -d '\n\r \t' } dodo_provider_constructor_hex() { local prov="$1" rpc="$2" admin="$3" local di di=$(cast call "$prov" "dodoIntegration()(address)" --rpc-url "$rpc" 2>/dev/null | tr -d '\n\r \t') || return 1 cast abi-encode 'constructor(address,address)' "$di" "$admin" 2>/dev/null | tr -d '\n\r \t' } transaction_mirror_constructor_hex() { local mir="$1" rpc="$2" local adm adm=$(cast call "$mir" "admin()(address)" --rpc-url "$rpc" 2>/dev/null | tr -d '\n\r \t') || return 1 cast abi-encode 'constructor(address)' "$adm" 2>/dev/null | tr -d '\n\r \t' } verify_one_explicit() { local addr="$1" path="$2" name="$3" ctor_hex="$4" local profile="${VERIFY_CHAIN138_FOUNDRY_PROFILE:-deploy}" echo "Verifying $name at $addr (explicit ctor, FOUNDRY_PROFILE=$profile)..." if ! has_contract_bytecode "$addr"; then echo " skip: no contract bytecode at $addr" return 0 fi if [[ -z "$ctor_hex" ]]; then echo " skip: could not build constructor args (cast/RPC failed)" return 0 fi if FOUNDRY_PROFILE="$profile" forge verify-contract "$addr" "$path" \ --chain-id 138 \ --constructor-args "$ctor_hex" \ --verifier blockscout \ --verifier-url "$VERIFIER_URL" \ --rpc-url "$RPC" \ --skip-is-verified-check 2>&1; then echo " OK" else echo " (skip: mismatch or already verified — try VERIFY_CHAIN138_FOUNDRY_PROFILE=default or confirm deploy profile in foundry.toml)" fi } ADDR_DODO_INTEGRATION="${DODO_PMM_INTEGRATION_ADDRESS:-${DODO_PMM_INTEGRATION:-${CHAIN_138_DODO_PMM_INTEGRATION:-}}}" ADDR_DODO_PROVIDER="${DODO_PMM_PROVIDER_ADDRESS:-${DODO_PMM_PROVIDER:-}}" if [[ -n "$ADDR_DODO_INTEGRATION" ]] && should_verify DODOPMMIntegration; then admin_i=$(resolve_access_control_admin "$ADDR_DODO_INTEGRATION" "$RPC" || true) if [[ -z "$admin_i" ]]; then echo "Verifying DODOPMMIntegration at $ADDR_DODO_INTEGRATION — skip: could not resolve AccessControl admin (set DODO_INTEGRATION_ADMIN or TRANSACTION_MIRROR_ADDRESS for hasRole probe)" else enc=$(dodo_integration_constructor_hex "$ADDR_DODO_INTEGRATION" "$RPC" "$admin_i" || true) verify_one_explicit "$ADDR_DODO_INTEGRATION" "contracts/dex/DODOPMMIntegration.sol:DODOPMMIntegration" "DODOPMMIntegration" "${enc:-}" fi fi if [[ -n "$ADDR_DODO_PROVIDER" ]] && should_verify DODOPMMProvider; then admin_p=$(resolve_access_control_admin "$ADDR_DODO_PROVIDER" "$RPC" || true) if [[ -z "$admin_p" ]]; then echo "Verifying DODOPMMProvider at $ADDR_DODO_PROVIDER — skip: could not resolve AccessControl admin (set DODO_PMM_PROVIDER_ADMIN or TRANSACTION_MIRROR_ADDRESS)" else enc=$(dodo_provider_constructor_hex "$ADDR_DODO_PROVIDER" "$RPC" "$admin_p" || true) verify_one_explicit "$ADDR_DODO_PROVIDER" "contracts/liquidity/providers/DODOPMMProvider.sol:DODOPMMProvider" "DODOPMMProvider" "${enc:-}" fi fi if [[ -n "$ADDR_TX_MIRROR" ]] && should_verify TransactionMirror; then enc=$(transaction_mirror_constructor_hex "$ADDR_TX_MIRROR" "$RPC" || true) verify_one_explicit "$ADDR_TX_MIRROR" "contracts/mirror/TransactionMirror.sol:TransactionMirror" "TransactionMirror" "${enc:-}" fi # CompliantFiatToken: one deployment per currency with distinct constructor args — verify per token in the Blockscout UI or add scripted entries when addresses are enumerated in env. echo "" echo "Done. Check http://${IP_BLOCKSCOUT} or https://explorer.d-bis.org for verification status."