Platform: Code4rena
Start Date: 12/12/2022
Pot Size: $36,500 USDC
Total HM: 8
Participants: 103
Period: 7 days
Judge: berndartmueller
Id: 193
League: ETH
Rank: 46/103
Findings: 2
Award: $64.99
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0xSmartContract
Also found by: 0xGusMcCrae, 8olidity, Bnke0x0, IllIllI, JC, RaymondFam, Rolezn, SleepingBugs, UNCHAIN, ahayashi, aviggiano, caventa, cozzetti, h0wl, helios, immeas, ladboy233, minhquanym, obront, rjs, rvierdiiev, shung, unforgiven, yixxas
50.16 USDC - $50.16
address(0x0)
when assigning values to address
state variables2022-12-caviar/src/Pair.sol::47 => nft = _nft; 2022-12-caviar/src/Pair.sol::48 => baseToken = _baseToken; // use address(0) for native ETH 2022-12-caviar/src/Pair.sol::49 => merkleRoot = _merkleRoot;
_safeMint()
should be used rather than _mint()
wherever possible2022-12-caviar/src/LpToken.sol::20 => _mint(to, amount); 2022-12-caviar/src/Pair.sol::233 => _mint(msg.sender, fractionalTokenAmount);
Avoid floating pragmas for non-library contracts.
2022-12-caviar/src/Caviar.sol::2 => pragma solidity ^0.8.17; 2022-12-caviar/src/LpToken.sol::2 => pragma solidity ^0.8.17; 2022-12-caviar/src/Pair.sol::2 => pragma solidity ^0.8.17; 2022-12-caviar/src/lib/SafeERC20Namer.sol::2 => pragma solidity ^0.8.17;
Non-Critical Issues
2022-12-caviar/src/LpToken.sol::13 => ERC20(string.concat(pairSymbol, " LP token"), string.concat("LP-", pairSymbol), 18) 2022-12-caviar/src/Pair.sol::20 => uint256 public constant ONE = 1e18; 2022-12-caviar/src/Pair.sol::399 => return (outputAmount * 1000 * baseTokenReserves()) / ((fractionalTokenReserves() - outputAmount) * 997); 2022-12-caviar/src/Pair.sol::407 => uint256 inputAmountWithFee = inputAmount * 997; 2022-12-caviar/src/Pair.sol::408 => return (inputAmountWithFee * baseTokenReserves()) / ((fractionalTokenReserves() * 1000) + inputAmountWithFee); 2022-12-caviar/src/lib/SafeERC20Namer.sol::55 => return Strings.toHexString(uint160(token) >> (160 - 4 * 4));
indexed
fields2022-12-caviar/src/Pair.sol::30 => event Add(uint256 baseTokenAmount, uint256 fractionalTokenAmount, uint256 lpTokenAmount); 2022-12-caviar/src/Pair.sol::31 => event Remove(uint256 baseTokenAmount, uint256 fractionalTokenAmount, uint256 lpTokenAmount); 2022-12-caviar/src/Pair.sol::32 => event Buy(uint256 inputAmount, uint256 outputAmount); 2022-12-caviar/src/Pair.sol::33 => event Sell(uint256 inputAmount, uint256 outputAmount);
2022-12-caviar/src/Caviar.sol::1 => // SPDX-License-Identifier: MIT 2022-12-caviar/src/LpToken.sol::1 => // SPDX-License-Identifier: MIT 2022-12-caviar/src/Pair.sol::1 => // SPDX-License-Identifier: MIT 2022-12-caviar/src/lib/SafeERC20Namer.sol::1 => // SPDX-License-Identifier: MIT
public
functions not called by the contract should be declared external
insteadContracts are allowed to override their parents’ functions and change the visibility from public to external .#### Findings:
2022-12-caviar/src/Caviar.sol::28 => function create(address nft, address baseToken, bytes32 merkleRoot) public returns (Pair pair) { 2022-12-caviar/src/Caviar.sol::49 => function destroy(address nft, address baseToken, bytes32 merkleRoot) public { 2022-12-caviar/src/LpToken.sol::19 => function mint(address to, uint256 amount) public onlyOwner { 2022-12-caviar/src/LpToken.sol::26 => function burn(address from, uint256 amount) public onlyOwner { 2022-12-caviar/src/Pair.sol::147 => function buy(uint256 outputAmount, uint256 maxInputAmount) public payable returns (uint256 inputAmount) { 2022-12-caviar/src/Pair.sol::182 => function sell(uint256 inputAmount, uint256 minOutputAmount) public returns (uint256 outputAmount) { 2022-12-caviar/src/Pair.sol::248 => function unwrap(uint256[] calldata tokenIds) public returns (uint256 fractionalTokenAmount) { 2022-12-caviar/src/Pair.sol::280 => ) public payable returns (uint256 lpTokenAmount) { 2022-12-caviar/src/Pair.sol::310 => function nftBuy(uint256[] calldata tokenIds, uint256 maxInputAmount) public payable returns (uint256 inputAmount) { 2022-12-caviar/src/Pair.sol::341 => function close() public { 2022-12-caviar/src/Pair.sol::359 => function withdraw(uint256 tokenId) public { 2022-12-caviar/src/Pair.sol::379 => function baseTokenReserves() public view returns (uint256) { 2022-12-caviar/src/Pair.sol::383 => function fractionalTokenReserves() public view returns (uint256) { 2022-12-caviar/src/Pair.sol::390 => function price() public view returns (uint256) { 2022-12-caviar/src/Pair.sol::398 => function buyQuote(uint256 outputAmount) public view returns (uint256) { 2022-12-caviar/src/Pair.sol::406 => function sellQuote(uint256 inputAmount) public view returns (uint256) { 2022-12-caviar/src/Pair.sol::417 => function addQuote(uint256 baseTokenAmount, uint256 fractionalTokenAmount) public view returns (uint256) { 2022-12-caviar/src/Pair.sol::435 => function removeQuote(uint256 lpTokenAmount) public view returns (uint256, uint256) {
There are units for seconds, minutes, hours, days, and weeks
2022-12-caviar/src/Pair.sol::21 => uint256 public constant CLOSE_GRACE_PERIOD = 7 days;
2022-12-caviar/src/Caviar.sol::2 => pragma solidity ^0.8.17; 2022-12-caviar/src/LpToken.sol::2 => pragma solidity ^0.8.17; 2022-12-caviar/src/Pair.sol::2 => pragma solidity ^0.8.17; 2022-12-caviar/src/lib/SafeERC20Namer.sol::2 => pragma solidity ^0.8.17;
#0 - c4-judge
2023-01-16T11:41:28Z
berndartmueller marked the issue as grade-b
🌟 Selected for report: Rolezn
Also found by: 0x1f8b, 0xAgro, 0xSmartContract, 0xab00, 0xhacksmithh, Aymen0909, Bnke0x0, Breeje, Diana, HardlyCodeMan, IllIllI, JC, JrNet, Madalad, NoamYakov, RaymondFam, ReyAdmirado, SleepingBugs, UdarTeam, c3phas, carlitox477, cryptonue, gz627, lukris02, millersplanet, oyc_109, pavankv, ret2basic, saneryee, tnevler
14.833 USDC - $14.83
immutable
Avoids a Gusset (20000 gas)
2022-12-caviar/src/Pair.sol::28 => uint256 public closeTimestamp;
<array>.length
should not be looked up in every loop of a for
loopEven memory arrays incur the overhead of bit tests and bit shifts to calculate the array length. Storage array length checks incur an extra Gwarmaccess (100 gas) PER-LOOP.
2022-12-caviar/src/Pair.sol::238 => for (uint256 i = 0; i < tokenIds.length; i++) { 2022-12-caviar/src/Pair.sol::258 => for (uint256 i = 0; i < tokenIds.length; i++) { 2022-12-caviar/src/Pair.sol::468 => for (uint256 i = 0; i < tokenIds.length; i++) {
++i/i++
should be unchecked{++i}
/unchecked{++i}
when it is not possible for them to overflow, as is the case when used in for
and while
loops2022-12-caviar/src/Pair.sol::238 => for (uint256 i = 0; i < tokenIds.length; i++) { 2022-12-caviar/src/Pair.sol::258 => for (uint256 i = 0; i < tokenIds.length; i++) { 2022-12-caviar/src/Pair.sol::468 => for (uint256 i = 0; i < tokenIds.length; i++) { 2022-12-caviar/src/lib/SafeERC20Namer.sol::22 => for (uint256 j = 0; j < charCount; j++) { 2022-12-caviar/src/lib/SafeERC20Namer.sol::33 => for (uint256 i = 32; i < 64; i++) { 2022-12-caviar/src/lib/SafeERC20Namer.sol::39 => for (uint256 i = 0; i < charCount; i++) {
> 0
costs more gas than != 0
when used on a uint
in a require()
statement2022-12-caviar/src/Pair.sol::71 => require(baseTokenAmount > 0 && fractionalTokenAmount > 0, "Input token amount is zero");
2022-12-caviar/src/Pair.sol::238 => for (uint256 i = 0; i < tokenIds.length; i++) { 2022-12-caviar/src/Pair.sol::258 => for (uint256 i = 0; i < tokenIds.length; i++) { 2022-12-caviar/src/Pair.sol::468 => for (uint256 i = 0; i < tokenIds.length; i++) { 2022-12-caviar/src/lib/SafeERC20Namer.sol::12 => uint256 charCount = 0; 2022-12-caviar/src/lib/SafeERC20Namer.sol::13 => for (uint256 j = 0; j < 32; j++) { 2022-12-caviar/src/lib/SafeERC20Namer.sol::22 => for (uint256 j = 0; j < charCount; j++) { 2022-12-caviar/src/lib/SafeERC20Namer.sol::31 => uint256 charCount = 0; 2022-12-caviar/src/lib/SafeERC20Namer.sol::39 => for (uint256 i = 0; i < charCount; i++) {
++i
costs less gas than i++
, especially when it’s used in for loops (--i
/i--
too)2022-12-caviar/src/Pair.sol::238 => for (uint256 i = 0; i < tokenIds.length; i++) { 2022-12-caviar/src/Pair.sol::258 => for (uint256 i = 0; i < tokenIds.length; i++) { 2022-12-caviar/src/Pair.sol::468 => for (uint256 i = 0; i < tokenIds.length; i++) { 2022-12-caviar/src/lib/SafeERC20Namer.sol::13 => for (uint256 j = 0; j < 32; j++) { 2022-12-caviar/src/lib/SafeERC20Namer.sol::17 => charCount++; 2022-12-caviar/src/lib/SafeERC20Namer.sol::22 => for (uint256 j = 0; j < charCount; j++) { 2022-12-caviar/src/lib/SafeERC20Namer.sol::33 => for (uint256 i = 32; i < 64; i++) { 2022-12-caviar/src/lib/SafeERC20Namer.sol::35 => charCount += uint8(b[i]); 2022-12-caviar/src/lib/SafeERC20Namer.sol::39 => for (uint256 i = 0; i < charCount; i++) {
require()
statements that use &&
Cost gasSee this issue for an example
2022-12-caviar/src/Pair.sol::71 => require(baseTokenAmount > 0 && fractionalTokenAmount > 0, "Input token amount is zero");
uints
/ints
smaller than 32 bytes (256 bits) incurs overheadWhen using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.
https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html Use a larger size then downcast where needed
2022-12-caviar/src/lib/SafeERC20Namer.sol::49 => return Strings.toHexString(uint160(token)); 2022-12-caviar/src/lib/SafeERC20Namer.sol::55 => return Strings.toHexString(uint160(token) >> (160 - 4 * 4));
require()
/revert()
checks should be refactored to a modifier or function2022-12-caviar/src/Pair.sol::74 => require(baseToken == address(0) ? msg.value == baseTokenAmount : msg.value == 0, "Invalid ether input");
revert()
/require()
strings to save deployment gas2022-12-caviar/src/Caviar.sol::30 => require(pairs[nft][baseToken][merkleRoot] == address(0), "Pair already exists"); 2022-12-caviar/src/Caviar.sol::51 => require(msg.sender == pairs[nft][baseToken][merkleRoot], "Only pair can destroy itself"); 2022-12-caviar/src/Pair.sol::71 => require(baseTokenAmount > 0 && fractionalTokenAmount > 0, "Input token amount is zero"); 2022-12-caviar/src/Pair.sol::74 => require(baseToken == address(0) ? msg.value == baseTokenAmount : msg.value == 0, "Invalid ether input"); 2022-12-caviar/src/Pair.sol::80 => require(lpTokenAmount >= minLpTokenAmount, "Slippage: lp token amount out"); 2022-12-caviar/src/Pair.sol::117 => require(baseTokenOutputAmount >= minBaseTokenOutputAmount, "Slippage: base token amount out"); 2022-12-caviar/src/Pair.sol::120 => require(fractionalTokenOutputAmount >= minFractionalTokenOutputAmount, "Slippage: fractional token out"); 2022-12-caviar/src/Pair.sol::151 => require(baseToken == address(0) ? msg.value == maxInputAmount : msg.value == 0, "Invalid ether input"); 2022-12-caviar/src/Pair.sol::157 => require(inputAmount <= maxInputAmount, "Slippage: amount in"); 2022-12-caviar/src/Pair.sol::189 => require(outputAmount >= minOutputAmount, "Slippage: amount out"); 2022-12-caviar/src/Pair.sol::224 => require(closeTimestamp == 0, "Wrap: closed"); 2022-12-caviar/src/Pair.sol::343 => require(caviar.owner() == msg.sender, "Close: not owner"); 2022-12-caviar/src/Pair.sol::361 => require(caviar.owner() == msg.sender, "Withdraw: not owner"); 2022-12-caviar/src/Pair.sol::364 => require(closeTimestamp != 0, "Withdraw not initiated"); 2022-12-caviar/src/Pair.sol::367 => require(block.timestamp >= closeTimestamp, "Not withdrawable yet"); 2022-12-caviar/src/Pair.sol::470 => require(isValid, "Invalid merkle proof");
payable
If a function modifier such as onlyOwner is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided.
2022-12-caviar/src/LpToken.sol::19 => function mint(address to, uint256 amount) public onlyOwner { 2022-12-caviar/src/LpToken.sol::26 => function burn(address from, uint256 amount) public onlyOwner {
The code should be refactored such that they no longer exist, or the block should do something useful, such as emitting an event or reverting. If the contract is meant to be extended, the contract should be abstract and the function signatures be added without any default implementation. If the block is an empty if-statement block to avoid doing subsequent checks in the else-if/else conditions, the else-if/else conditions should be nested under the negation of the if-statement, because they involve different classes of checks, which may lead to the introduction of errors when the code is later modified (if(x){}else if(y){...}else{...} => if(!x){if(y){...}else{...}})
2022-12-caviar/src/Caviar.sol::21 => constructor() Owned(msg.sender) {} 2022-12-caviar/src/LpToken.sol::14 => {}
#0 - c4-judge
2023-01-14T17:20:32Z
berndartmueller marked the issue as grade-b