Fix "max" liquidity crash-loop: tolerate consumed unlimited allowance - #1
Conversation
With `*_total_liquidity = "max"`, required_approvals sets the per-token required allowance to U256::MAX. The live-start preflight then only treats a token as approved when its ERC20-allowance-to-Permit2 is >= U256::MAX. But Permit2 decrements that allowance by each filled order's input, so an "unlimited" approval never stays exactly U256::MAX. After the first fill the preflight reports "missing Permit2 approvals" and the operator crash-loops, even though `stitch approve` was run and the allowance is effectively unlimited. Observed live on Base: cNGN allowance still == U256::MAX (passes), USDC allowance == U256::MAX - 50_000 after ~$0.05 of fills (fails) -> "missing Permit2 approvals for 1 token(s)". Fix: for the max sentinel (required == U256::MAX), treat any allowance above half of max as effectively unlimited. Reaching that floor would require 2^255 atomic units of fills, unreachable by any real balance; a genuinely under-approved wallet (below the floor) still gets a fresh max approval. Exact-mode coverage is unchanged. Adds a regression test using the real-world consumed-allowance scenario. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Thanks for the detailed report and candidate patch. I investigated it against the current The root cause is valid: for a I handled this in I also added regression coverage for both cases:
Verified with: cargo test --manifest-path packages/stitch-bot/Cargo.tomlThat passed locally. Closing this PR as superseded by the monorepo fix. |
What this is
A proposed fix for the operator crash-loop that happens when a side is configured with
*_total_liquidity = "max". Take it or leave it, just flagging the root cause with a candidate patch. No urgency from my end since the numeric-ceiling config is a clean workaround (see below).Symptom
On
v0.1.43, running withbuy_total_liquidity_debt = "max"(and/or the collateral equivalent) crash-loops at startup with:...even right after
stitch approvesucceeds and reports the token approved. Numeric liquidity values (e.g."12000000000") do not hit this.Root cause
required_approvalssets a"max"-liquidity token'srequiredallowance toU256::MAX(add_required_amount, theLiquidityAmount::Maxarm). The live-start preflight (unapproved_tokens->approval_action) then only counts the token as approved when the ERC20-allowance-to-Permit2 is>= U256::MAX.But Permit2 decrements that ERC20 allowance by each filled order's input amount, so an "unlimited" approval never stays exactly
U256::MAX. After the very first fill,current_allowance < U256::MAX-> preflight reports the token unapproved -> withKeepAlivethe operator crash-loops. Re-runningstitch approvetops it back toU256::MAX, but the next fill breaks it again, so it's not durable.Confirmed on Base mainnet for maker
0x4C6E…384d:U256::MAX-> passesU256::MAX - 50_000(~$0.05 consumed by fills) -> fails -> "missing Permit2 approvals for 1 token(s)"The change
In
approval_action, for the max sentinel (required == U256::MAX) treat any allowance above half of max as effectively unlimited. Falling below that floor would take 2^255 atomic units of fills, which no real balance can reach. A genuinely under-approved wallet (allowance below the floor) still gets a freshApprove(U256::MAX). Exact-mode coverage is untouched.One function changed + one regression test that encodes the real consumed-allowance scenario. Existing
max_liquidity_*tests (which assertrequired == U256::MAX) are intentionally left as-is.Notes / open questions for you
RequiredApproval.uses_max_liquidityflag in the preflight instead of keying onrequired == U256::MAX. Happy to switch it if you prefer that shape.cargo test'd. Please run CI / tests before trusting it."12000000000") makes the bot cap live depth tofunded - committedevery tick and use the full balance, with none of this fragility. That's what we're running.🤖 Generated with Claude Code