-
Notifications
You must be signed in to change notification settings - Fork 3
Open
Description
Summary
The UniswapV2SwapConnectors.swapExactTokensForTokens() function receives an amountOutMin parameter but completely ignores it, passing UInt256(0) to the EVM router call. This means users have no protection against price movement between quote and execution.
Severity
MEDIUM - Users can receive less than expected if pool state changes between quote and swap
Note: Flow transactions are atomic - state cannot change mid-transaction. However, this is still a bug because:
- Users typically get quotes via scripts before submitting transactions
- Pool state can change between the script call and transaction execution (other txns complete first)
- The
amountOutMinparameter exists and is computed by callers but then discarded- Behavior differs from UniswapV3SwapConnectors which correctly enforces the minimum
Location
File: cadence/contracts/connectors/evm/UniswapV2SwapConnectors.cdc
Line: 244
The Bug
access(self) fun swapExactTokensForTokens(
exactVaultIn: @{FungibleToken.Vault},
amountOutMin: UFix64, // ← Parameter is RECEIVED...
reverse: Bool
): @{FungibleToken.Vault} {
...
// perform the swap
res = self.call(to: self.routerAddress,
signature: "swapExactTokensForTokens(uint256,uint256,address[],address,uint256)",
args: [
evmAmountIn,
UInt256(0), // ← ...but IGNORED! Hardcoded to zero
(reverse ? self.addressPath.reverse() : self.addressPath),
coa.address(),
UInt256(getCurrentBlock().timestamp)
],
...
)The callers (swap() and swapBack()) properly compute amountOutMin:
// Line 168 - swap()
let amountOutMin = quote?.outAmount ?? self.quoteOut(forProvided: inVault.balance, reverse: false).outAmount
return <-self.swapExactTokensForTokens(exactVaultIn: <-inVault, amountOutMin: amountOutMin, reverse: false)
// Line 186 - swapBack()
let amountOutMin = quote?.outAmount ?? self.quoteOut(forProvided: residual.balance, reverse: true).outAmount
return <-self.swapExactTokensForTokens(exactVaultIn: <-residual, amountOutMin: amountOutMin, reverse: true)But swapExactTokensForTokens() discards this value entirely.
Impact Scenario
1. User calls script to get quote: 1000 FLOW → 800 USDC (with amountOutMin = 800)
2. User submits transaction with this quote
3. Before user's txn executes, other transactions change pool state
4. User's txn executes: amountOutMin=0 means ANY output accepted
→ User receives 700 USDC instead of transaction reverting
5. User expected their quoted minimum (800) to be enforced, but it wasn't
Comparison with UniswapV3SwapConnectors (Fixed)
The V3 connector correctly uses the parameter (after PR #82):
// UniswapV3SwapConnectors.cdc:518-522
let minOutUint = FlowEVMBridgeUtils.convertCadenceAmountToERC20Amount(
amountOutMin,
erc20Address: outToken
)
// ... then passes minOutUint to the routerSuggested Fix
access(self) fun swapExactTokensForTokens(
exactVaultIn: @{FungibleToken.Vault},
amountOutMin: UFix64,
reverse: Bool
): @{FungibleToken.Vault} {
...
// Convert amountOutMin to EVM units
let outTokenAddress = reverse ? self.addressPath[0] : self.addressPath[self.addressPath.length - 1]
let minOutUint = FlowEVMBridgeUtils.convertCadenceAmountToERC20Amount(
amountOutMin,
erc20Address: outTokenAddress
)
// perform the swap
res = self.call(to: self.routerAddress,
signature: "swapExactTokensForTokens(uint256,uint256,address[],address,uint256)",
args: [
evmAmountIn,
minOutUint, // ← Use the converted amountOutMin
(reverse ? self.addressPath.reverse() : self.addressPath),
coa.address(),
UInt256(getCurrentBlock().timestamp)
],
...
)Related
- PR fix(UniswapV3): remove hardcoded slippage from swap execution #82 fixed the equivalent issue in UniswapV3SwapConnectors
- This is the V2 counterpart that was missed
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels