fix: address review findings in red-team.sh (#520)

- Move snapshot to after setRecenterAccess so agent reverts restore
  recenterAccess for account 2 on every retry
- Read feeDestination() dynamically from LM (removes hardcoded constant)
  and add || die guards on impersonation calls
- Add EXIT/INT/TERM cleanup trap that reverts to the baseline snapshot
- Fix agent floor-check snippet: add FEE_DEST/FEE_BAL reads so formula
  matches compute_eth_per_token (adj=s-f-k, not adj=s-k)
- Use `timeout "$CLAUDE_TIMEOUT"` to enforce wall-clock process limit
- Correct taxRateIndex range: 0-29 (30-element TAX_RATES array)
- Fix outstandingSupply() description: excludes LM-held KRK, not all KRK

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
openhands 2026-03-09 03:59:12 +00:00
parent 23d460542b
commit ea53e4cfce

View file

@ -37,7 +37,6 @@ SWAP_ROUTER=0x94cC0AaC535CCDB3C01d6787D6413C739ae12bc4
V3_FACTORY=0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24
NPM=0x27F971cb582BF9E50F397e4d29a5C7A34f11faA2
POOL_FEE=10000
FEE_DEST_CONST=0xf6a3eef9088A255c32b6aD2025f83E57291D9011
# ── Logging helpers ────────────────────────────────────────────────────────────
log() { echo "[red-team] $*"; }
@ -84,20 +83,37 @@ POOL=$("$CAST" call "$V3_FACTORY" "getPool(address,address,uint24)(address)" \
"$WETH" "$KRK" "$POOL_FEE" --rpc-url "$RPC_URL")
log " Pool: $POOL"
# ── 3. Take Anvil snapshot (clean baseline) ────────────────────────────────────
# ── 3. Grant recenterAccess to account 2 ──────────────────────────────────────
# Done BEFORE the snapshot so every revert restores account 2's access.
# LM.recenterAccess is a single address slot — replace it with account 2.
# Only the feeDestination is authorised to call setRecenterAccess().
log "Granting recenterAccess to account 2 ($RECENTER_ADDR) ..."
FEE_DEST=$("$CAST" call "$LM" "feeDestination()(address)" --rpc-url "$RPC_URL") \
|| die "Failed to read feeDestination() from LM"
FEE_DEST=$(echo "$FEE_DEST" | tr -d '[:space:]')
"$CAST" rpc --rpc-url "$RPC_URL" anvil_impersonateAccount "$FEE_DEST" \
|| die "anvil_impersonateAccount $FEE_DEST failed"
"$CAST" send --rpc-url "$RPC_URL" --from "$FEE_DEST" --unlocked \
"$LM" "setRecenterAccess(address)" "$RECENTER_ADDR" >/dev/null 2>&1 \
|| die "setRecenterAccess($RECENTER_ADDR) failed — check that feeDestination is correct"
"$CAST" rpc --rpc-url "$RPC_URL" anvil_stopImpersonatingAccount "$FEE_DEST" \
|| die "anvil_stopImpersonatingAccount $FEE_DEST failed"
log " recenterAccess granted"
# ── 4. Take Anvil snapshot (clean baseline, includes recenterAccess grant) ─────
log "Taking Anvil snapshot..."
SNAP=$("$CAST" rpc anvil_snapshot --rpc-url "$RPC_URL" | tr -d '"')
log " Snapshot ID: $SNAP"
# ── 4. Grant recenterAccess to account 2 (if not already) ─────────────────────
# LM.recenterAccess is a single slot — we replace it with account 2.
# The fee destination is the only address that can call setRecenterAccess().
log "Granting recenterAccess to account 2 ($RECENTER_ADDR) ..."
"$CAST" rpc --rpc-url "$RPC_URL" anvil_impersonateAccount "$FEE_DEST_CONST" >/dev/null 2>&1
"$CAST" send --rpc-url "$RPC_URL" --from "$FEE_DEST_CONST" --unlocked \
"$LM" "setRecenterAccess(address)" "$RECENTER_ADDR" >/dev/null 2>&1 \
|| log " WARNING: setRecenterAccess failed — account 2 may already hold access or differ"
"$CAST" rpc --rpc-url "$RPC_URL" anvil_stopImpersonatingAccount "$FEE_DEST_CONST" >/dev/null 2>&1
# Revert to the baseline snapshot on exit so subsequent runs start clean.
cleanup() {
local rc=$?
if [[ -n "${SNAP:-}" ]]; then
"$CAST" rpc anvil_revert "$SNAP" --rpc-url "$RPC_URL" >/dev/null 2>&1 || true
fi
exit $rc
}
trap cleanup EXIT INT TERM
# ── Helper: compute ethPerToken (mirrors floor.ts getEthPerToken) ──────────────
# ethPerToken = (lm_native_eth + lm_weth) * 1e18 / adjusted_outstanding_supply
@ -216,13 +232,14 @@ Only recenterAccess account can call it.
### Staking
\`Stake.snatch(assets, receiver, taxRateIndex, positionsToSnatch)\`
- taxRateIndex: 04 (index into TAX_RATES array — not a raw percentage)
- taxRateIndex: 029 (index into the 30-element TAX_RATES array — not a raw percentage)
- KRK staked is held by the Stake contract (excluded from adjusted_supply)
- KRK in Stake does NOT count against the floor denominator
### outstandingSupply() vs totalSupply()
\`KRK.outstandingSupply()\` ≈ totalSupply but counts all KRK regardless of where it is.
The floor helper subtracts Stake and feeDestination balances to get adjusted_supply.
\`KRK.outstandingSupply() = totalSupply() - balanceOf(liquidityManager)\`
LM-held KRK (in pool positions) is excluded from outstandingSupply.
The floor formula then additionally subtracts KRK at Stake and feeDestination to get adjusted_supply.
---
@ -233,8 +250,10 @@ The floor helper subtracts Stake and feeDestination balances to get adjusted_sup
LM_ETH=\$(/home/debian/.foundry/bin/cast balance ${LM} --rpc-url http://localhost:8545)
LM_WETH=\$(/home/debian/.foundry/bin/cast call ${WETH} "balanceOf(address)(uint256)" ${LM} --rpc-url http://localhost:8545)
SUPPLY=\$(/home/debian/.foundry/bin/cast call ${KRK} "outstandingSupply()(uint256)" --rpc-url http://localhost:8545)
FEE_DEST=\$(/home/debian/.foundry/bin/cast call ${LM} "feeDestination()(address)" --rpc-url http://localhost:8545)
FEE_BAL=\$(/home/debian/.foundry/bin/cast call ${KRK} "balanceOf(address)(uint256)" \$FEE_DEST --rpc-url http://localhost:8545)
STAKE_BAL=\$(/home/debian/.foundry/bin/cast call ${KRK} "balanceOf(address)(uint256)" ${STAKE} --rpc-url http://localhost:8545)
python3 -c "e=\$LM_ETH; w=\$LM_WETH; s=\$SUPPLY; k=\$STAKE_BAL; adj=s-k; print('ethPerToken:', (e+w)*10**18//adj if adj>0 else 0, 'wei/token')"
python3 -c "e=\$LM_ETH; w=\$LM_WETH; s=\$SUPPLY; f=\$FEE_BAL; k=\$STAKE_BAL; adj=s-f-k; print('ethPerToken:', (e+w)*10**18//adj if adj>0 else 0, 'wei/token')"
\`\`\`
### Wrap ETH to WETH
@ -385,7 +404,7 @@ log "Spawning Claude red-team agent (timeout: ${CLAUDE_TIMEOUT}s)..."
log " Report will be written to: $REPORT"
set +e
CLAUDE_TIMEOUT="$CLAUDE_TIMEOUT" claude -p --dangerously-skip-permissions \
timeout "$CLAUDE_TIMEOUT" claude -p --dangerously-skip-permissions \
"$PROMPT" >"$REPORT" 2>&1
AGENT_EXIT=$?
set -e