mirror of
https://github.com/luxfi/migration.git
synced 2026-10-30 15:25:07 +00:00
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]>
133 lines
7.0 KiB
Bash
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
|