First Flight #18: T-Swap

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

The function `TSwapPool::getInputAmountBasedOnOutput` incorrectly calculates the return value

Description
The function TSwapPool::getInputAmountBasedOnOutput incorrectly calculates the return value, based on the description provided in the documentation.

The documentation states:

The TSwap protocol accrues fees from users who make swaps. Every swap has a 0.3 fee, represented in getInputAmountBasedOnOutput and getOutputAmountBasedOnInput. Each applies a 997 out of 1000 multiplier. That fee stays in the protocol.

The function is shown below for reference, with a suggested change.

function getInputAmountBasedOnOutput(
uint256 outputAmount,
uint256 inputReserves,
uint256 outputReserves
)
public
pure
revertIfZero(outputAmount)
revertIfZero(outputReserves)
returns (uint256 inputAmount)
{
return
- ((inputReserves * outputAmount) * 10000) /
+ ((inputReserves * outputAmount) * 1000) /
((outputReserves - outputAmount) * 997);
}

Impact

The protocol incorrectly calculates the input amount of tokens required to return the required output token.

Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import { Test, console } from "forge-std/Test.sol";
import { PoolFactory } from "../../src/PoolFactory.sol";
import { TSwapPool } from "../../src/TSwapPool.sol";
import { ERC20Mock } from "@openzeppelin/contracts/mocks/token/ERC20Mock.sol";
import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol";
import {USDCMock} from "./USDCMock.sol"; //usdc
import {WETHMock} from "./WETHMock.sol"; //weth
contract ExperimentTest is Test {
TSwapPool pool;
USDCMock poolToken;
WETHMock weth;
PoolFactory poolFactory;
address liquidityProvider = makeAddr("liquidityProvider");
address user = makeAddr("user");
address poolAddress;
function setUp() public {
poolToken = new USDCMock();
weth = new WETHMock();
poolFactory = new PoolFactory( address(weth) );
poolAddress = poolFactory.createPool( address(poolToken) );
pool = TSwapPool(poolFactory.getPool( address(poolToken) ));
}
function testGetInputAmountBasedOnOutput() public {
uint256 hundred = 1_000_000_000;
weth.mint(liquidityProvider, hundred);
poolToken.mint(liquidityProvider, hundred);
vm.startPrank(liquidityProvider);
weth.approve(address(pool), hundred);
poolToken.approve(address(pool), hundred);
pool.deposit(hundred, hundred, hundred/2, uint64(block.timestamp));
vm.stopPrank();
// give the user some tokens
poolToken.mint(user, 100);
weth.mint(user, 100);
vm.startPrank(user);
uint256 inputReserves = poolToken.balanceOf(address(pool));
uint256 outputReserves = weth.balanceOf(address(pool));
uint256 inputAmount = pool.getInputAmountBasedOnOutput(
10, // 10 pool tokens
inputReserves, // pool token
outputReserves // weth token
);
vm.stopPrank();
console.log("inputAmount ", inputAmount);
}

Result with current implemetation ((inputReserves * outputAmount) * 10000)

Ran 1 test for test/unit/ExperimentTest.t.sol:ExperimentTest
[PASS] testGetInputAmountBasedOnOutput() (gas: 290707)
Logs:
inputAmount 50
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.41ms (670.25µs CPU time)

This incorrect results requires the user to input 50 tokens to receive 10 - clearly this is incorrect for a 2:1 token ratio.

Changing the code as shown yeilds the following results

- ((inputReserves * outputAmount) * 10000) /
+ ((inputReserves * outputAmount) * 1000) /

Result with ((inputReserves * outputAmount) * 1000) - The suggested code change.

forge test --mt testGetInputAmountBasedOnOutput -vvv
[⠊] Compiling...
No files changed, compilation skipped
Ran 1 test for test/unit/ExperimentTest.t.sol:ExperimentTest
[PASS] testGetInputAmountBasedOnOutput() (gas: 290707)
Logs:
inputAmount 5
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 5.83ms (441.88µs CPU time)

This result requires the user to input 5 tokens to get 10 which is what you'd expect for a 2:1 token ratio. Input 5 to receive 10.

References

https://github.com/Cyfrin/2024-06-t-swap/blob/d1783a0ae66f4f43f47cb045e51eca822cd059be/src/TSwapPool.sol#L292

Tools Used

  • Manual review

  • Foundry

Updates

Appeal created

inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

Incorrect fee calculation in TSwapPool::getInputAmountBasedOnOutput causes protocol to take too many tokens from users, resulting in lost fees

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!