Merge pull request 'fix: bootstrap + red-team on forked networks' (#684) from fix/fitness-factory-address into master
This commit is contained in:
commit
969c910b04
5 changed files with 115 additions and 45 deletions
|
|
@ -130,7 +130,33 @@ main() {
|
|||
|
||||
bootstrap_log "Waiting for Anvil"
|
||||
wait_for_rpc
|
||||
|
||||
# Idempotency: if deployments-local.json exists and contracts have code,
|
||||
# bootstrap already ran against this Anvil instance — skip.
|
||||
local deploy_file="$ONCHAIN_DIR/deployments-local.json"
|
||||
if [[ -f "$deploy_file" ]]; then
|
||||
local krk_addr
|
||||
krk_addr=$(jq -r '.contracts.Kraiken // empty' "$deploy_file" 2>/dev/null || true)
|
||||
if [[ -n "$krk_addr" ]]; then
|
||||
local code
|
||||
code=$(cast call --rpc-url "$ANVIL_RPC" "$krk_addr" "decimals()(uint8)" 2>/dev/null || true)
|
||||
if [[ -n "$code" && "$code" != "0x" ]]; then
|
||||
bootstrap_log "Already bootstrapped (Kraiken at $krk_addr responds) — skipping"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
maybe_set_deployer_from_mnemonic
|
||||
|
||||
# On forked networks, well-known addresses (Anvil mnemonic accounts) may
|
||||
# have code (e.g. ERC-4337 Account Abstraction proxies on Base Sepolia).
|
||||
# The feeDestination lock in LiquidityManager treats any address with code
|
||||
# as a contract and locks permanently. Strip code so they behave as EOAs.
|
||||
bootstrap_log "Clearing code from deployer + feeDest (fork safety)"
|
||||
cast rpc --rpc-url "$ANVIL_RPC" anvil_setCode "$DEPLOYER_ADDR" "0x" 2>/dev/null || true
|
||||
# feeDest = address(uint160(uint256(keccak256("harb.local.feeDest"))))
|
||||
cast rpc --rpc-url "$ANVIL_RPC" anvil_setCode "0x8A9145E1Ea4C4d7FB08cF1011c8ac1F0e10F9383" "0x" 2>/dev/null || true
|
||||
|
||||
derive_txnbot_wallet
|
||||
run_forge_script
|
||||
extract_addresses
|
||||
|
|
|
|||
|
|
@ -151,11 +151,9 @@ contract AttackRunner is Script {
|
|||
|
||||
uint24 internal constant POOL_FEE = 10_000;
|
||||
address internal constant WETH = 0x4200000000000000000000000000000000000006;
|
||||
address internal constant SWAP_ROUTER = 0x94cC0AaC535CCDB3C01d6787D6413C739ae12bc4;
|
||||
// Base mainnet NonfungiblePositionManager — https://basescan.org/address/0x03a520B32c04bf3beef7BEb72E919cF822Ed34F3
|
||||
address internal constant NPM_ADDR = 0x03a520B32c04bf3beef7BEb72E919cF822Ed34F3;
|
||||
// Base mainnet Uniswap V3 Factory — https://basescan.org/address/0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24
|
||||
address internal constant V3_FACTORY = 0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24;
|
||||
address internal constant SWAP_ROUTER = 0x2626664c2603336E57B271c5C0b26F421741e481;
|
||||
address internal constant NPM_ADDR = 0x03a520b32C04BF3bEEf7BEb72E919cf822Ed34f1;
|
||||
address internal constant V3_FACTORY = 0x33128a8fC17869897dcE68Ed026d694621f6FDfD; // Base mainnet
|
||||
|
||||
// ─── Anvil test accounts ──────────────────────────────────────────────────
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
{"op":"buy","amount":"31900000000000000000"}
|
||||
{"op":"recenter"}
|
||||
{"op":"sell","amount":"all","token":"KRK"}
|
||||
65
scripts/harb-evaluator/bootstrap-light.sh
Executable file
65
scripts/harb-evaluator/bootstrap-light.sh
Executable file
|
|
@ -0,0 +1,65 @@
|
|||
#!/usr/bin/env bash
|
||||
# Lightweight bootstrap for red-team / evaluator use.
|
||||
# Starts only Anvil + deploys contracts. No ponder, no webapp, no txnbot.
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
ONCHAIN_DIR="$REPO_ROOT/onchain"
|
||||
RPC_URL="http://localhost:8545"
|
||||
CAST="$HOME/.foundry/bin/cast"
|
||||
FORGE="$HOME/.foundry/bin/forge"
|
||||
|
||||
log() { echo "[bootstrap-light] $*"; }
|
||||
die() { log "ERROR: $*" >&2; exit 1; }
|
||||
|
||||
# 1. Start Anvil (docker)
|
||||
log "Starting Anvil..."
|
||||
cd "$REPO_ROOT"
|
||||
sudo docker compose down -v 2>/dev/null || true
|
||||
sudo docker compose up -d anvil
|
||||
for i in $(seq 1 30); do
|
||||
$CAST chain-id --rpc-url "$RPC_URL" 2>/dev/null && break
|
||||
sleep 1
|
||||
done
|
||||
$CAST chain-id --rpc-url "$RPC_URL" >/dev/null 2>&1 || die "Anvil not responding"
|
||||
log "Anvil running"
|
||||
|
||||
# 2. Clear ERC-4337 code from well-known addresses (fork safety)
|
||||
DEPLOYER=$($CAST wallet address --mnemonic "test test test test test test test test test test test junk" 2>/dev/null)
|
||||
log "Clearing code from deployer ($DEPLOYER) + feeDest"
|
||||
$CAST rpc --rpc-url "$RPC_URL" anvil_setCode "$DEPLOYER" "0x" 2>/dev/null || true
|
||||
$CAST rpc --rpc-url "$RPC_URL" anvil_setCode "0x8A9145E1Ea4C4d7FB08cF1011c8ac1F0e10F9383" "0x" 2>/dev/null || true
|
||||
|
||||
# 3. Deploy contracts — capture output for addresses
|
||||
log "Deploying contracts..."
|
||||
cd "$ONCHAIN_DIR"
|
||||
# Fix ownership of forge artifacts (docker creates root-owned files)
|
||||
sudo chown -R "$(id -u):$(id -g)" cache out broadcast 2>/dev/null || true
|
||||
rm -f deployments-local.json # force fresh
|
||||
DEPLOY_OUT=$($FORGE script script/DeployLocal.sol --rpc-url "$RPC_URL" --broadcast 2>&1)
|
||||
echo "$DEPLOY_OUT" | grep -E "^\[|deployed|complete|Summary" || true
|
||||
|
||||
# 4. Extract addresses from output and write deployments-local.json
|
||||
KRK=$(echo "$DEPLOY_OUT" | grep -oP 'Kraiken deployed: \K0x[a-fA-F0-9]+')
|
||||
STAKE=$(echo "$DEPLOY_OUT" | grep -oP 'Stake deployed: \K0x[a-fA-F0-9]+')
|
||||
OPT=$(echo "$DEPLOY_OUT" | grep -oP 'Optimizer deployed: \K0x[a-fA-F0-9]+')
|
||||
LM=$(echo "$DEPLOY_OUT" | grep -oP 'LiquidityManager deployed: \K0x[a-fA-F0-9]+')
|
||||
|
||||
[[ -n "$LM" ]] || die "Could not extract LiquidityManager address from deploy output"
|
||||
|
||||
cat > "$ONCHAIN_DIR/deployments-local.json" << EOF
|
||||
{
|
||||
"contracts": {
|
||||
"Kraiken": "$KRK",
|
||||
"Stake": "$STAKE",
|
||||
"LiquidityManager": "$LM",
|
||||
"OptimizerProxy": "$OPT"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# 5. Verify
|
||||
VWAP=$($CAST call --rpc-url "$RPC_URL" "$LM" "cumulativeVolume()(uint256)" 2>/dev/null || echo "0")
|
||||
log "LiquidityManager: $LM"
|
||||
log "cumulativeVolume: $VWAP"
|
||||
[[ "$VWAP" != "0" ]] && log "✅ Bootstrap complete — VWAP active" || log "⚠️ VWAP not bootstrapped"
|
||||
|
|
@ -33,7 +33,7 @@ DEPLOYMENTS="$REPO_ROOT/onchain/deployments-local.json"
|
|||
# ── Anvil accounts ─────────────────────────────────────────────────────────────
|
||||
# Account 8 — adversary (10k ETH, 0 KRK)
|
||||
ADV_PK=0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97
|
||||
# Account 2 — recenter caller (recenter() is permissionless; any account can call it)
|
||||
# Account 2 — recenter caller (recenter is public, any account can call)
|
||||
RECENTER_PK=0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a
|
||||
|
||||
# ── Infrastructure constants ───────────────────────────────────────────────────
|
||||
|
|
@ -55,39 +55,14 @@ command -v claude &>/dev/null || die "claude CLI not found (install: npm i -g
|
|||
command -v python3 &>/dev/null || die "python3 not found"
|
||||
command -v jq &>/dev/null || die "jq not found"
|
||||
|
||||
# ── 1. Fresh stack — tear down, rebuild, wait for bootstrap ────────────────────
|
||||
log "Rebuilding fresh stack ..."
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
# Free RAM: drop caches
|
||||
sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches' 2>/dev/null || true
|
||||
|
||||
# Tear down completely (volumes too — clean anvil state)
|
||||
sudo -E docker compose down -v >/dev/null 2>&1 || true
|
||||
sleep 3
|
||||
|
||||
# Bring up
|
||||
# -E preserves FORK_URL (and other env vars) across the sudo boundary so that
|
||||
# anvil-entrypoint.sh honours the caller's FORK_URL override.
|
||||
sudo -E docker compose up -d >/dev/null 2>&1 \
|
||||
|| die "docker compose up -d failed"
|
||||
|
||||
# Wait for bootstrap to complete (max 120s)
|
||||
log "Waiting for bootstrap ..."
|
||||
for i in $(seq 1 40); do
|
||||
if sudo docker logs harb-bootstrap-1 2>&1 | grep -q "Bootstrap complete"; then
|
||||
log " Bootstrap complete (${i}x3s)"
|
||||
break
|
||||
fi
|
||||
if [[ $i -eq 40 ]]; then
|
||||
die "Bootstrap did not complete within 120s"
|
||||
fi
|
||||
sleep 3
|
||||
done
|
||||
# ── 1. Fresh stack via bootstrap-light ─────────────────────────────────────────
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
log "Running bootstrap-light ..."
|
||||
bash "$SCRIPT_DIR/bootstrap-light.sh" || die "bootstrap-light failed"
|
||||
|
||||
# Verify Anvil responds
|
||||
"$CAST" chain-id --rpc-url "$RPC_URL" >/dev/null 2>&1 \
|
||||
|| die "Anvil not accessible at $RPC_URL after stack start"
|
||||
|| die "Anvil not accessible at $RPC_URL after bootstrap-light"
|
||||
|
||||
# ── 2. Read contract addresses ─────────────────────────────────────────────────
|
||||
[[ -f "$DEPLOYMENTS" ]] || die "deployments-local.json not found at $DEPLOYMENTS (bootstrap not complete)"
|
||||
|
|
@ -119,8 +94,11 @@ POOL=$("$CAST" call "$V3_FACTORY" "getPool(address,address,uint24)(address)" \
|
|||
"$WETH" "$KRK" "$POOL_FEE" --rpc-url "$RPC_URL" | sed 's/\[.*//;s/[[:space:]]//g')
|
||||
log " Pool: $POOL"
|
||||
|
||||
# ── 3a. Set feeDestination to LM itself (fees accrue as liquidity) ─────────────
|
||||
# recenter() is now permissionless — no setRecenterAccess() call needed.
|
||||
# ── 3a. recenter() is now public (no recenterAccess needed) ──
|
||||
# Any address can call recenter() — TWAP oracle enforces safety.
|
||||
log "recenter() is public — no access grant needed"
|
||||
|
||||
# ── 3b. Set feeDestination to LM itself (fees accrue as liquidity) ─────────────
|
||||
# setFeeDestination allows repeated EOA sets; setting to a contract locks it permanently.
|
||||
# The deployer (Anvil account 0) deployed LiquidityManager and may call setFeeDestination again.
|
||||
# DEPLOYER_PK is Anvil's deterministic account-0 key — valid ONLY against a local ephemeral
|
||||
|
|
@ -134,7 +112,7 @@ VERIFY=$("$CAST" call "$LM" "feeDestination()(address)" --rpc-url "$RPC_URL" | s
|
|||
log " feeDestination set to: $VERIFY"
|
||||
[[ "${VERIFY,,}" == "${LM,,}" ]] || die "feeDestination verification failed: expected $LM, got $VERIFY"
|
||||
|
||||
# ── 3b. Fund LM with 1000 ETH and deploy into positions via recenter ───────────
|
||||
# ── 3c. Fund LM with 1000 ETH and deploy into positions via recenter ───────────
|
||||
# Send ETH as WETH (LM uses WETH internally), then recenter to deploy into positions.
|
||||
# Without recenter, the ETH sits idle and the first recenter mints massive KRK.
|
||||
log "Funding LM with 1000 ETH ..."
|
||||
|
|
@ -164,7 +142,7 @@ LM_ETH=$("$CAST" balance "$LM" --rpc-url "$RPC_URL" | sed 's/\[.*//;s/[[:space:]
|
|||
LM_WETH=$("$CAST" call "$WETH" "balanceOf(address)(uint256)" "$LM" --rpc-url "$RPC_URL" | sed 's/\[.*//;s/[[:space:]]//g')
|
||||
log " LM after recenter: ETH=$LM_ETH WETH=$LM_WETH"
|
||||
|
||||
# ── 4. Take Anvil snapshot (clean baseline) ────────────────────────────────────
|
||||
# ── 4. Take Anvil snapshot (clean baseline) ─────
|
||||
log "Taking Anvil snapshot..."
|
||||
SNAP=$("$CAST" rpc anvil_snapshot --rpc-url "$RPC_URL" | tr -d '"')
|
||||
log " Snapshot ID: $SNAP"
|
||||
|
|
@ -190,9 +168,9 @@ trap cleanup EXIT INT TERM
|
|||
# instead of multiple cast calls + Python float approximation.
|
||||
compute_lm_total_eth() {
|
||||
local output result
|
||||
output=$(LM="$LM" WETH="$WETH" POOL="$POOL" \
|
||||
/home/debian/.foundry/bin/forge script script/LmTotalEth.s.sol \
|
||||
--rpc-url "$RPC_URL" --root "$REPO_ROOT/onchain" --no-color 2>&1)
|
||||
output=$(cd "$REPO_ROOT" && LM="$LM" WETH="$WETH" POOL="$POOL" \
|
||||
"$FORGE" script onchain/script/LmTotalEth.s.sol \
|
||||
--rpc-url "$RPC_URL" --root onchain 2>&1)
|
||||
# forge script prints "== Logs ==" then " <value>" — extract the number
|
||||
result=$(echo "$output" | awk '/^== Logs ==/{getline; gsub(/^[[:space:]]+/,""); print; exit}')
|
||||
[[ -n "$result" && "$result" =~ ^[0-9]+$ ]] || die "Failed to read LM total ETH (forge output: $output)"
|
||||
|
|
@ -409,7 +387,7 @@ CAST binary: /home/debian/.foundry/bin/cast
|
|||
### Recenter caller — Anvil account 2
|
||||
- Address: ${RECENTER_ADDR}
|
||||
- Private key: ${RECENTER_PK}
|
||||
- Can call recenter() (permissionless — 60s cooldown + TWAP check enforced)
|
||||
- Can call recenter() (public, TWAP-enforced)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -436,7 +414,7 @@ to rebalance, then re-deploys positions at the current price. It:
|
|||
- Can mint NEW KRK (increasing supply → decreasing floor)
|
||||
- Can burn KRK (decreasing supply → increasing floor)
|
||||
- Moves ETH between positions
|
||||
recenter() is permissionless — any account can call it (subject to 60s cooldown and TWAP check).
|
||||
Any account can call it (public). TWAP oracle enforces safety.
|
||||
|
||||
### Staking
|
||||
\`Stake.snatch(assets, receiver, taxRateIndex, positionsToSnatch)\`
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue