code-423n4 / 2024-01-curves-findings

0 stars 0 forks source link

If a token subject sells his curves token, that last user to sell a token from that collection won't be able to #933

Open c4-bot-2 opened 8 months ago

c4-bot-2 commented 8 months ago

Lines of code

https://github.com/code-423n4/2024-01-curves/blob/main/contracts/Curves.sol#L282-L293

Vulnerability details

Impact

If a user wants to sell his curves tokens for a specific token subject he has to call the

function sellCurvesToken(address curvesTokenSubject, uint256 amount) public {
        uint256 supply = curvesTokenSupply[curvesTokenSubject];
        if (supply <= amount) revert LastTokenCannotBeSold();
        if (curvesTokenBalance[curvesTokenSubject][msg.sender] < amount) revert InsufficientBalance();

        uint256 price = getPrice(supply - amount, amount);
        // console2.log("From curves contract sellCurvesToken price: ", price);
        curvesTokenBalance[curvesTokenSubject][msg.sender] -= amount;
        curvesTokenSupply[curvesTokenSubject] = supply - amount;

        _transferFees(curvesTokenSubject, false, price, amount, supply);
}

The function checks if the supply is <= to the specified amount. When a token subject creates a curves token, he has to mint the first token, and that token is free. However the problem arises if the token subject decides to sell his token. No matter the supply a user that paid ETH for a curves token should always be able to sell his token for some amount. As shown in the below POC if the token subject sells his token, the last user to sell won't be able to.

Proof of Concept

Gist After following the steps in the above gist, add the following test in the AuditorTests.t.sol:

    function test_UserCantSellLastToken() public {
        vm.startPrank(alice);
        curves.buyCurvesToken(alice, 1); 
        vm.stopPrank();

        vm.startPrank(bob);
        uint256 bobAmount = curves.getBuyPriceAfterFee(alice, 1);
        vm.deal(bob, bobAmount);
        curves.buyCurvesToken{value:bobAmount}(alice, 1);
        vm.stopPrank();

        vm.startPrank(alice);
        uint256 lastTokenAmount = curves.getSellPriceAfterFee(alice, 1);
        console2.log("The minimum amount for which a user that bought a token should be able to sell it: ", lastTokenAmount);
        curves.sellCurvesToken(alice, 1);
        vm.stopPrank();

        vm.startPrank(bob);
        vm.expectRevert(abi.encodeWithSelector(CurvesErrors.LastTokenCannotBeSold.selector));
        curves.sellCurvesToken(alice, 1);
        vm.stopPrank();
}
Logs:
  The minimum amount for which a user that bought a token should be able to sell it:  46875000000000

To run the test use: forge test -vvv --mt test_UserCantSellLastToken

As can be seen form the logs with the fees used to set up the Curves.sol contract as shown in the gist above, the minimum amount for which a user that bough a token should be able to sell it is 468750000000000,000046875ETH

Tools Used

Manual review & Foundry

Recommended Mitigation Steps

Consider not allowing the token subject to sell his token.

Assessed type

Context

c4-pre-sort commented 7 months ago

raymondfam marked the issue as insufficient quality report

c4-pre-sort commented 7 months ago

raymondfam marked the issue as duplicate of #22

c4-judge commented 7 months ago

alcueca changed the severity to QA (Quality Assurance)

alcueca commented 7 months ago

Limited impact, can sell all minus one wei.

c4-judge commented 7 months ago

alcueca marked the issue as grade-a

c4-judge commented 7 months ago

This previously downgraded issue has been upgraded by alcueca

c4-judge commented 7 months ago

alcueca marked the issue as satisfactory

c4-judge commented 7 months ago

alcueca changed the severity to QA (Quality Assurance)

c4-judge commented 7 months ago

alcueca marked the issue as grade-b

AtanasDimulski commented 7 months ago

@alcueca I would like to ask why this issue is graded as grade-b when the primary is grade-a. This issue clearly describes the vulnerability and has a provided POC. Thank you for your time.