Caviar Private Pools - yixxas'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: 80/120

Findings: 3

Award: $26.17

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

5.9827 USDC - $5.98

Labels

bug
2 (Med Risk)
satisfactory
duplicate-858

External Links

Lines of code

https://github.com/code-423n4/2023-04-caviar/blob/cd8a92667bcb6657f70657183769c244d04c015c/src/PrivatePool.sol#L733

Vulnerability details

Impact

Base tokens with less than 4 decimals cannot utilise changeFeeQuote(). This means that such token is used, users of the pool cannot make a change() of NFT since a change fee is paid on each change(). However, buying and selling of NFTs to the pool is still allowed.

Proof of Concept

Base tokens with less than 4 decimals will revert when changeFeeQuote() is called. Calculation in exponent assumes that baseToken.decimals >= 4.

function changeFeeQuote(uint256 inputAmount) public view returns (uint256 feeAmount, uint256 protocolFeeAmount) {
	// multiply the changeFee to get the fee per NFT (4 decimals of accuracy)
	uint256 exponent = baseToken == address(0) ? 18 - 4 : ERC20(baseToken).decimals() - 4;
	uint256 feePerNft = changeFee * 10 ** exponent;

	feeAmount = inputAmount * feePerNft / 1e18;
	protocolFeeAmount = feeAmount * Factory(factory).protocolFeeRate() / 10_000;
}

Tools Used

Manual review

Consider allowing base tokens with less than 4 decimals to still be changable, considering that both buy and sell are still allowed.

#0 - c4-pre-sort

2023-04-20T15:22:59Z

0xSorryNotSorry marked the issue as duplicate of #858

#1 - c4-judge

2023-05-01T07:14:50Z

GalloDaSballo marked the issue as satisfactory

Awards

9.3258 USDC - $9.33

Labels

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

External Links

Lines of code

https://github.com/code-423n4/2023-04-caviar/blob/cd8a92667bcb6657f70657183769c244d04c015c/src/PrivatePool.sol#L235-L249

Vulnerability details

Impact

User can reduce the royalty fee paid by purchasing a NFTs in a group with big difference in weight.

Proof of Concept

In the calculations of royalities, royalty fee is based on salePrice, which is averaged out across all NFTs even if they have different weights. This can lead to extremely wrong royality fees caculated.

Assuming virtualBaseTokenReserves = 100000, virtualNftReserves = 2000, royaltyFee = 10%, and user is buying 2 NFT, 1 with weight = 1 and the other with weight = 999

To calculate netInputAmount, we take the weight sum

(netInputAmount, feeAmount, protocolFeeAmount) = buyQuote(weightSum);

function buyQuote(uint256 outputAmount)
	public
	view
	returns (uint256 netInputAmount, uint256 feeAmount, uint256 protocolFeeAmount)
{
	// calculate the input amount based on xy=k invariant and round up by 1 wei
	uint256 inputAmount =
		FixedPointMathLib.mulDivUp(outputAmount, virtualBaseTokenReserves, (virtualNftReserves - outputAmount));

	protocolFeeAmount = inputAmount * Factory(factory).protocolFeeRate() / 10_000;
	feeAmount = inputAmount * feeRate / 10_000;
	netInputAmount = inputAmount + feeAmount + protocolFeeAmount;
}

For simplicity, we assume that there are no other fees.

outputAmount = weight = 1000

then netInputAmount = ceil(1000 * 100000 / (2000 - 1000)) = 100000

Total royaltyFeeAmount = (10% of (100000 / 2) ) * 2 = 10000


 

On the other hand, if NFT is bought separately instead,

first buy weight = 1 NFT,

netInputAmount = ceil(1 * 100000 / (2000 - 1)) = 51

new virtualBaseTokenReserves = 100000 + 51 = 100051 new virtualNftReserves = 2000 - 1 = 1999

royaltyFeeAmount = 10% of 51 = 5.1

next buys weight = 999 NFT,

netInputAmount = ceil(999 * 100051 / (1999 - 999)) = 99951 royaltyFeeAmount = 10% of 100961 = 10096.1

Total royaltyFeeAmount = 5.1 + 10096.1 = 10101.2

In this case, the user who is buying NFTs as a group is paying about 1% less in royalities. In fact, the bigger the weight difference in the buy token list and with more NFTs to average out, the difference would be larger.

For example, if a user buys NFTs with weight 1, 1, 1, 1, 1, 1, 1000000, the difference in fees paid would be much larger than 1%.

Tools Used

Manual Review

We should consider the weight of NFT when calculating royalty fee instead of assuming all of them having the same price.

#0 - c4-pre-sort

2023-04-20T17:32:23Z

0xSorryNotSorry marked the issue as duplicate of #669

#1 - c4-judge

2023-04-30T15:34:21Z

GalloDaSballo changed the severity to 2 (Med Risk)

#2 - c4-judge

2023-05-01T07:27:07Z

GalloDaSballo marked the issue as satisfactory

Awards

10.8554 USDC - $10.86

Labels

bug
2 (Med Risk)
satisfactory
duplicate-419

External Links

Lines of code

https://github.com/code-423n4/2023-04-caviar/blob/cd8a92667bcb6657f70657183769c244d04c015c/src/Factory.sol#L92

Vulnerability details

Impact

Creation of new private pools can be frontrun by an adversary, preventing pools from being created.

Proof of Concept

create() uses cloneDeterministic as seen below.

privatePool = PrivatePool(payable(privatePoolImplementation.cloneDeterministic(_salt)));

This clone pattern uses the CREATE2 opcode. This means that, if implementation address and salt used is the same, it will revert as the resulting contract is already existing. In our case, _salt is an user input freely choosable by users. create() is also a public function. This means that an adversary can scan the mempool, look for calls to create() and frontrun this transaction with the same salt. The original transaction would revert, preventing legitimate users from creating a pool.

Tools Used

Manual Review

Consider using clone instead of cloneDeterministic which deploys a contract with the CREATE opcode.

#0 - c4-pre-sort

2023-04-20T17:18:24Z

0xSorryNotSorry marked the issue as duplicate of #419

#1 - c4-judge

2023-05-01T07:24:07Z

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