Arena: intermittent approve tx failures when CL8Y allowance already sufficient (infinite approve) #224
Labels
No labels
agent:fix_bugfix
agent:fix_conflicts
agent:fix_security
agent:gap_analysis
agent:implement
agent:open_issues
agents
agent:security_audit
agent:verify
area
timecurve
arena
arena-v2
audit
block:feature_review
block:security
bug
contracts
design
devops
docs
documentation
enhancement
epic
feature
frontend
indexer
low priority
megaeth
operations
ops
performance
QA
qa checklist
referrals
removal
security
smart-contracts
tech-debt
testing
time-arena
timecurve
visual-qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
code/yieldomega#224
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Report
On the TimeCurve Arena page (
/timecurve/arena), some wallet flows intermittently surface a failed or rejectedapprovetransaction even when the participant already granted a large / “infinite” CL8Y allowance toTimeCurve(e.g. prior opt-in unlimited approve viaCl8yTimeCurveUnlimitedApprovalFieldset, or a wallet that previously approvedtype(uint256).max).Affected surfaces (all share CL8Y approval plumbing):
TimeCurve)Investigation summary
Approval logic lives primarily in:
frontend/src/pages/timeCurveArena/useTimeCurveArenaModel.tsx—handleBuy+ensureTcAllowancefrontend/src/lib/cl8yTimeCurveApprovalPreference.ts— exact vs opt-in unlimited sizing (#143)frontend/src/lib/timeCurveKumbayaSingleTx.ts— USDM →TimeCurveBuyRouterapproveInconsistent allowance guard (likely contributor)
Buy path (
handleBuy) correctly gates approval on the target allowance amount:WarBow path (
ensureTcAllowance) uses a different predicate:Implications:
handleBuyensureTcAllowance(WarBow)allow≥needbut< need + 50 bps headroom(exact mode)allowistype(uint256).maxallow < approveAmtfalse)allow < needfalse)allowance == 0while on-chain maxSo WarBow and Buy are not using one shared helper; drift risk is higher on Arena WarBow buttons.
Hypotheses (ranked)
allowanceread (RPC fallback, account switch mid-poll, or wrong spender) → wallet shows a second approve; user rejects or simulation warns → perceived “approve failed”. With on-chain max allowance the tx is unnecessary.ensureTcAllowanceusesallow < needinstead ofallow < approveAmt(#143 / inclusion headroom family related to #82): edge cases around exact vs unlimited preference; WarBow steal with bypass sumsneed = stealBurn + bypassBurn— if allowance was sized for base steal only, approve is required (expected), but sizing should use the sameapproveAmtguard as buy.TimeCurveaddress (see #61 DeployDev implementation vs ERC-1967 proxy) while the app readsallowance(owner, proxy)→ reads0, prompts approve; rare on mainnet but possible on misconfigured forks.maxInfrom a refreshed Kumbaya quote exceeds prior finite approval — approve is required and can fail on non-standard tokens (USDT-style “must reset to 0”); less likely for devMockCL8Y, worth checking production stable metadata.Recommended fixes
ensureCl8yTimeCurveAllowance({ wagmiConfig, owner, token, timeCurve, needWei })infrontend/src/lib/cl8yTimeCurveApprovalPreference.ts(or adjacent module) used by:handleBuyensureTcAllowance/ WarBow runnersuseTimeCurveSaleSession.submitBuy(already mirrors buy guard)approveAmt = cl8yTimeCurveApprovalAmountWei(needWei, readCl8yTimeCurveUnlimitedApproval())thenif (allow < approveAmt)beforewriteContract(approve); early-return whenneedWei <= 0n.allow >= needWeiand (allow === maxUint256orallow >= approveAmt), skip approve without submitting (covers “already infinite” explicitly).{ spender: tc, allow, needWei, approveAmt, unlimitedPref }when Arena submits approve.allow = maxUint256,allow = needWeiexact,allow = needWei - 1n, unlimited pref on/off.Verification checklist
Setup
buyFeeRoutingEnabledtrue).TimeCurveproxy address from Arena protocol accordion vs wallet token approval spender.A. Already unlimited on-chain (max allowance)
yieldomega.erc20.cl8yTimeCurveUnlimited.v1 = 1).buy), no secondapprove.warbowSteal, noapprove.approve.B. Exact allowance sufficient for next action
C. ETH / USDM buy legs
allowance < maxIn.buyViaKumbayawhen configured: no redundant stable approve.D. Regression / failure capture
allowance(owner, TimeCurve)andallowance(owner, router)via explorer/cast, pay mode, and CTA (buy / steal / guard / revenge).chainIdmatchesVITE_CHAIN_ID(#95).E. Automated
npm test -- cl8yTimeCurveApprovalPreference(existing) + new helper tests after implementation.approvein trace (Playwright / casteth_getTransactionReceipt).Related
INV-ERC20-APPROVAL-143indocs/testing/invariants-and-business-logic.mdmentioned in commit
f61affdfacImplementation (pushed to
main@f61affd)Root cause: Arena WarBow (
ensureTcAllowance) gatedapproveonallow < needwhile buy paths usedallow < approveAmt(cl8yTimeCurveApprovalAmountWei+ 50 bps headroom / unlimited pref). That drift caused redundantapprovewallet prompts when on-chain allowance was alreadytype(uint256).maxor otherwise covered the sized target.Fix: Shared
ensureCl8yTimeCurveAllowance+ pureplanCl8yTimeCurveApproveused by ArenahandleBuy, WarBow steal/guard/revenge, and SimplesubmitBuy. Submitapprove(TimeCurve, …)only whenallowance < approveAmt. Kumbaya buy debug logscl8y-timecurve-approve:skip/:submitwhen enabled.Docs / invariants:
INV-FRONTEND-224-CL8Y-APPROVE— invariants §224, wallet-connection §143, timecurve-views §224, manual QA #224, guardrails skill + skills/README.Automated:
npm test -- ensureCl8yTimeCurveAllowance cl8yTimeCurveApprovalPreference(10 tests) +tsc -bvia pre-commit.Verification checklist
@brouie — please verify on a live/staging wallet when you have a moment (issue stays open until confirmed):
buyViaKumbaya/ two-step buy does not re-approve stable unlessmaxIngrewallowance(owner, TimeCurve), pay mode, CTA; confirmchainId(#95) and spender is proxyThanks!
marked the checklist item Wallet with CL8Y balance on target chain; Arena sale live (buyFeeRoutingEnabled true). as completed
marked the checklist item Note TimeCurve proxy address from Arena protocol accordion vs wallet token approval spender. as completed
marked the checklist item Buy CHARM
CL8Y: only one wallet promptbuy, no second approve. as completedmarked the checklist item WarBow Guard / Revenge
when available: no approve. as completedmarked the checklist item Repeat after hard refresh (ensure no spurious approve from stale reads). as completed
marked the checklist item Approve CL8Y → TimeCurve once with unlimited checkbox enabled (yieldomega.erc20.cl8yTimeCurveUnlimited.v1 = 1). as completed
Walked verification on production. Wallet a80650 with 12.46 CL8Y, sale live, unlimited approval already on file from prior session.
A — Already unlimited on-chain:
Not walked (state unavailable):
Bottom line: A section's core paths (Buy + Guard with already-unlimited allowance) verified PASS via popup observation. The shared helper correctly skips the redundant approve.
Follow-up — managed to walk A-Steal AND A-Revenge.
Switched to cl8y bot w2 (rank #4, 14,386 BP), which puts a80650 (rank #1, 53,141 BP) inside the eligible steal band. Clicked STEAL on #1.
MetaMask popup decoded: warbowSteal selector 0x23df5215 directly on TimeCurve proxy 0x1B68b...2bF71. No approve prompt. Data payload includes the victim address embedded. Cancelled tx — PASS.
Same wallet had an open counter-hit from 29b126. Clicked Take revenge. MetaMask popup decoded: warbowRevenge selector 0xbf38cfbf directly on TimeCurve proxy. Stealer address 29b126 embedded in data. No approve prompt. Cancelled — PASS.
Full A section now covered:
All four paths go straight to their action selector when CL8Y allowance to TimeCurve is already sufficient. The shared ensureCl8yTimeCurveAllowance helper is correctly skipping the redundant approve across the entire WarBow + Buy surface. Fix landed clean.
@PlasticDigits
mentioned in issue #277