First Flight #18: T-Swap

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

ncentive mechanism in TSwapPool::_swap breaks x * y = k invariant and risks draining protocol funds

Summary

The extra tokens given to users as an incentive in the TSwapPool::_swap function disrupt the protocol's invariant of x * y = k, leading to a risk of protocol funds being drained over time.

Vulnerability Details

The TSwapPool protocol adheres to a strict invariant x * y = k, where:

x: The balance of the pool token
y: The balance of WETH
k: The constant product of the two balances

This invariant ensures that the product of the balances of pool tokens and WETH remains constant after each swap. However, in the _swap function, users receive extra tokens after every swap_count reaches SWAP_COUNT_MAX. This breaks the invariant, allowing users to drain the protocol funds by repeatedly swapping and collecting the extra tokens.

swap_count++;
if (swap_count >= SWAP_COUNT_MAX) {
swap_count = 0;
outputToken.safeTransfer(msg.sender, 1_000_000_000_000_000_000);
}

Impact

A malicious user can exploit this mechanism by performing a large number of swaps to continuously collect the extra incentive tokens, potentially draining the protocol's funds.

POC

function testInvariantBroken() public {
vm.startPrank(liquidityProvider);
weth.approve(address(pool), 100e18);
poolToken.approve(address(pool), 100e18);
pool.deposit(100e18, 100e18, 100e18, uint64(block.timestamp));
vm.stopPrank();
uint256 outputWeth = 1e17;
vm.startPrank(user);
poolToken.approve(address(pool), type(uint256).max);
poolToken.mint(user, 100e18);
vm.stopPrank();
vm.startPrank(user);
for (uint256 i = 0; i < 10; i++) {
pool.swapExactOutput(poolToken, weth, outputWeth, uint64(block.timestamp));
}
vm.stopPrank();
int256 startingY = int256(weth.balanceOf(address(pool)));
int256 expectedDeltaY = int256(-1) * int256(outputWeth);
vm.startPrank(user);
pool.swapExactOutput(poolToken, weth, outputWeth, uint64(block.timestamp));
vm.stopPrank();
uint256 endingY = weth.balanceOf(address(pool));
int256 actualDeltaY = int256(endingY) - int256(startingY);
assertEq(actualDeltaY, expectedDeltaY);
}

Tools Used

Manual code review

Recommendations

The simplest solution is to remove the extra incentive mechanism entirely. This will ensure that the protocol maintains its invariant without any additional complexities.

swap_count++;
// Remove the incentive mechanism block entirely
// if (swap_count >= SWAP_COUNT_MAX) {
// swap_count = 0;
// outputToken.safeTransfer(msg.sender, 1_000_000_000_000_000_000);
// }
Updates

Appeal created

inallhonesty Lead Judge about 1 year 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.