First Flight #18: T-Swap

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

The extra incentive token given upon the 10th swap found in `TSwapPool::_swap` breaks the protocol invariant of `x * y = k`

Summary

The protocol specified its core invariant x * y = k in README.md that doesn't account for any extra incentive given to encourage more trading among users. However in TSwapPool::_swap, there's an extra output token incentive given to user upon every 10th swap. This breaks the protocol invariant resulting its core invariant doesn't hold anymore.

Vulnerability Details

In TSwapPool::_swap, the function implements a transfer of extra 1e18 output token to user upon every 10th swap. However, there's no corresponding adjustment on input token to balance back its core invariant of x * y = k

function _swap(
IERC20 inputToken,
uint256 inputAmount,
IERC20 outputToken,
uint256 outputAmount
) private {
if (
_isUnknown(inputToken) ||
_isUnknown(outputToken) ||
inputToken == outputToken
) {
revert TSwapPool__InvalidToken();
}
<@@><@@> //@audit-high: violation to protocol invariant
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);
}
Proof of Concept

Add the following test to test/unit/TSwapPool.t.sol

function testProtocolInvariant(uint256 swap_iteration) 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 swapWethAmount = 1e18;
vm.startPrank(user);
poolToken.approve(address(pool), type(uint256).max);
poolToken.mint(user, 100e18);
uint256 startWethBalance = weth.balanceOf(address(pool));
// every swap before and after should just differ by the swapWethAmount
int256 deltaWethExpected = - int(swapWethAmount);
// a variable to check if the delta has changed during any swap iteration
bool isDeltaWethConsistent;
for (uint256 i = 0; i < swap_iteration; i++) {
pool.swapExactOutput(poolToken, weth, swapWethAmount, uint64(block.timestamp));
uint256 balanceWethAftOneSwap = weth.balanceOf(address(pool));
int256 deltaWethAftEachSwap = int(balanceWethAftOneSwap) - int(startWethBalance);
isDeltaWethConsistent = deltaWethExpected == deltaWethAftEachSwap ? true : false;
startWethBalance = balanceWethAftOneSwap;
console2.log("no of swap: ", i+1);
console2.log("===============");
console2.log("deltaWethAftEachSwap: ", deltaWethAftEachSwap);
console2.log("isDeltaWethConsistent: ", isDeltaWethConsistent);
assert(isDeltaWethConsistent == true);
}
vm.stopPrank();
}

The test above shows consistent delta after each swap but fails at 10th swap iteration indicating the protocol invariant is no longer hold as the amount of weth in the pool reduced with no adjustment made to the pool token to tune back the protocol invariant of x * y = k

Impact

Protocol invariant of x * y = k doesn't hold when extra incentive token given to user upon 10th swap of their protocol trading usage, affecting the protocol logic and claimed core invariant feature

Tools Used

Manual review

Recommendations

The protocol shall consider either do the corresponding adjustment to the input token amount when an extra incentive of output token is given out upon the 10th swap. Alternatively, the protocol could just discard the scheme of giving out extra token to keep the core invariant stay unchanged.

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