Files
migration/program/ownership_fork_e2e.sh
zeekayandHanzo Dev 55b8d52b88 Sweep anvil's state dump; the harnesses only ever killed the process
Nine state dumps had accumulated under ~/.foundry/anvil/tmp — 508 GB, the
largest 305 GB, the oldest from April — and filled a 3.6 TB disk to 100%.
Every cleanup here killed ANVIL_PID and stopped there; anvil writes its
state beside the process and nothing removed it.

anvil_state_mark records the directory listing before anvil starts and
anvil_state_sweep removes what appeared, so a run cleans up exactly its
own dumps and never touches a concurrent run's. Wired into the EXIT trap
of all three harnesses.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-01 13:22:07 -07:00

133 lines
7.0 KiB
Bash

#!/usr/bin/env bash
# ownership_fork_e2e.sh — FORKED-MAINNET proof of the EXPANDED ownership state-migration.
#
# Forks live mainnet (caught-up node, max head), applies the EXACT manifest words via
# anvil_setStorageAt (simulating the fork-block StateUpgrade edit — luxd applies the same
# via stateupgrade.Configure -> state.SetState), then proves for EVERY manifest contract:
# 1. owner()/feeToSetter() now == the DAO Safe (re-owned) — was a non-Safe key before
# 2. ERC20 decimals()/totalSupply()/symbol() UNCHANGED (packed-byte + metadata
# preservation — the bricking risk)
# 3. contracts STILL FUNCTION: the Safe (new owner) can exercise owner-gated control
# (LUSD.mint / V2.setFeeTo)
# 4. NON-manifest contracts (WLUX/WETH9, a router) are byte-for-byte untouched
# 5. real balances preserved: treasury 0x9011 unchanged
# Fork only. Mainnet is NEVER written. Manifest is the single source of truth (FRAG).
# Portable: bash 3.2 (no mapfile / no associative arrays).
set -uo pipefail
export FOUNDRY_DISABLE_NIGHTLY_WARNING=1
PORT="${PORT:-8546}"; FRPC="http://127.0.0.1:$PORT"
CTX=do-sfo3-lux-k8s
SAFE="${SAFE:-0xE54cAf7E0C04E0eC69BE9302B97613De89745DDC}"
FRAG="${FRAG:-$(cd "$(dirname "$0")/../manifests" && pwd)/96369-ownership.stateupgrade.json}"
TMP="$(mktemp -d)"
# the 3 V2 factories use feeToSetter() as the owner-getter; everything else uses owner()
V2FACS="d173926a10a0c4ecd3a51b1422270b65df0551c1 eac0a50112b5ee20cc18e42ba4d37777012afd0d aa6a41cacb18bed5b98059a5fa30f9dbabe0cc64"
PASS=0; FAIL=0
ok(){ echo " PASS: $1"; PASS=$((PASS+1)); }
no(){ echo " FAIL: $1"; FAIL=$((FAIL+1)); }
low(){ echo "$1" | tr 'A-F' 'a-f'; }
eq(){ [ "$(low "$2")" = "$(low "$3")" ] && ok "$1" || no "$1 ($2 != $3)"; }
cleanup(){ kill "${ANVIL_PID:-0}" 2>/dev/null; rm -rf "$TMP"; anvil_state_sweep; }
trap cleanup EXIT
getter_for(){ case " $V2FACS " in *" $(low "${1#0x}") "*) echo 'feeToSetter()(address)';; *) echo 'owner()(address)';; esac; }
echo "0. select caught-up mainnet node"
BEST=""; BESTH=0
if [ -n "${FORK_RPC:-}" ]; then
BEST="$FORK_RPC"; BESTH=$(cast block-number --rpc-url "$BEST" 2>/dev/null || echo 0)
else
for n in 0 1 2 3 4; do
ip=$(kubectl --context $CTX -n lux-mainnet get svc "luxd-$n" -o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>/dev/null)
[ -z "$ip" ] && continue
h=$(cast block-number --rpc-url "http://$ip:9630/v1/chain/C/rpc" 2>/dev/null) || continue
[ "$h" -gt "$BESTH" ] 2>/dev/null && { BESTH=$h; BEST="http://$ip:9630/v1/chain/C/rpc"; }
done
fi
[ -n "$BEST" ] || { echo "ABORT: no reachable node"; exit 1; }
echo " fork base head=$BESTH $BEST"
echo " manifest: $FRAG"
echo "1. start anvil fork at head"
pkill -f "anvil.*$PORT" 2>/dev/null; sleep 1
anvil_state_mark
anvil --fork-url "$BEST" --port "$PORT" --silent >/tmp/own_fork_anvil.log 2>&1 & ANVIL_PID=$!
for i in $(seq 1 40); do cast chain-id --rpc-url "$FRPC" >/dev/null 2>&1 && break; sleep 1; done
eq "fork chainId" "$(cast chain-id --rpc-url "$FRPC")" "96369"
# --- parse manifest into a triples file: addr slot newval ---
python3 - "$FRAG" > "$TMP/triples.txt" <<'PY'
import json,sys
frag=json.load(open(sys.argv[1]))
for up in frag:
for addr,acc in up["accounts"].items():
for slot,val in acc["storage"].items():
print(addr,slot,val)
PY
NEDIT=$(wc -l < "$TMP/triples.txt" | tr -d ' ')
cut -d' ' -f1 "$TMP/triples.txt" | sort -u > "$TMP/addrs.txt"
NADDR=$(wc -l < "$TMP/addrs.txt" | tr -d ' ')
echo " parsed $NEDIT storage edits across $NADDR contracts"
WETH=0x4888E4a2Ee0F03051c72d2bD3acf755Ed3498b3E # NON-manifest (immutable WETH9/WLUX, no owner)
ROUTER=0xAe2cf1E403aAFE6C05A5b8Ef63EB19ba591d8511 # NON-manifest V2 router (immutable)
TREAS=0x9011E888251AB053B7bD1cdB598Db4f9DEd94714
echo "2. PRE snapshot — every manifest contract owned by a NON-Safe key; token metadata"
while read -r a; do
g=$(getter_for "$a")
pre=$(cast call "$a" "$g" --rpc-url "$FRPC" 2>/dev/null)
[ "$(low "$pre")" != "$(low "$SAFE")" ] && ok "PRE $a ${g%%(*} != Safe (now $pre)" || no "PRE $a already Safe?!"
dec=$(cast call "$a" 'decimals()(uint8)' --rpc-url "$FRPC" 2>/dev/null)
if [ -n "$dec" ]; then
echo "$dec" > "$TMP/$a.dec"
cast call "$a" 'totalSupply()(uint256)' --rpc-url "$FRPC" 2>/dev/null > "$TMP/$a.sup"
cast call "$a" 'symbol()(string)' --rpc-url "$FRPC" 2>/dev/null > "$TMP/$a.sym"
fi
done < "$TMP/addrs.txt"
WETH_CODE0=$(cast code $WETH --rpc-url $FRPC | cast keccak)
ROUTER_CODE0=$(cast code $ROUTER --rpc-url $FRPC | cast keccak)
TREAS_BAL0=$(cast balance $TREAS --rpc-url $FRPC)
echo "3. APPLY manifest words via anvil_setStorageAt (simulates the fork-block StateUpgrade)"
while read -r a s v; do
cast rpc anvil_setStorageAt "$a" "$s" "$v" --rpc-url "$FRPC" >/dev/null 2>&1 \
&& echo " set $a[$s]=$v" || echo " ERR set $a[$s]"
done < "$TMP/triples.txt"
echo "4. POST — ownership rewritten to the DAO Safe (all $NADDR contracts)"
while read -r a; do
g=$(getter_for "$a")
eq "$a ${g%%(*} == Safe" "$(cast call "$a" "$g" --rpc-url "$FRPC")" "$SAFE"
done < "$TMP/addrs.txt"
echo "5. POST — token decimals/supply/symbol preserved (bricking check)"
while read -r a; do
[ -f "$TMP/$a.dec" ] || continue
eq "$a decimals preserved" "$(cast call "$a" 'decimals()(uint8)' --rpc-url "$FRPC")" "$(cat "$TMP/$a.dec")"
eq "$a totalSupply preserved" "$(cast call "$a" 'totalSupply()(uint256)' --rpc-url "$FRPC")" "$(cat "$TMP/$a.sup")"
eq "$a symbol preserved" "$(cast call "$a" 'symbol()(string)' --rpc-url "$FRPC")" "$(cat "$TMP/$a.sym")"
done < "$TMP/addrs.txt"
echo "6. POST — contracts STILL FUNCTION (control transferred to Safe)"
LUSD=0x848Cff46eb323f323b6Bbe1Df274E40793d7f2c2
V2=0xD173926A10A0C4eCd3A51B1422270b65Df0551c1
cast rpc anvil_impersonateAccount $SAFE --rpc-url $FRPC >/dev/null 2>&1
cast rpc anvil_setBalance $SAFE 0xde0b6b3a7640000 --rpc-url $FRPC >/dev/null 2>&1
B0=$(cast call $LUSD 'balanceOf(address)(uint256)' $SAFE --rpc-url $FRPC 2>/dev/null)
cast send $LUSD 'mint(address,uint256)' $SAFE 1000 --from $SAFE --unlocked --rpc-url $FRPC >/dev/null 2>&1
B1=$(cast call $LUSD 'balanceOf(address)(uint256)' $SAFE --rpc-url $FRPC 2>/dev/null)
[ "$B1" != "$B0" ] && ok "LUSD.mint by new owner Safe — control transferred + code intact" \
|| echo " NOTE: LUSD.mint not exercised (selector may differ); owner==Safe already proven above"
cast send $V2 'setFeeTo(address)' $SAFE --from $SAFE --unlocked --rpc-url $FRPC >/dev/null 2>&1 \
&& eq "V2.setFeeTo by Safe -> feeTo==Safe" "$(cast call $V2 'feeTo()(address)' --rpc-url $FRPC)" "$SAFE" \
|| no "V2.setFeeTo by new feeToSetter"
echo "7. POST — non-manifest contracts UNTOUCHED + treasury balance preserved"
eq "WLUX/WETH9 (non-manifest) code unchanged" "$(cast code $WETH --rpc-url $FRPC | cast keccak)" "$WETH_CODE0"
eq "V2Router (non-manifest) code unchanged" "$(cast code $ROUTER --rpc-url $FRPC | cast keccak)" "$ROUTER_CODE0"
eq "treasury 0x9011 balance unchanged" "$(cast balance $TREAS --rpc-url $FRPC)" "$TREAS_BAL0"
echo ""
echo "================ PASS=$PASS FAIL=$FAIL ================"
[ "$FAIL" -eq 0 ] || exit 1