BUG: UniswapV2SwapConnectors ignores amountOutMin parameter
Nobody has claimed this yet.
- Dominant language
- Cadence
- Stars
- 9
- Forks
- 3
- Avg merge
- 1h 45m
- Merged PRs (30d)
- 2
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 router
Suggested 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 #82 fixed the equivalent issue in UniswapV3SwapConnectors
- This is the V2 counterpart that was missed
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in cadence/contracts/connectors/evm/UniswapV2SwapConnectors.cdc around line 244 and compare the V2 implementation with UniswapV3SwapConnectors.cdc around lines 518-522. Trace how swap() and swapBack() provide amountOutMin. Done means the V2 router call uses the converted minimum-output value instead of a hardcoded zero.
Written by the indexing model from the issue text.
Assessment
- Domain
- blockchain
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 55/100