Caviar Private Pools - tsvetanovv's results

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

General Information

Platform: Code4rena

Start Date: 07/04/2023

Pot Size: $47,000 USDC

Total HM: 20

Participants: 120

Period: 6 days

Judge: GalloDaSballo

Total Solo HM: 4

Id: 230

League: ETH

Caviar

Findings Distribution

Researcher Performance

Rank: 86/120

Findings: 1

Award: $23.08

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

23.0813 USDC - $23.08

Labels

bug
3 (High Risk)
satisfactory
sponsor confirmed
upgraded by judge
duplicate-167

External Links

Lines of code

https://github.com/code-423n4/2023-04-caviar/blob/main/src/PrivatePool.sol#L230-L231 https://github.com/code-423n4/2023-04-caviar/blob/main/src/PrivatePool.sol#L323-L324

Vulnerability details

Impact

In PrivatePool.sol we have functions buy() and sell(). The buy() function is responsible for allowing users to buy a basket of NFTs represented by a list of token IDs with different weights. The sell() function sells NFTs into the pool and transfers base tokens to the caller.

In these functions, we have unsafe cast from uint128 to uint256.

Proof of Concept

In buy() we have:

231: virtualNftReserves -= uint128(weightSum);

weightSum is calculated as:

219: uint256 weightSum = sumWeightsAndValidateProof(
            tokenIds,
            tokenWeights,
            proof
        );

Which returns uint256 weightSum. We have the potential risk of overflow if the sum of the NFT weights is larger than what can be represented by a uint128 integer. If the value of weightSum is greater than 2^128-1, the subtraction operation could result in an integer underflow.

We have a similar situation in sell() function.

323: virtualBaseTokenReserves -= uint128(
            netOutputAmount + protocolFeeAmount + feeAmount
        );
324:        virtualNftReserves += uint128(weightSum); 

virtualNftReserves is a uint128 variable that tracks the virtual reserves of the NFTs being sold: https://github.com/code-423n4/2023-04-caviar/blob/main/src/PrivatePool.sol#L112

112: uint128 public virtualNftReserves;

weightSum is a uint256 variable that represents the total weight of the NFTs being sold.

To update the virtualNftReserves variable, the weightSum variable is cast to uint128 using the unsafe cast operator. This may result in data loss if the weightSum variable is larger than the maximum value that can be represented by uint128.

The same applies to virtualBaseTokenReserves.

Tools Used

Manual Review

Consider using OpenZeppelin's SafeCast library to prevent unexpected overflow/underflow when casting or change uint128 to uint256 where necessary.

#0 - c4-pre-sort

2023-04-20T18:00:19Z

0xSorryNotSorry marked the issue as primary issue

#1 - c4-sponsor

2023-04-22T13:43:45Z

outdoteth marked the issue as sponsor confirmed

#2 - outdoteth

2023-04-24T12:05:39Z

Fixed in: https://github.com/outdoteth/caviar-private-pools/pull/10

Proposed fix is to use OpenZeppelin's SafeCast library when casting from uint256 down to uint128 in the reserve updates.

// update the virtual reserves
virtualBaseTokenReserves += SafeCast.toUint128(netInputAmount - feeAmount - protocolFeeAmount);
virtualNftReserves -= SafeCast.toUint128(weightSum);

// -- snip --//

// update the virtual reserves
virtualBaseTokenReserves -= SafeCast.toUint128(netOutputAmount + protocolFeeAmount + feeAmount);
virtualNftReserves += SafeCast.toUint128(weightSum);

#3 - c4-judge

2023-04-27T08:54:23Z

GalloDaSballo marked the issue as duplicate of #167

#4 - c4-judge

2023-05-02T07:55:16Z

GalloDaSballo changed the severity to 3 (High Risk)

#5 - c4-judge

2023-05-02T07:56:05Z

GalloDaSballo 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