Caviar Private Pools - juancito'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: 100/120

Findings: 1

Award: $10.86

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

10.8554 USDC - $10.86

Labels

bug
2 (Med Risk)
high quality report
satisfactory
duplicate-419

External Links

Lines of code

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

Vulnerability details

Factory::create uses a _salt parameter for the creation of the private pool. If an adversary frontruns the original transaction using the same _salt, the pool will be created with the adversary as the owner, and the original deployer transaction will fail.

Impact

An adversary can prevent the creation of any private pools, making the protocol useless, or perform a griefing attack targeting specific users or NFT collections, preventing them from deploying private pools.

The cost of performing the attack is low. Only having to pay enough gas to frontrun the original transactions

Proof of Concept

The Factory::create function directly uses the _salt parameter to clone the contract:

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

Link to code

Add this test to test/Factory/Create.t.sol to prove that any frontrunning tx with the same salt will make the deployer transaction fail:

    function test_FrontrunCreate() public {
        address deployer = address(0x1);
        address adversary = address(0x2);
        vm.deal(deployer, 1 ether);

        uint256[] memory emptyTokenIds;

        // Place the adversary tx first to simulate the adversary frontrunning the deployer tx
        // It calls the function with the same `salt` that the deployer will use
        vm.prank(adversary);
        factory.create(address(0), address(0), 0, 0, 0, 0, 0, false, false, salt, emptyTokenIds, 0);

        // The deployer tx comes after the one from the adversary and reverts
        vm.prank(deployer);
        vm.expectRevert();
        factory.create{value: baseTokenAmount}(
            baseToken,
            nft,
            virtualBaseTokenReserves,
            virtualNftReserves,
            changeFee,
            feeRate,
            merkleRoot,
            true,
            false,
            salt,
            tokenIds,
            baseTokenAmount
        );
    }

Tools Used

Manual review

Use a hashed salt that includes the msg.sender. That way it is not possible to frontrun the transaction.

// function create(
-    privatePool = PrivatePool(payable(privatePoolImplementation.cloneDeterministic(_salt)));
+    bytes32 hashedSalt = bytes32(keccak256(abi.encode(_salt, msg.sender)));
+    privatePool = PrivatePool(payable(privatePoolImplementation.cloneDeterministic(hashedSalt)));
    function predictPoolDeploymentAddress(bytes32 salt) public view returns (address predictedAddress) {
-        predictedAddress = privatePoolImplementation.predictDeterministicAddress(salt, address(this));
+        bytes32 hashedSalt = bytes32(keccak256(abi.encode(_salt, msg.sender)));
+        predictedAddress = privatePoolImplementation.predictDeterministicAddress(hashedSalt, address(this));
    }

#0 - c4-pre-sort

2023-04-19T07:01:01Z

0xSorryNotSorry marked the issue as high quality report

#1 - c4-pre-sort

2023-04-20T17:18:57Z

0xSorryNotSorry marked the issue as duplicate of #419

#2 - c4-judge

2023-05-01T07:23:00Z

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