First Flight #18: T-Swap

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

`TSwapPool::_swap` provides extra tokens each time the `swapCount` condition is met, violating the constant product invariant

Summary

The protocol adheres to the invariant x * y = k, where:
x: The pool's token balance
y: The pool's WETH balance
k: The constant product of these two balances

Whenever the pool balances change, the ratio between the token and WETH amounts must remain constant to preserve the invariant x * y = k. However, the _swap function disrupts this balance by providing extra tokens as incentives each time the swapCount condition is met. This breaks the invariant over time, leading to a potential drain of the protocol's funds.

Vulnerability Details

Proof of Concept:

  1. A user performs 10 swaps and accumulates an additional 1_000_000_000_000_000_000 tokens as an incentive.

  2. The user continues to swap repeatedly until the protocol's funds are completely drained.

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);
}

Impact

Users can exploit the protocol by performing numerous swaps to repeatedly collect the additional incentives, potentially draining the protocol's funds.

Tools Used

Manual Review

Recommendations

Remove the extra incentive mechanism. If retaining it is essential, adjust the protocol to maintain the x * y = k invariant. Alternatively, allocate a separate pool of tokens for incentives, similar to how fees are managed.

- 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.