Merge pull request 'fix: Remove recenterAccess — make recenter() public with TWAP enforcement (#706)' (#713) from fix/issue-706 into master
This commit is contained in:
commit
6ff8282a7e
19 changed files with 241 additions and 330 deletions
|
|
@ -100,50 +100,72 @@ fund_liquidity_manager() {
|
|||
"$LIQUIDITY_MANAGER" --value 10ether >>"$LOG_FILE" 2>&1
|
||||
}
|
||||
|
||||
grant_recenter_access() {
|
||||
bootstrap_log "Granting recenter access to deployer"
|
||||
cast rpc --rpc-url "$ANVIL_RPC" anvil_impersonateAccount "$FEE_DEST" >>"$LOG_FILE" 2>&1
|
||||
cast send --rpc-url "$ANVIL_RPC" --from "$FEE_DEST" --unlocked \
|
||||
"$LIQUIDITY_MANAGER" "setRecenterAccess(address)" "$DEPLOYER_ADDR" >>"$LOG_FILE" 2>&1
|
||||
cast rpc --rpc-url "$ANVIL_RPC" anvil_stopImpersonatingAccount "$FEE_DEST" >>"$LOG_FILE" 2>&1
|
||||
|
||||
if [[ -n "${TXNBOT_ADDRESS:-}" ]]; then
|
||||
bootstrap_log "Granting recenter access to txnBot ($TXNBOT_ADDRESS)"
|
||||
cast rpc --rpc-url "$ANVIL_RPC" anvil_impersonateAccount "$FEE_DEST" >>"$LOG_FILE" 2>&1
|
||||
cast send --rpc-url "$ANVIL_RPC" --from "$FEE_DEST" --unlocked \
|
||||
"$LIQUIDITY_MANAGER" "setRecenterAccess(address)" "$TXNBOT_ADDRESS" >>"$LOG_FILE" 2>&1
|
||||
cast rpc --rpc-url "$ANVIL_RPC" anvil_stopImpersonatingAccount "$FEE_DEST" >>"$LOG_FILE" 2>&1
|
||||
fi
|
||||
}
|
||||
|
||||
call_recenter() {
|
||||
local recenter_pk="$DEPLOYER_PK"
|
||||
local recenter_addr="$DEPLOYER_ADDR"
|
||||
if [[ -n "${TXNBOT_ADDRESS:-}" ]]; then
|
||||
recenter_pk="$TXNBOT_PRIVATE_KEY"
|
||||
recenter_addr="$TXNBOT_ADDRESS"
|
||||
fi
|
||||
|
||||
# If the deploy script already bootstrapped VWAP (cumulativeVolume > 0), positions
|
||||
# are in place at the post-seed-buy tick. Calling recenter() now would fail with
|
||||
# "amplitude not reached" because currentTick == anchorCenterTick. Skip it.
|
||||
bootstrap_vwap() {
|
||||
# Idempotency guard: if a previous run already bootstrapped VWAP, skip.
|
||||
local cumvol
|
||||
cumvol="$(cast call --rpc-url "$ANVIL_RPC" \
|
||||
"$LIQUIDITY_MANAGER" "cumulativeVolume()(uint256)" 2>/dev/null || echo "0")"
|
||||
# cast call with a typed (uint256) selector returns a plain decimal string for
|
||||
# non-zero values (e.g. "140734553600000") and "0" for zero. A simple != "0"
|
||||
# check is sufficient; note that the output may include a scientific-notation
|
||||
# annotation (e.g. "140734553600000 [1.407e14]") which is also != "0", so we
|
||||
# do NOT attempt to parse it further with cast to-dec (which would fail on the
|
||||
# annotation and incorrectly fall back to "0").
|
||||
if [[ "$cumvol" != "0" && -n "$cumvol" ]]; then
|
||||
bootstrap_log "VWAP already bootstrapped by deploy script (cumulativeVolume=$cumvol) -- skipping initial recenter"
|
||||
bootstrap_log "VWAP already bootstrapped (cumulativeVolume=$cumvol) -- skipping"
|
||||
return 0
|
||||
fi
|
||||
|
||||
bootstrap_log "Calling recenter() via $recenter_addr"
|
||||
local recenter_pk="${TXNBOT_PRIVATE_KEY:-$DEPLOYER_PK}"
|
||||
|
||||
# Fund LM with 1 ETH (thin bootstrap positions; 0.5 ETH seed swap moves >400 ticks)
|
||||
bootstrap_log "Funding LM with 1 ETH for VWAP bootstrap..."
|
||||
cast send --rpc-url "$ANVIL_RPC" --private-key "$DEPLOYER_PK" \
|
||||
"$LIQUIDITY_MANAGER" --value 1ether >>"$LOG_FILE" 2>&1
|
||||
|
||||
# Advance Anvil time 301s so TWAP oracle has sufficient history for _isPriceStable()
|
||||
cast rpc --rpc-url "$ANVIL_RPC" evm_increaseTime 301 >>"$LOG_FILE" 2>&1
|
||||
cast rpc --rpc-url "$ANVIL_RPC" evm_mine >>"$LOG_FILE" 2>&1
|
||||
|
||||
# First recenter: places initial bootstrap positions; no fees yet, cumulativeVolume stays 0
|
||||
bootstrap_log "First recenter (places bootstrap positions)..."
|
||||
cast send --rpc-url "$ANVIL_RPC" --private-key "$recenter_pk" \
|
||||
"$LIQUIDITY_MANAGER" "recenter()" >>"$LOG_FILE" 2>&1
|
||||
|
||||
# Seed buy: wrap 0.5 ETH to WETH and swap WETH->KRK
|
||||
# Generates a non-zero WETH fee in the anchor position and moves price >400 ticks.
|
||||
# sqrtPriceLimitX96 is direction-dependent: MIN+1 when WETH<KRK (token0), MAX-1 otherwise.
|
||||
bootstrap_log "Executing seed buy (0.5 ETH WETH->KRK)..."
|
||||
cast send --rpc-url "$ANVIL_RPC" --private-key "$DEPLOYER_PK" \
|
||||
"$WETH" "deposit()" --value 0.5ether >>"$LOG_FILE" 2>&1
|
||||
cast send --rpc-url "$ANVIL_RPC" --private-key "$DEPLOYER_PK" \
|
||||
"$WETH" "approve(address,uint256)" "$SWAP_ROUTER" "$MAX_UINT" >>"$LOG_FILE" 2>&1
|
||||
|
||||
local weth_addr kraiken_addr sqrt_limit
|
||||
weth_addr=$(echo "$WETH" | tr '[:upper:]' '[:lower:]' | sed 's/^0x//')
|
||||
kraiken_addr=$(echo "$KRAIKEN" | tr '[:upper:]' '[:lower:]' | sed 's/^0x//')
|
||||
if [[ "$weth_addr" < "$kraiken_addr" ]]; then
|
||||
sqrt_limit=4295128740 # WETH=token0, zeroForOne=true, price decreases
|
||||
else
|
||||
sqrt_limit=1461446703485210103287273052203988822378723970341 # WETH=token1, price increases
|
||||
fi
|
||||
|
||||
cast send --legacy --gas-limit 300000 --rpc-url "$ANVIL_RPC" --private-key "$DEPLOYER_PK" \
|
||||
"$SWAP_ROUTER" "exactInputSingle((address,address,uint24,address,uint256,uint256,uint160))" \
|
||||
"($WETH,$KRAIKEN,10000,$DEPLOYER_ADDR,500000000000000000,0,$sqrt_limit)" >>"$LOG_FILE" 2>&1
|
||||
|
||||
# Advance time 301s so TWAP settles at post-buy price and cooldown (60s) elapses
|
||||
cast rpc --rpc-url "$ANVIL_RPC" evm_increaseTime 301 >>"$LOG_FILE" 2>&1
|
||||
cast rpc --rpc-url "$ANVIL_RPC" evm_mine >>"$LOG_FILE" 2>&1
|
||||
|
||||
# Second recenter: cumulativeVolume==0 path fires (bootstrap), ethFee>0 -> records VWAP
|
||||
bootstrap_log "Second recenter (records VWAP)..."
|
||||
cast send --rpc-url "$ANVIL_RPC" --private-key "$recenter_pk" \
|
||||
"$LIQUIDITY_MANAGER" "recenter()" >>"$LOG_FILE" 2>&1
|
||||
|
||||
# Verify VWAP bootstrap succeeded
|
||||
cumvol="$(cast call --rpc-url "$ANVIL_RPC" \
|
||||
"$LIQUIDITY_MANAGER" "cumulativeVolume()(uint256)" 2>/dev/null || echo "0")"
|
||||
if [[ "$cumvol" == "0" || -z "$cumvol" ]]; then
|
||||
bootstrap_log "ERROR: VWAP bootstrap failed -- cumulativeVolume is 0"
|
||||
return 1
|
||||
fi
|
||||
bootstrap_log "VWAP bootstrapped (cumulativeVolume=$cumvol)"
|
||||
}
|
||||
|
||||
seed_application_state() {
|
||||
|
|
@ -174,8 +196,8 @@ seed_application_state() {
|
|||
fi
|
||||
|
||||
bootstrap_log "Swap returned 0 KRK — recentering and retrying"
|
||||
# Mine a few blocks to advance time, then recenter
|
||||
cast rpc --rpc-url "$ANVIL_RPC" evm_mine >>"$LOG_FILE" 2>&1 || true
|
||||
# Advance 61 s to clear the 60-second recenter cooldown, then mine a block.
|
||||
cast rpc --rpc-url "$ANVIL_RPC" evm_increaseTime 61 >>"$LOG_FILE" 2>&1 || true
|
||||
cast rpc --rpc-url "$ANVIL_RPC" evm_mine >>"$LOG_FILE" 2>&1 || true
|
||||
local recenter_pk="${TXNBOT_PRIVATE_KEY:-$DEPLOYER_PK}"
|
||||
cast send --rpc-url "$ANVIL_RPC" --private-key "$recenter_pk" \
|
||||
|
|
|
|||
|
|
@ -54,15 +54,12 @@ write_deployments_json "$ONCHAIN_DIR/deployments-local.json"
|
|||
echo "=== deployments-local.json written ==="
|
||||
cat "$ONCHAIN_DIR/deployments-local.json"
|
||||
|
||||
echo "=== Bootstrapping VWAP ==="
|
||||
bootstrap_vwap
|
||||
|
||||
echo "=== Funding LiquidityManager ==="
|
||||
fund_liquidity_manager
|
||||
|
||||
echo "=== Granting recenter access ==="
|
||||
grant_recenter_access
|
||||
|
||||
echo "=== Calling recenter() to seed liquidity ==="
|
||||
call_recenter
|
||||
|
||||
echo "=== Seeding application state (initial swap) ==="
|
||||
seed_application_state
|
||||
|
||||
|
|
|
|||
|
|
@ -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 (granted recenterAccess by bootstrap)
|
||||
# Account 2 — recenter caller (recenter() is permissionless; any account can call it)
|
||||
RECENTER_PK=0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a
|
||||
|
||||
# ── Infrastructure constants ───────────────────────────────────────────────────
|
||||
|
|
@ -119,21 +119,8 @@ 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. Grant recenterAccess FIRST (while original feeDestination is still set) ──
|
||||
FEE_DEST=$("$CAST" call "$LM" "feeDestination()(address)" --rpc-url "$RPC_URL") \
|
||||
|| die "Failed to read feeDestination() from LM"
|
||||
FEE_DEST=$(echo "$FEE_DEST" | sed 's/\[.*//;s/[[:space:]]//g')
|
||||
log "Granting recenterAccess to account 2 ($RECENTER_ADDR) via feeDestination ($FEE_DEST) ..."
|
||||
"$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"
|
||||
"$CAST" rpc --rpc-url "$RPC_URL" anvil_stopImpersonatingAccount "$FEE_DEST" \
|
||||
|| die "anvil_stopImpersonatingAccount $FEE_DEST failed"
|
||||
log " recenterAccess granted"
|
||||
|
||||
# ── 3b. Set feeDestination to LM itself (fees accrue as liquidity) ─────────────
|
||||
# ── 3a. Set feeDestination to LM itself (fees accrue as liquidity) ─────────────
|
||||
# recenter() is now permissionless — no setRecenterAccess() call needed.
|
||||
# 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
|
||||
|
|
@ -147,7 +134,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"
|
||||
|
||||
# ── 3c. Fund LM with 1000 ETH and deploy into positions via recenter ───────────
|
||||
# ── 3b. 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 ..."
|
||||
|
|
@ -177,7 +164,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, includes recenterAccess grant) ─────
|
||||
# ── 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"
|
||||
|
|
@ -422,7 +409,7 @@ CAST binary: /home/debian/.foundry/bin/cast
|
|||
### Recenter caller — Anvil account 2
|
||||
- Address: ${RECENTER_ADDR}
|
||||
- Private key: ${RECENTER_PK}
|
||||
- Has recenterAccess on LiquidityManager
|
||||
- Can call recenter() (permissionless — 60s cooldown + TWAP check enforced)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -449,7 +436,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
|
||||
Only recenterAccess account can call it.
|
||||
recenter() is permissionless — any account can call it (subject to 60s cooldown and TWAP check).
|
||||
|
||||
### Staking
|
||||
\`Stake.snatch(assets, receiver, taxRateIndex, positionsToSnatch)\`
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue