#!/usr/bin/env bash # Read-only: scan deployed bytecode (Chain 138) for common flash / callback entrypoints. # Address source: config/smart-contracts-master.json (chains.138.contracts) plus optional extras. # # Usage: # RPC_URL_138=https://rpc-core.d-bis.org bash scripts/verify/sweep-flash-selectors-chain138.sh # OUT_JSON=path/to/flash_candidates.json RPC_URL_138=... bash scripts/verify/sweep-flash-selectors-chain138.sh # # Requires: cast, jq. Does not use cast selectors --resolve (no OpenChain); matches 4-byte selectors only. set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" JSON="${ROOT}/config/smart-contracts-master.json" RPC="${RPC_URL_138:-https://rpc-core.d-bis.org}" OUT_JSON="${OUT_JSON:-${ROOT}/config/flash_candidates-chain138.json}" CHAIN_KEY="${CHAIN_KEY:-138}" if ! command -v cast >/dev/null || ! command -v jq >/dev/null; then echo "ERROR: need cast and jq on PATH" >&2 exit 1 fi if [[ ! -f "$JSON" ]]; then echo "ERROR: missing $JSON" >&2 exit 1 fi # Workspace-canonical PMM addresses (may differ from smart-contracts-master.json); always merged for sweep. EXTRA_ADDRS=( "0x5BDc62f1ae7D630c37A8B363a1d49845356Ee72d" "0xff8d3b8fDF7B112759F076B69f4271D4209C0849" "0x6fc60DEDc92a2047062294488539992710b99D71" "0x9f74Be42725f2Aa072a9E0CdCce0E7203C510263" "0xBe9e0B2d4cF6A3b2994d6f2f0904D2B165eB8ffC" "0xD084b68cB4B1ef2cBA09CF99FB1B6552fd9b4859" "0x89F7a1fcbBe104BeE96Da4b4b6b7d3AF85f7E661" ) declare -A SIG_OF while IFS= read -r line; do sel="${line%%$'\t'*}" sig="${line#*$'\t'}" sel_lc=$(echo "$sel" | tr '[:upper:]' '[:lower:]') SIG_OF[$sel_lc]="$sig" done <<'EOF' 0xd0a494e4 flashLoan(uint256,uint256,address,bytes) 0xadf51de1 flashLoan(address,uint256,address,bytes) 0xf859ddd7 flashLoan(address,uint256,uint256,address,bytes) 0x5cffe9de flashLoan(address,address,uint256,bytes) 0x5c38449e flashLoan(address,address[],uint256[],bytes) 0xf740f328 flash(address,uint256,bytes) 0x10d1e85c uniswapV2Call(address,uint256,uint256,bytes) 0x244dac6e executeOperation(address,address,address,uint256,uint256,bytes) 0xfc08f9f6 executeOperation(address[],uint256[],uint256[],address,address,bytes) 0x3e0a794c operateAction(uint8,uint256,bytes) 0x2b2d17ad flashLoanSimple(address,uint256,bytes,uint16) 0x613255ab maxFlashLoan(address)(uint256) 0x88e492ab flashFees(address,address)(uint256) 0xeb2021c3 DVMFlashLoanCall(address,uint256,uint256,bytes) 0x4d7f652f DPPFlashLoanCall(address,uint256,uint256,bytes) 0x2eaeb2e6 DSPFlashLoanCall(address,uint256,uint256,bytes) EOF mapfile -t PAIRS < <( jq -r --arg c "$CHAIN_KEY" '.chains[$c].contracts // {} | to_entries[] | "\(.key)\t\(.value)"' "$JSON" ) for a in "${EXTRA_ADDRS[@]}"; do PAIRS+=("EXTRA_CANONICAL"$'\t'"$a") done declare -A SEEN_ADDR declare -a SCAN_ROWS=() declare -a CANDIDATE_JSON=() declare -a NO_CODE=() declare -a ERRORS=() cid=$(cast chain-id --rpc-url "$RPC" 2>/dev/null || echo "") for row in "${PAIRS[@]}"; do name="${row%%$'\t'*}" addr="${row#*$'\t'}" addr_lc=$(echo "$addr" | tr '[:upper:]' '[:lower:]') if [[ -n "${SEEN_ADDR[$addr_lc]:-}" ]]; then continue fi SEEN_ADDR[$addr_lc]=1 if [[ ! "$addr" =~ ^0x[0-9a-fA-F]{40}$ ]]; then ERRORS+=("{\"name\":\"$name\",\"address\":\"$addr\",\"error\":\"invalid address\"}") continue fi code=$(cast code "$addr" --rpc-url "$RPC" 2>&1) || true if [[ "$code" == "0x" || -z "$code" ]]; then NO_CODE+=("{\"name\":\"$(jq -cn --arg n "$name" '$n')\",\"address\":\"$addr\"}") continue fi if [[ "$code" == Error:* ]]; then ERRORS+=("{\"name\":\"$(jq -cn --arg n "$name" '$n')\",\"address\":\"$addr\",\"error\":$(jq -cn --arg m "$code" '$m')}") continue fi sel_text=$(cast selectors "$code" 2>/dev/null || true) matched=() matched_sigs=() while IFS= read -r sline; do sel=$(echo "$sline" | awk '{print $1}' | tr '[:upper:]' '[:lower:]') [[ "$sel" =~ ^0x[0-9a-f]{8}$ ]] || continue if [[ -n "${SIG_OF[$sel]:-}" ]]; then matched+=("$sel") matched_sigs+=("${SIG_OF[$sel]}") fi done <<< "$sel_text" if ((${#matched[@]} > 0)); then ms=$(printf '%s\n' "${matched_sigs[@]}" | jq -R . | jq -s .) mc=$(printf '%s\n' "${matched[@]}" | jq -R . | jq -s .) CANDIDATE_JSON+=("$(jq -nc \ --arg name "$name" \ --arg addr "$addr" \ --argjson ms "$ms" \ --argjson mc "$mc" \ '{name:$name,address:$addr,matchedSelectors:$mc,matchedSignatures:$ms}')") fi SCAN_ROWS+=("{\"name\":$(jq -cn --arg n "$name" '$n'),\"address\":\"$addr\",\"bytecodeHexChars\":$((${#code} - 2)),\"flashMatchCount\":${#matched[@]}}") done ts=$(date -u +%Y-%m-%dT%H:%M:%SZ) ref=$(printf '%s\n' "${!SIG_OF[@]}" | sort -u | while read -r s; do jq -nc --arg sel "$s" --arg sig "${SIG_OF[$s]}" '{selector:$sel,signature:$sig}' done | jq -s .) candidates_arr=$(printf '%s\n' "${CANDIDATE_JSON[@]}" | jq -s .) scanned_arr=$(printf '%s\n' "${SCAN_ROWS[@]}" | jq -s .) nocode_arr=$(printf '%s\n' "${NO_CODE[@]}" | jq -s .) errors_arr=$(printf '%s\n' "${ERRORS[@]}" | jq -s .) jq -nc \ --arg ts "$ts" \ --arg rpc "$RPC" \ --arg chainId "$cid" \ --argjson ref "$ref" \ --argjson candidates "$candidates_arr" \ --argjson scanned "$scanned_arr" \ --argjson noCode "$nocode_arr" \ --argjson errors "$errors_arr" \ '{ schemaVersion: 1, generatedAt: $ts, rpcUrl: $rpc, chainId: ($chainId | tonumber? // $chainId), description: "Heuristic flash/callback selector scan; false positives possible (e.g. unrelated executeOperation).", flashSelectorsReference: $ref, candidates: $candidates, scannedContracts: $scanned, emptyBytecode: $noCode, errors: $errors }' > "$OUT_JSON" echo "Wrote $OUT_JSON" echo "Candidates: $(jq '.candidates | length' "$OUT_JSON")" jq '.candidates' "$OUT_JSON"