Caviar contest - minhtrng'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: 42/103

Findings: 2

Award: $86.20

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

40.2564 USDC - $40.26

Labels

bug
3 (High Risk)
satisfactory
upgraded by judge
duplicate-376

External Links

Lines of code

https://github.com/code-423n4/2022-12-caviar/blob/0212f9dc3b6a418803dbfacda0e340e059b8aae2/src/Pair.sol#L421-L423 https://github.com/code-423n4/2022-12-caviar/blob/0212f9dc3b6a418803dbfacda0e340e059b8aae2/src/Pair.sol#L77-L95

Vulnerability details

Impact

One of the assets (either baseTokens or fractionalTokens) will likely be overpaid when the user calls Pair.add liquidity.

Proof of Concept

The Pair.add function takes baseTokenAmount, fractionalTokenAmount and minLpTokenAmount as inputs. The first two parameters are pulled in from the user (or in the case of Ether as baseToken have to be sent with the transaction), while the latter is used as slippage control:

function addQuote(uint256 baseTokenAmount, uint256 fractionalTokenAmount) public view returns (uint256) {
    uint256 lpTokenSupply = lpToken.totalSupply();
    if (lpTokenSupply > 0) {
        // calculate amount of lp tokens as a fraction of existing reserves
        uint256 baseTokenShare = (baseTokenAmount * lpTokenSupply) / baseTokenReserves();
        uint256 fractionalTokenShare = (fractionalTokenAmount * lpTokenSupply) / fractionalTokenReserves();
        return Math.min(baseTokenShare, fractionalTokenShare);

The addQuote function returns the amount of lp tokens minted, as a minimum of the baseTokenShare and fractionalTokenShare. In the add function both amounts are pulled in full though:

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

This will likely cause overpayment for one of the assets as described under #Impact, unless the user choses 0% slippage, in which case his tx will fail, if there are any swaps or liquidity changes happening before the transaction passes.

This could also possibly used in frontrunning attacks, though this has not been checked in-depth for a viable scenario due to time constraints.

Tools Used

Manual Review

  • Option 1: calculate excess tokens and send them back to the user
  • Option 2: Only pull the funds necessary (as UniswapV2 does)

#0 - Shungy

2022-12-19T20:48:02Z

#1 - iFrostizz

2022-12-19T20:52:59Z

Seems valid.

Duplicate: #90

The slippage is checked afterwards, in the add() function that is used to add liquidity. https://github.com/code-423n4/2022-12-caviar/blob/0212f9dc3b6a418803dbfacda0e340e059b8aae2/src/Pair.sol#L77 Additionally, it actually "pulls the funds necessary" because the pulled funds are those which are supplied by the user.

#2 - Minh-Trng

2022-12-19T21:01:40Z

Seems valid. Duplicate: #90

The slippage is checked afterwards, in the add() function that is used to add liquidity. https://github.com/code-423n4/2022-12-caviar/blob/0212f9dc3b6a418803dbfacda0e340e059b8aae2/src/Pair.sol#L77 Additionally, it actually "pulls the funds necessary" because the pulled funds are those which are supplied by the user.

It will pull more than necessary if baseTokenShare and fractionalTokenShare have different values. The slippage check does not mitigate that

#3 - Shungy

2022-12-19T21:02:01Z

@iFrostizz , nope, this is not about slippage per se. Please read this report and report #90 carefully.

#4 - c4-judge

2022-12-28T11:04:19Z

berndartmueller marked the issue as duplicate of #376

#5 - c4-judge

2023-01-10T09:01:26Z

berndartmueller changed the severity to 3 (High Risk)

#6 - c4-judge

2023-01-10T09:01:30Z

berndartmueller marked the issue as satisfactory

Findings Information

Awards

45.9386 USDC - $45.94

Labels

bug
2 (Med Risk)
satisfactory
duplicate-243

External Links

Lines of code

https://github.com/code-423n4/2022-12-caviar/blob/0212f9dc3b6a418803dbfacda0e340e059b8aae2/src/Pair.sol#L399

Vulnerability details

Impact

The buyQuote is not rounded up, which can cause a leak of value, due to the buyQuote being underestimated.

Proof of Concept

The function Pair.buyQuote does not round up, which can cause the issue described under #Impact:

function buyQuote(uint256 outputAmount) public view returns (uint256) {
    return (outputAmount * 1000 * baseTokenReserves()) / ((fractionalTokenReserves() - outputAmount) * 997);
}

Compare this to the equivalent function of UniswapV2Library:

function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) internal pure returns (uint amountIn) {
    require(amountOut > 0, 'UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT');
    require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
    uint numerator = reserveIn.mul(amountOut).mul(1000);
    uint denominator = reserveOut.sub(amountOut).mul(997);

    //[warden-note]: addition of 1 here is equivalent to rounding up
    amountIn = (numerator / denominator).add(1); 
}

Tools Used

Manual Review

Add 1 in the Pair.buyQuote function to round up:

return (outputAmount * 1000 * baseTokenReserves()) / ((fractionalTokenReserves() - outputAmount) * 997) + 1;

#0 - Shungy

2022-12-20T18:11:30Z

Seems technically valid.

I think the severity is Low, because the 0.3% swap fee overcompensates for the 1 wei of rounding error.

#1 - c4-judge

2022-12-28T12:24:27Z

berndartmueller marked the issue as duplicate of #243

#2 - c4-judge

2023-01-10T09:44:47Z

berndartmueller marked the issue as satisfactory

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