256 lines
10 KiB
Bash
Executable File
256 lines
10 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Thirdweb / EIP-2470 singleton: simulate full calldata on VM 2103 before any broadcast.
|
|
# - eth_call -> CREATE2 precomputed address (20 bytes, left-padded in 32-byte word)
|
|
# - cast estimate -> gas (same as eth_estimateGas)
|
|
# - getCode on predicted -> should be 0x if not yet deployed
|
|
# - getTransactionCount -> for operator nonce when planning a real send
|
|
#
|
|
# Default mode is DRY-RUN only (no transaction). Use --send to broadcast (requires
|
|
# CONFIRM_BROADCAST=1 in the environment in addition to PRIVATE_KEY from dotenv).
|
|
#
|
|
# Usage (from repo root, operator LAN + load-project-env):
|
|
# source scripts/lib/load-project-env.sh
|
|
# export INPUT_FILE=/path/to/one-line-0x-hex
|
|
# ./scripts/verify/dry-run-thirdweb-singleton-2103.sh
|
|
#
|
|
# Optional: INPUT_FILE=... RPC_URL_2103=... GAS_LIM= GAS_BUMP_PCT=20 CAST_NONCE= SKIP_2103_POOL_SSH_CHECK=1
|
|
# EIP-1559 (required on 2103): cast send without fees used ~15/1 wei maxFee/priority; node often reports
|
|
# eth_gasPrice ≈ 1000 wei. Set explicitly:
|
|
# GAS_MAX_FEE_PER_GAS= (wei) GAS_PRIORITY_FEE_PER_GAS= (wei) — or we derive from baseFee + cast gas-price
|
|
# Send tuning: ETH_TIMEOUT= (sec, default 600) CAST_ASYNC=1 (return hash, no receipt wait)
|
|
# Post-mine: verify-singleton-post-deploy-2103.sh 0xPred
|
|
#
|
|
# Ref: docs/04-configuration/RPC_ENDPOINTS_MASTER.md (2103 Thirdweb admin core)
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
# shellcheck disable=SC1091
|
|
source "${PROJECT_ROOT}/scripts/lib/load-project-env.sh"
|
|
|
|
SEND=0
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--send) SEND=1; shift ;;
|
|
-h | --help)
|
|
sed -n '1,30p' "$0"
|
|
exit 0
|
|
;;
|
|
*) echo "Unknown arg: $1 (use --help)" >&2; exit 2 ;;
|
|
esac
|
|
done
|
|
|
|
if [[ "$SEND" -eq 1 && "${CONFIRM_BROADCAST:-0}" != "1" ]]; then
|
|
echo "ERROR: --send requires CONFIRM_BROADCAST=1. Run this script with no args for a dry-run only; then:" >&2
|
|
echo " CONFIRM_BROADCAST=1 $0 --send" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# VM 2103 Thirdweb admin — do not inherit generic dotenv RPC_URL (often 2101 @ .211)
|
|
RPC_2103="${RPC_URL_2103:-http://192.168.11.217:8545}"
|
|
SINGLETON="${SINGLETON:-0x4e59b44847b379578588920cA78FbF26c0B4956C}"
|
|
INPUT_FILE="${INPUT_FILE:-/tmp/thirdweb-factory-input-2103.hex}"
|
|
|
|
if [[ -z "${PRIVATE_KEY:-}" ]]; then
|
|
echo "ERROR: PRIVATE_KEY not set (e.g. from smom-dbis-138/.env via load-project-env.sh)" >&2
|
|
exit 1
|
|
fi
|
|
if ! command -v cast &>/dev/null; then
|
|
echo "ERROR: foundry 'cast' not in PATH" >&2
|
|
exit 1
|
|
fi
|
|
if [[ ! -f "$INPUT_FILE" ]]; then
|
|
echo "ERROR: INPUT_FILE not found: $INPUT_FILE" >&2
|
|
echo " Hint: run scripts/verify/decode-singleton-deploy-pending-2103.sh when pool has" >&2
|
|
echo " Thirdweb deploys, or set INPUT_FILE= to a one-line 0x… file." >&2
|
|
exit 1
|
|
fi
|
|
|
|
DATA="$(tr -d ' \n\r' < "$INPUT_FILE")"
|
|
if [[ "${#DATA}" -lt 10 || "${DATA:0:2}" != "0x" ]]; then
|
|
echo "ERROR: $INPUT_FILE must be a single line 0x-prefixed hex" >&2
|
|
exit 1
|
|
fi
|
|
|
|
FROM_ADDR="$(cast wallet address "$PRIVATE_KEY" 2>/dev/null || true)"
|
|
if [[ -z "$FROM_ADDR" || "$FROM_ADDR" == "0x0000000000000000000000000000000000000000" ]]; then
|
|
echo "ERROR: could not derive address from PRIVATE_KEY" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$SEND" -eq 1 ]]; then
|
|
echo "=== 2103 singleton broadcast (CONFIRM_BROADCAST=1) ==="
|
|
else
|
|
echo "=== 2103 singleton dry-run (no broadcast) ==="
|
|
fi
|
|
echo "RPC: $RPC_2103 (set RPC_URL_2103= to override)"
|
|
echo "Singleton: $SINGLETON"
|
|
echo "Input: $INPUT_FILE (${#DATA} hex chars = $(( ( ${#DATA} - 2) / 2 )) bytes data)"
|
|
echo "From: $FROM_ADDR (cast wallet address of PRIVATE_KEY)"
|
|
echo ""
|
|
|
|
if ! out=$(cast call -r "$RPC_2103" --from "$FROM_ADDR" "$SINGLETON" --data "$DATA" 2>&1); then
|
|
echo "eth_call (cast call) failed:" >&2
|
|
echo "$out" >&2
|
|
exit 1
|
|
fi
|
|
if [[ ${#out} -ge 66 ]]; then
|
|
PRED=0x${out: -40}
|
|
elif [[ ${#out} -eq 42 && "$out" == 0x* ]]; then
|
|
PRED="$out"
|
|
else
|
|
PRED="$out"
|
|
fi
|
|
echo "eth_call result: $out"
|
|
if [[ "$PRED" =~ ^0x[0-9a-fA-F]{40}$ ]]; then
|
|
echo "Predicted CREATE2 address: $PRED"
|
|
else
|
|
echo "WARN: could not parse 20-byte address from return (see raw above)" >&2
|
|
fi
|
|
|
|
if ! gas=$(cast estimate -r "$RPC_2103" --from "$FROM_ADDR" "$SINGLETON" "$DATA" 2>&1); then
|
|
echo "eth_estimateGas (cast estimate) failed: $gas" >&2
|
|
exit 1
|
|
fi
|
|
echo "eth_estimateGas: $gas"
|
|
if ! [[ "$gas" =~ ^[0-9]+$ ]]; then
|
|
echo "ERROR: could not parse gas estimate: $gas" >&2
|
|
exit 1
|
|
fi
|
|
GAS_BUMP_PCT="${GAS_BUMP_PCT:-20}"
|
|
if ! [[ "$GAS_BUMP_PCT" =~ ^[0-9]+$ ]]; then
|
|
echo "ERROR: GAS_BUMP_PCT must be a non-negative integer, got: $GAS_BUMP_PCT" >&2
|
|
exit 1
|
|
fi
|
|
if [[ -z "${GAS_LIM:-}" ]]; then
|
|
GAS_LIM=$((( gas * (100 + GAS_BUMP_PCT) + 99) / 100))
|
|
echo "suggested gas limit: $GAS_LIM (estimate * (100+${GAS_BUMP_PCT})/100; set GAS_LIM= to override, GAS_BUMP_PCT= to retune headroom)"
|
|
else
|
|
if ! [[ "$GAS_LIM" =~ ^[0-9]+$ ]]; then
|
|
echo "ERROR: GAS_LIM must be a non-negative integer, got: $GAS_LIM" >&2
|
|
exit 1
|
|
fi
|
|
echo "using GAS_LIM=$GAS_LIM (overrides headroom from estimate $gas)"
|
|
fi
|
|
|
|
# ---- EIP-1559 fee hint from RPC (cast send defaults are unsafe on this chain) ----
|
|
base_fee_per_gas="${BASE_FEE_PER_GAS:-$(cast block latest --field baseFeePerGas -r "$RPC_2103" 2>/dev/null || echo 0)}"
|
|
sugg_wei="${GAS_SUGGEST_WEI:-$(cast gas-price -r "$RPC_2103" 2>/dev/null || echo 0)}"
|
|
[[ "$base_fee_per_gas" =~ ^[0-9]+$ ]] || base_fee_per_gas=0
|
|
if ! [[ "$sugg_wei" =~ ^[0-9]+$ && "$sugg_wei" -ge 1 ]]; then
|
|
sugg_wei=1000
|
|
echo "WARN: could not read eth_gasPrice; using sugg_wei=$sugg_wei" >&2
|
|
fi
|
|
# Max fee: at least 1.2x suggested; override in wei with GAS_MAX_FEE_PER_GAS
|
|
if [[ -n "${GAS_MAX_FEE_PER_GAS:-}" ]]; then
|
|
if ! [[ "$GAS_MAX_FEE_PER_GAS" =~ ^[0-9]+$ ]]; then
|
|
echo "ERROR: GAS_MAX_FEE_PER_GAS must be integer wei, got: $GAS_MAX_FEE_PER_GAS" >&2
|
|
exit 1
|
|
fi
|
|
max_fee_per_gas="$GAS_MAX_FEE_PER_GAS"
|
|
else
|
|
max_fee_per_gas=$(((sugg_wei * 12 + 9) / 10))
|
|
fi
|
|
# Priority: (sugg - base), min 1; override with GAS_PRIORITY_FEE_PER_GAS
|
|
if [[ -n "${GAS_PRIORITY_FEE_PER_GAS:-}" ]]; then
|
|
if ! [[ "$GAS_PRIORITY_FEE_PER_GAS" =~ ^[0-9]+$ ]]; then
|
|
echo "ERROR: GAS_PRIORITY_FEE_PER_GAS must be integer wei, got: $GAS_PRIORITY_FEE_PER_GAS" >&2
|
|
exit 1
|
|
fi
|
|
priority_fee_per_gas="$GAS_PRIORITY_FEE_PER_GAS"
|
|
else
|
|
t=$((sugg_wei - base_fee_per_gas))
|
|
if [[ "$t" -lt 1 ]]; then t=1; fi
|
|
priority_fee_per_gas=$t
|
|
fi
|
|
need_max=$((base_fee_per_gas + priority_fee_per_gas))
|
|
if [[ "$max_fee_per_gas" -lt "$need_max" ]]; then
|
|
max_fee_per_gas=$(((need_max * 12 + 9) / 10))
|
|
fi
|
|
echo "EIP-1559 (from RPC): baseFeePerGas=$base_fee_per_gas wei eth_gasPrice(sugg)=$sugg_wei wei"
|
|
echo " would send with: --gas-price $max_fee_per_gas (maxFeePerGas wei) --priority-gas-price $priority_fee_per_gas (priority wei)"
|
|
echo " (set GAS_MAX_FEE_PER_GAS / GAS_PRIORITY_FEE_PER_GAS in wei to override; see header comment)"
|
|
|
|
if [[ -n "$PRED" && "$PRED" =~ ^0x[0-9a-fA-F]{40}$ ]]; then
|
|
c=$(cast code -r "$RPC_2103" "$PRED" 2>/dev/null || true)
|
|
if [[ -n "$c" && "$c" != "0x" && "$c" != "0x0" ]]; then
|
|
clen=$(((${#c} - 2) / 2))
|
|
echo "getCode($PRED) on 2103: $clen byte(s) ${c:0:22}…"
|
|
else
|
|
echo "getCode($PRED) on 2103: empty (not yet deployed; a send would create runtime if pool/gas allow)"
|
|
fi
|
|
fi
|
|
|
|
nonce_l=$(cast nonce -r "$RPC_2103" --block latest "$FROM_ADDR" 2>&1) || true
|
|
nonce_p=$(cast nonce -r "$RPC_2103" --block pending "$FROM_ADDR" 2>&1) || true
|
|
echo "nonce (latest): $nonce_l"
|
|
echo "nonce (pending): $nonce_p"
|
|
if [[ ! "$nonce_l" =~ ^[0-9]+$ || ! "$nonce_p" =~ ^[0-9]+$ ]]; then
|
|
echo "WARN: could not parse both nonces (RPC error?); for --send set CAST_NONCE= or fix RPC" >&2
|
|
else
|
|
if [[ "$nonce_l" -lt "$nonce_p" ]]; then
|
|
echo "NOTE: mempool in-flight (latest=$nonce_l < pending=$nonce_p) — a **new** first tx reuses/replaces"
|
|
echo " nonce $nonce_l with higher maxFee+priority, not nonce $nonce_p. Default --send will use $nonce_l unless CAST_NONCE= is set."
|
|
else
|
|
echo "next send nonce: $nonce_p (no gap between latest and pending; passed to cast send as --nonce)"
|
|
fi
|
|
fi
|
|
|
|
echo ""
|
|
if [[ "$SEND" -eq 1 ]]; then
|
|
if ! [[ "$nonce_p" =~ ^[0-9]+$ && "$nonce_l" =~ ^[0-9]+$ ]]; then
|
|
echo "ERROR: could not read both nonces (latest+pending) for $FROM_ADDR" >&2
|
|
exit 1
|
|
fi
|
|
if [[ -n "${CAST_NONCE:-}" ]]; then
|
|
if [[ ! "$CAST_NONCE" =~ ^[0-9]+$ ]]; then
|
|
echo "ERROR: CAST_NONCE must be a non-negative integer, got: $CAST_NONCE" >&2
|
|
exit 1
|
|
fi
|
|
NONCE_USE="$CAST_NONCE"
|
|
echo "using CAST_NONCE=$NONCE_USE"
|
|
elif [[ "$nonce_p" -gt "$nonce_l" ]]; then
|
|
NONCE_USE="$nonce_l"
|
|
echo "using --nonce $NONCE_USE (mempool: replace / bump same in-flight; pending counter was $nonce_p)"
|
|
else
|
|
NONCE_USE="$nonce_p"
|
|
echo "using --nonce $NONCE_USE (no extra in-flight per latest vs pending)"
|
|
fi
|
|
echo "Broadcasting: --gas-price $max_fee_per_gas --priority-gas-price $priority_fee_per_gas --nonce $NONCE_USE --gas-limit $GAS_LIM"
|
|
export ETH_TIMEOUT="${ETH_TIMEOUT:-600}"
|
|
send_args=(cast send -r "$RPC_2103" --private-key "$PRIVATE_KEY" --nonce "$NONCE_USE" --gas-limit "$GAS_LIM"
|
|
--gas-price "$max_fee_per_gas" --priority-gas-price "$priority_fee_per_gas" "$SINGLETON" "$DATA")
|
|
if [[ "${CAST_ASYNC:-0}" == "1" ]]; then
|
|
send_args+=(--async)
|
|
echo "ETH_TIMEOUT=$ETH_TIMEOUT (CAST_ASYNC=1, no wait for receipt)"
|
|
else
|
|
echo "ETH_TIMEOUT=$ETH_TIMEOUT (receipt wait; set CAST_ASYNC=1 for hash only)"
|
|
fi
|
|
if ! send_out=$("${send_args[@]}" 2>&1); then
|
|
echo "cast send failed:" >&2
|
|
echo "$send_out" >&2
|
|
exit 1
|
|
fi
|
|
echo "$send_out"
|
|
if [[ -n "$PRED" && "$PRED" =~ ^0x[0-9a-fA-F]{40}$ ]]; then
|
|
echo ""
|
|
echo "=== post-deploy (predicted address + admin hint; not the signer) ==="
|
|
if [[ -x "${SCRIPT_DIR}/verify-singleton-post-deploy-2103.sh" ]]; then
|
|
export RPC_URL_2103="$RPC_2103"
|
|
export DEPLOY_TX_SIGNER="$FROM_ADDR"
|
|
"${SCRIPT_DIR}/verify-singleton-post-deploy-2103.sh" "$PRED" || true
|
|
fi
|
|
fi
|
|
echo ""
|
|
echo "If the tx is still pending, re-run: DEPLOY_TX_SIGNER=$FROM_ADDR ${SCRIPT_DIR}/verify-singleton-post-deploy-2103.sh $PRED"
|
|
exit 0
|
|
else
|
|
echo "Dry-run complete. No transaction sent."
|
|
if [[ -z "${SKIP_2103_POOL_SSH_CHECK:-}" ]]; then
|
|
echo "Pool (live, optional): ${PROJECT_ROOT}/scripts/verify/verify-2103-besu-txpool-config.sh"
|
|
fi
|
|
echo " (or: ${PROJECT_ROOT}/scripts/maintenance/apply-2103-thirdweb-strict-tx-pool.sh --value 1 if the live LXC has 0)"
|
|
echo "To broadcast after review: CONFIRM_BROADCAST=1 $0 --send"
|
|
exit 0
|
|
fi
|