First Flight #18: T-Swap

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

Extra Tokens in `TSwapPool::_swap` Break Protocol Invariant of x * y = k

Summary

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

Vulnerability Details

The protocol maintains a strict invariant of x * y = k, where:

x is the balance of the pool token.
y is the balance of WETH.
k is a constant representing the product of the two balances.
This invariant ensures that the ratio between the pool token and WETH remains constant. However, the _swap function currently includes an extra token incentive that disrupts this invariant. Specifically, after every swapCount swaps, additional tokens are transferred to the user, breaking the balance and potentially draining the protocol's funds over time.

The problematic code block is:

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

Impact

Users can exploit this mechanism to repeatedly swap and collect the extra incentive tokens, eventually depleting the protocol's funds. This not only breaks the core invariant of the protocol but also poses a significant financial risk.

Proof Of Code

Place the following into TSwapPool.t.sol.

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);
pool.swapExactOutput(poolToken, weth, outputWeth, uint64(block.timestamp));
pool.swapExactOutput(poolToken, weth, outputWeth, uint64(block.timestamp));
pool.swapExactOutput(poolToken, weth, outputWeth, uint64(block.timestamp));
pool.swapExactOutput(poolToken, weth, outputWeth, uint64(block.timestamp));
pool.swapExactOutput(poolToken, weth, outputWeth, uint64(block.timestamp));
pool.swapExactOutput(poolToken, weth, outputWeth, uint64(block.timestamp));
pool.swapExactOutput(poolToken, weth, outputWeth, uint64(block.timestamp));
pool.swapExactOutput(poolToken, weth, outputWeth, uint64(block.timestamp));
pool.swapExactOutput(poolToken, weth, outputWeth, uint64(block.timestamp));
int256 startingY = int256(weth.balanceOf(address(pool)));
int256 expectedDeltaY = int256(-1) * int256(outputWeth);
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

Foundry

Recommendations

Remove the extra incentive mechanism or adjust the protocol to account for these additional tokens in maintaining the x * y = k invariant. Alternatively, set aside tokens in a manner similar to how fees are handled.

- swap_count++;
- // Fee-on-transfer
- 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.