fix(swap): align flash-loan and zero-LTV guards with cow-swap-adapter sellToken#2960
Open
fix(swap): align flash-loan and zero-LTV guards with cow-swap-adapter sellToken#2960
Conversation
… sellToken For inverted swaps (DebtSwap, RepayWithCollateral) both guards were deriving the asset to check from sourceToken/destinationToken via isInvertedSwap and landing on the wrong side. The cow-swap-adapter contracts unambiguously flash-loan and withdraw _sellToken, exposed in state as sellAmountToken. Switching both guards to read sellAmountToken aligns them with the on-chain behavior and removes the inverted-swap heuristic. Also gates the liquidity guard on useFlashloan, supports all flash-loan swap types (drops the RepayWithCollateral exclusion in the dispatcher), and applies the borrow-cap clamp only for DebtSwap (the only flow that leaves the user holding new debt). Fixes the rsETH-style virtual-balance reverts on CollateralSwap and the validateHFAndLtvzero reverts on RepayWithCollateral when the user holds an LTV=0 collateral asset alongside positive-LTV collateral.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d36a6d8dc6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
📦 Next.js Bundle Analysis for aave-uiThis analysis was generated by the Next.js Bundle Analysis action. 🤖 This PR introduced no changes to the JavaScript bundle! 🙌 |
…ashloan Codex review caught that gating on state.useFlashloan disabled the guard for several flows that always flash-loan regardless of the flag: DebtSwap on both CoW (forceFlashloanFlow) and ParaSwap (DebtSwitchAdapter has no non-flashloan branch), plus ParaSwap CollateralSwap (useFlashLoan: true hardcoded). Even on non-flashloan paths the withdraw/borrow still decrements virtualUnderlyingBalance, so the same liquidity ceiling applies. Removes the gate; the check now runs for every ProtocolSwap state. Direct SwapType.Swap is still excluded by isProtocolSwapState.
|
📦 Next.js Bundle Analysis for aave-uiThis analysis was generated by the Next.js Bundle Analysis action. 🤖 This PR introduced no changes to the JavaScript bundle! 🙌 |
AGMASO
approved these changes
Apr 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Both
InsufficientLiquidityBlockingGuardandZeroLTVBlockingGuardderived the asset to check fromstate.sourceToken/state.destinationTokengated onisInvertedSwap. For inverted swaps (DebtSwap,RepayWithCollateral) the mapping was the wrong direction: the guards inspected the user's debt asset when the cow-swap-adapter actually flash-loans and withdraws the destination.The cow-swap-adapter contracts in
cow-swap-adapters/src/adapters/v3/*Adapter.solunambiguously flash-loan and withdraw_sellTokenfor every position swap. The interface already exposes that asset asstate.sellAmountTokeninuseSwapOrderAmounts. Reading it directly aligns both guards with the on-chain behavior and removes the inverted-swap heuristic.Use-case behavior — before vs. after
InsufficientLiquidityBlockingGuardSwap(no Aave)isProtocolSwapStateexcludes itsourceReserve(old debt) → typically missessellAmountTokenreserve (new debt) → blocksstate.useFlashloan)ZeroLTVBlockingGuardSwapassetLTV == 0exception)sourceToken)sellAmountToken)Real cases this fixes
virtualUnderlyingBalance(WETH)was 0.022 WETH while the adapter tried to flash-loan 1 WETH.Pool.flashLoanreverted with arithmetic underflow at the receiver-callback boundary. The previous guard only fired forDebtSwap, so the order reached the orderbook and the solver hit the panic at settlement.validateHFAndLtvzeroreverted because the withdrawn collateral has positive LTV while a zero-LTV collateral is enabled. The previous check compared against the user's debt asset (sourceToken) instead of the withdrawn collateral, so the order went out and reverted on settlement withLtvValidationFailed.Developer Notes
state.sellAmountTokenmatches_sellTokenin every cow-swap-adapter v3/v4 file. Source/destination +isInvertedSwapis no longer needed for these guards.formattedAvailableLiquidityis the aToken's underlyingbalanceOf, which is a strict upper bound on the pool'svirtualUnderlyingBalancefor typical reserves and catches the rsETH-style drainage cleanly. A livePool.getVirtualUnderlyingBalance(asset)read would be more precise but is out of scope for this fix.DebtSwap;CollateralSwap,RepayWithCollateral, andWithdrawAndSwaprepay the flash loan in-flight and never touch the borrow cap.state.useFlashloanbecause several protocol paths flash-loan unconditionally regardless of the flag (CoW DebtSwap / RepayWithCollateral viaforceFlashloanFlow, ParaSwap DebtSwap viaDebtSwitchAdapterwith no non-flashloan branch, ParaSwap CollateralSwap withuseFlashLoan: truehardcoded). Even non-flashloan paths still withdraw/borrow from the pool, which decrementsvirtualUnderlyingBalance— the same liquidity ceiling.Reviewer Checklist
.env.examplefile as well as the pertinent.github/actions/*filesTest plan
WETHand the action is disabled.useFlashloangate doesn't false-positive).Swap(direct DEX, no flash loan) does not trip the liquidity guard.