Caviar contest - 0xmuxyz's results

A fully on-chain NFT AMM that allows you to trade every NFT in a collection.

General Information

Platform: Code4rena

Start Date: 12/12/2022

Pot Size: $36,500 USDC

Total HM: 8

Participants: 103

Period: 7 days

Judge: berndartmueller

Id: 193

League: ETH

Caviar

Findings Distribution

Researcher Performance

Rank: 30/103

Findings: 1

Award: $184.33

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: obront

Also found by: 0xmuxyz, 8olidity, CRYP70, Tricko, cozzetti, cryptostellar5, koxuan, ktg, ladboy233, yixxas

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
duplicate-141

Awards

184.3311 USDC - $184.33

External Links

Lines of code

https://github.com/masaun/2022-12-caviar/blob/main/src/Pair.sol#L63-L99 https://github.com/masaun/2022-12-caviar/blob/main/src/Pair.sol#L275-L286

Vulnerability details

Impact

In case a ERC20 token that has smaller number of decimals (eg. USDC ) is used as the baseToken , this vulnerability lead to miscalculation of LpTokens distributed. As a result, a liquidity provider receives smaller amount of LpTokens than they expected.

Proof of Concept

The number of decimals of ERC20 tokens are different depends on ERC20 token. For example, unlike WETH that is a 18 decimals token, USDC is a 6 decimals token.

In the Pair.sol, there is possibility that USDC is used as a base token .

  • According to the technical document, the things that USDC is possibly used as a base token is described like this:

    The core AMM is a stripped down version of UNI-V2 that handles swapping between a base token (such as WETH or USDC ) and a fractional NFT token.

However, there is no input validation to check the number of decimals of baseToken and there is no code in order to adjust baseTokenAmount depends on the number of decimals of baseToken on the add() and nftAdd() functions like below:

    function add(uint256 baseTokenAmount, uint256 fractionalTokenAmount, uint256 minLpTokenAmount)
        public
        payable
        returns (uint256 lpTokenAmount)
    {
        // *** Checks *** //

        // check the token amount inputs are not zero
        require(baseTokenAmount > 0 && fractionalTokenAmount > 0, "Input token amount is zero");

        // check that correct eth input was sent - if the baseToken equals address(0) then native ETH is used
        require(baseToken == address(0) ? msg.value == baseTokenAmount : msg.value == 0, "Invalid ether input");

        // calculate the lp token shares to mint
        lpTokenAmount = addQuote(baseTokenAmount, fractionalTokenAmount);

        // check that the amount of lp tokens outputted is greater than the min amount
        require(lpTokenAmount >= minLpTokenAmount, "Slippage: lp token amount out");

        // *** Effects *** //

        // transfer fractional tokens in
        _transferFrom(msg.sender, address(this), fractionalTokenAmount);

        // *** Interactions *** //

        // mint lp tokens to sender
        lpToken.mint(msg.sender, lpTokenAmount);

        // transfer base tokens in if the base token is not ETH
        if (baseToken != address(0)) {
            // transfer base tokens in
            ERC20(baseToken).safeTransferFrom(msg.sender, address(this), baseTokenAmount);
        }

        emit Add(baseTokenAmount, fractionalTokenAmount, lpTokenAmount);
    }
    function nftAdd(
        uint256 baseTokenAmount,
        uint256[] calldata tokenIds,
        uint256 minLpTokenAmount,
        bytes32[][] calldata proofs
    ) public payable returns (uint256 lpTokenAmount) {
        // wrap the incoming NFTs into fractional tokens
        uint256 fractionalTokenAmount = wrap(tokenIds, proofs);

        // add liquidity using the fractional tokens and base tokens
        lpTokenAmount = add(baseTokenAmount, fractionalTokenAmount, minLpTokenAmount);
    }

Currently, baseTokenAmount seems to be adjusted for the 18 decimal tokens only. Thus, if the 6 decimals token such as USDC is used as a baseToken , its baseTokenAmount is dealt as a 18 decimal token. Let's say a liquidity provider try to add 1 USDC as baseTokenAmount to liquidity and transfer it to the Pool. Its 1 USDC is dealt as 0.0000000000001 USDC . This lead to miscalculation of LpTokens distributed when a liquidity provider does adding liquidity. As a result, a liquidity provider above receives smaller amount of LpTokens than they expected.

Tools Used

  • Manual review
  • Consider adding the code that check the number of decimals of baseToken used.
  • Consider adding the code that makes any tokens possible to be used as the same number of decimals of the standard token (that has 18 decimals) even if smaller decimals ERC20 token (eg. USDC that is a 6 decimals token) is used as a baseToken to the first line of the add() function or the nftAdd() function.
    • For example, using _baseTokenAmount that is adjusted depends on the number of decimals of baseToken used for calculating LpToken distributed like below is one of way that I recommend.
// @dev - Calculate the difference of number of decimals between the standard token (18 decimals) and the baseToken.
uint256 differenceOfDecimals = 18 - baseToken.decimals();

// @dev - Adjust baseTokenAmount for dealing with the standard token (18 decimals)
uint256 _baseTokenAmount = baseTokenAmount * 10 ** (baseToken.decimals() + differenceOfDecimals);

#0 - c4-judge

2022-12-28T12:38:23Z

berndartmueller marked the issue as duplicate of #53

#1 - c4-judge

2023-01-10T09:31:34Z

berndartmueller changed the severity to 2 (Med Risk)

#2 - c4-judge

2023-01-10T09:31:38Z

berndartmueller marked the issue as satisfactory

#3 - C4-Staff

2023-01-25T12:23:03Z

CloudEllie marked the issue as duplicate of #141

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter