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
Rank: 100/120
Findings: 1
Award: $10.86
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: AkshaySrivastav
Also found by: 0xTheC0der, Dug, GT_Blockchain, Haipls, adriro, bin2chen, carlitox477, dingo2077, fs0c, hasmama, hihen, holyhansss_kr, juancito, ladboy233, philogy, saian, said, sashik_eth, yixxas, zion
10.8554 USDC - $10.86
https://github.com/code-423n4/2023-04-caviar/blob/main/src/Factory.sol#L92
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.
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
The Factory::create
function directly uses the _salt
parameter to clone the contract:
92: privatePool = PrivatePool(payable(privatePoolImplementation.cloneDeterministic(_salt)));
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 ); }
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