Platform: Code4rena
Start Date: 06/12/2022
Pot Size: $36,500 USDC
Total HM: 16
Participants: 119
Period: 3 days
Judge: berndartmueller
Total Solo HM: 2
Id: 189
League: ETH
Rank: 46/119
Findings: 2
Award: $66.18
๐ Selected for report: 0
๐ Solo Findings: 0
๐ Selected for report: 0xSmartContract
Also found by: 0x1f8b, 0x4non, 0xA5DF, 0xNazgul, 0xRobocop, 0xfuje, Bnke0x0, HollaDieWaldfee, Lambda, RaymondFam, TomJ, _Adam, adriro, ajtra, carrotsmuggler, cccz, danyams, gasperpre, hansfriese, helios, immeas, joestakey, ladboy233, nameruse, obront, oyc_109, rvierdiiev, saian, sakshamguruji, seyni, slvDev, yixxas, zaskoh
31.1555 USDC - $31.16
address(0)
checksEscher721.sol#L64-L69: Missing address(0) check to receiver
LPDAFactory.sol#L46-L48: Missing address(0) check to fees
OpenEditionFactory.sol#L42-L44: Missing address(0) check to fees
FixedPriceFactory.sol#L42-L44: Missing address(0) check to fees
Escher721Factory.sol#L17-L21: on _escher
uint256
to uint48
Downcasting, or converting a larger integer type to a smaller one, can potentially lead to loss of data if the value of the larger integer is too big to fit into the smaller type. This can happen in Solidity when using the uint48 type, which can only hold values up to 281474976710655 (or 2^48-1), but is often used to store values that are initially declared as uint256, which has a much larger range of possible values.
In order to prevent data loss, it is important to carefully check the value of the uint256 variable before downcasting it to uint48. This can be done using an if statement and the require function, which will throw an error if the condition is not met.
This happens on:
_amount
_amount
On Escher721.sol#L64-L69 it seems that feeNumerator
can be 100_00 bps, add a threashold if you intent to fix a max royalty percentage.
_safeMint
ย instead of _mint
for minting ERC721OpenZeppelin recommends the usage of _safeMint()
instead of _mint()
. If the recipient is a contract,_safeMint()
checks whether they can handle ERC721 tokens.
On (Escher721.sol#L52)[https://github.com/code-423n4/2022-12-escher/blob/main/src/Escher721.sol#L52]mint(address to, uint256 tokenId)
you are using _mint
function to mint the NTFs. However, this will not trigger the expected callback after minting the NFT if the buyer is a contract. This could cause issues with the implementation of the ERC721 standard and may result in stuck or malfunctioning contracts.
So if a contract buys a NFT in any of this;
It could get stuck or dont work as the ERC721 standard expect to work.
The recomendation is to use _safeMint
instead of _mint
, take in consideration that _safeMint
will trigger a callback to the receiver, so stick to check - effects - itterarion pattern or add a nonReentrant
modifier to minters/FixedPrice.sol#L66
, minters/LPDA.sol#L74
& minters/OpenEdition.sol#L67
In OZ ERC721 docs on _mint
method;
Usage of this method is discouraged, use _safeMint whenever possible
Reference: https://docs.openzeppelin.com/contracts/4.x/api/token/erc721#ERC721-_mint-address-uint256-
If minting would work as expected (if the minter is a contract he should receive a callback because of _safeMint
), this functions dont respect the check - effects - itterarion pattern or add a nonReentrant
modifier.
Floating pragma may cause unexpected compilation time behaviour and introduce unintended bugs. Is recommend to use only on interfaces and librearies
Change pragma solidity ^0.8.17;
to pragma solidity 0.8.17;
on;
src/uris/Base.sol:2:pragma solidity ^0.8.17;
src/uris/Generative.sol:2:pragma solidity ^0.8.17;
src/uris/Unique.sol:2:pragma solidity ^0.8.17;
src/minters/OpenEdition.sol:2:pragma solidity ^0.8.17;
src/minters/FixedPrice.sol:2:pragma solidity ^0.8.17;
src/minters/LPDAFactory.sol:2:pragma solidity ^0.8.17;
src/minters/OpenEditionFactory.sol:2:pragma solidity ^0.8.17;
src/minters/FixedPriceFactory.sol:2:pragma solidity ^0.8.17;
src/minters/LPDA.sol:2:pragma solidity ^0.8.17;
src/Escher.sol:2:pragma solidity ^0.8.17;
src/Escher721Factory.sol:2:pragma solidity ^0.8.17;
src/Escher721.sol:2:pragma solidity ^0.8.17;
Event emission is a critical aspect of smart contract development in Solidity, as it allows external contracts and applications to listen for and react to changes in the state of a contract. Failure to emit events on key functions can prevent external contracts and applications from functioning properly, and can also make it difficult to debug and track changes to the contract.
updateURIDelegate
on Escher721.sol#L57-L59 should emit an eventsetDefaultRoyalty
on Escher721.sol#L64-L69 should emit an eventresetDefaultRoyalty
on Escher721.sol#L72-L74 should emit an eventsetFeeReceiver
on OpenEditionFactory.sol#L42-L44 should emit an eventsetFeeReceiver
on LPDAFactory.sol#L46-L48 should emit an eventsetFeeReceiver
on FixedPriceFactory.sol#L42 should emit an eventstruct
should be defined before contract states varsOn OpenEdition.sol#L11 you have state variable an then the struct. Change it and first declare the structs.
On LPDA.sol#L11 you have state variable an then the struct. Change it and first declare the structs.
_disableInitializers
on Escher721
so Escher721Factory
dont have to intialize implementationdiff --git a/src/Escher721.sol b/src/Escher721.sol index c22429b..f9b9fd5 100644 --- a/src/Escher721.sol +++ b/src/Escher721.sol @@ -22,7 +22,9 @@ contract Escher721 is address public tokenUriDelegate; /// @custom:oz-upgrades-unsafe-allow constructor - constructor() {} + constructor() { + _disableInitializers(); + } /// @notice initialize the proxy contract /// @param _creator the initial admin for the contract diff --git a/src/Escher721Factory.sol b/src/Escher721Factory.sol index 9eb2771..b1bae91 100644 --- a/src/Escher721Factory.sol +++ b/src/Escher721Factory.sol @@ -17,7 +17,6 @@ contract Escher721Factory { constructor(address _escher) { escher = Escher(_escher); implementation = address(new Escher721()); - Escher721(implementation).initialize(address(this), address(0), "Implementation", "IMPL"); } /// @notice create a new escher unique contract
#0 - c4-judge
2023-01-04T10:44:33Z
berndartmueller marked the issue as grade-b
๐ Selected for report: slvDev
Also found by: 0x4non, Bnke0x0, Diana, Dinesh11G, RaymondFam, ReyAdmirado, adriro, ahmedov, ajtra, c3phas, cryptostellar5, nicobevi, pfapostol, sakshamguruji, tnevler, zaskoh
35.0246 USDC - $35.02
uint40
for representing time to pack structsIf you use uint40
, which is considere safe for representing unix timstamp you could pack the structs and save lot of gas;
test_LPDA() (gas: -24987 (-3.564%)) test_RevertsWhenSoldOut_Buy() (gas: -24931 (-3.576%)) test_RevertsWhenEnded_Buy() (gas: -24753 (-3.603%)) test_SellsOut_Buy() (gas: -24741 (-3.617%)) test_RevertsWhenAlreadyRefunded_Refund() (gas: -24750 (-6.225%)) test_Refund() (gas: -24616 (-6.243%)) test_WhenNotOver_Refund() (gas: -24616 (-6.243%)) test_RevertsWhenTooSoon_Buy() (gas: -24546 (-6.244%)) test_RevertsWhenNoRefund_Refund() (gas: -24627 (-6.315%)) test_RevertsWhenTooLate_Cancel() (gas: -24452 (-6.339%)) test_RevertsWhenNotOwner_Cancel() (gas: -24457 (-6.340%)) test_RevertsWhenTooLittleValue_Buy() (gas: -24647 (-6.364%)) test_Buy() (gas: -24457 (-6.416%)) test_WhenEnded_Finalize() (gas: -24517 (-6.958%)) test_RevertsWhenTooSoon_Buy() (gas: -24551 (-7.802%)) test_RevertsWhenEnded_Buy() (gas: -24561 (-7.806%)) test_RevertsTooMuchValue_Buy() (gas: -24563 (-7.811%)) test_RevertsWhenNotOwner_TransferOwnership() (gas: -24462 (-7.930%)) test_RevertsWhenNotOwner_Cancel() (gas: -24456 (-7.953%)) test_RevertsWhenTooLate_Cancel() (gas: -24468 (-7.958%)) test_RevertsWhenNotEnded_Finalize() (gas: -24537 (-7.964%)) test_RevertsWhenTooLittleValue_Buy() (gas: -24563 (-7.979%)) test_RevertsWhenNotAdmin_CreateSale() (gas: -2095 (-8.064%)) test_Buy() (gas: -24456 (-8.073%)) test_RevertsWhenNotAdmin_CreateSale() (gas: -2081 (-8.755%)) test_Cancel() (gas: -24294 (-9.627%)) test_Cancel() (gas: -24468 (-10.826%)) test_CreateSale() (gas: -24235 (-11.077%)) test_RevertsWhenInitialized_Initialize() (gas: -2128 (-11.795%)) test_CreateSale() (gas: -24337 (-12.925%)) test_RevertsWhenInitialized_Finalize() (gas: -2062 (-13.762%)) test_RevertsWhenInitialized_Buy() (gas: -2095 (-13.959%)) Overall gas change: -673443 (-249.656%)
diff --git a/src/minters/LPDA.sol b/src/minters/LPDA.sol index f52161c..d96efe0 100644 --- a/src/minters/LPDA.sol +++ b/src/minters/LPDA.sol @@ -20,10 +20,9 @@ contract LPDA is Initializable, OwnableUpgradeable, ISale { uint80 finalPrice; uint80 dropPerSecond; // slot 3 - uint96 endTime; + uint40 endTime; address payable saleReceiver; - // slot 4 - uint96 startTime; + uint40 startTime; } struct Receipt { @@ -46,9 +45,10 @@ contract LPDA is Initializable, OwnableUpgradeable, ISale { constructor() { factory = msg.sender; _disableInitializers(); + uint48 x = type(uint48).max; uint80 y = type(uint80).max; - uint96 z = type(uint96).max; + uint40 z = type(uint40).max; address payable i = payable(address(0)); sale = LPDA.Sale(x, x, i, y, y, y, z, i, z); }
diff --git a/src/minters/OpenEdition.sol b/src/minters/OpenEdition.sol index 004f7fd..2a52f97 100644 --- a/src/minters/OpenEdition.sol +++ b/src/minters/OpenEdition.sol @@ -16,10 +16,9 @@ contract OpenEdition is Initializable, OwnableUpgradeable, ISale { uint24 currentId; address edition; // slot 2 - uint96 startTime; + uint40 startTime; address payable saleReceiver; - // slot 3 - uint96 endTime; + uint40 endTime; } /// @notice sale info for this fixed price edition @@ -46,9 +45,9 @@ contract OpenEdition is Initializable, OwnableUpgradeable, ISale { type(uint72).max, type(uint24).max, address(0), - type(uint96).max, + type(uint40).max, payable(0), - type(uint96).max + type(uint40).max ); }
contract GasTest is DSTest { Contract0 c0; Contract1 c1; function setUp() public { c0 = new Contract0(); c1 = new Contract1(); } function testGas() public { c0.updateOwner(0x158B28A1b1CB1BE12C6bD8f5a646a0e3B2024734); c1.assemblyUpdateOwner(0x158B28A1b1CB1BE12C6bD8f5a646a0e3B2024734); } } contract Contract0 { address owner = 0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84; function updateOwner(address newOwner) public { owner = newOwner; } } contract Contract1 { address owner = 0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84; function assemblyUpdateOwner(address newOwner) public { assembly { sstore(owner.slot, newOwner) } } }
โญโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโฌโโโโโโโฌโโโโโโโโโฌโโโโโโโฌโโโโโโโโโโฎ โ Contract0 contract โ โ โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโโชโโโโโโโชโโโโโโโโโชโโโโโโโชโโโโโโโโโโก โ Deployment Cost โ Deployment Size โ โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโผโโโโโโโผโโโโโโโโโผโโโโโโโผโโโโโโโโโโค โ 60623 โ 261 โ โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโผโโโโโโโผโโโโโโโโโผโโโโโโโผโโโโโโโโโโค โ Function Name โ min โ avg โ median โ max โ # calls โ โโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโผโโโโโโโผโโโโโโโโโผโโโโโโโผโโโโโโโโโโค โ updateOwner โ 5302 โ 5302 โ 5302 โ 5302 โ 1 โ โฐโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโดโโโโโโโดโโโโโโโโโดโโโโโโโดโโโโโโโโโโฏ โญโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโฌโโโโโโโฌโโโโโโโโโฌโโโโโโโฌโโโโโโโโโโฎ โ Contract1 contract โ โ โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโโชโโโโโโโชโโโโโโโโโชโโโโโโโชโโโโโโโโโโก โ Deployment Cost โ Deployment Size โ โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโผโโโโโโโผโโโโโโโโโผโโโโโโโผโโโโโโโโโโค โ 54823 โ 232 โ โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโผโโโโโโโผโโโโโโโโโผโโโโโโโผโโโโโโโโโโค โ Function Name โ min โ avg โ median โ max โ # calls โ โโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโผโโโโโโโผโโโโโโโโโผโโโโโโโผโโโโโโโโโโค โ assemblyUpdateOwnerโ 5236 โ 5236 โ 5236 โ 5236 โ 1 โ โฐโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโดโโโโโโโดโโโโโโโโโดโโโโโโโดโโโโโโโโโโฏ
When defining storage variables, make sure to declare them in ascending order, according to size. When multiple variables are able to fit into one 256 bit slot, this will save storage size and gas during runtime. For example, if you have a bool
, uint256
and a bool
, instead of defining the variables in the previously mentioned order, defining the two boolean variables first will pack them both into one storage slot since they only take up one byte of storage.
contract GasTest is DSTest { Contract0 c0; Contract1 c1; function setUp() public { c0 = new Contract0(); c1 = new Contract1(); } function testGas() public { bool bool0 = true; bool bool1 = false; uint256 num0 = 200; uint256 num1 = 100; c0.accessNonTightlyPacked(bool0, bool1, num0, num1); c1.accessTightlyPacked(bool0, bool1, num0, num1); } } contract Contract0 { uint256 num0 = 100; bool bool0 = false; uint256 num1 = 200; bool bool1 = true; function accessNonTightlyPacked( bool _bool0, bool _bool1, uint256 _num0, uint256 _num1 ) public { bool0 = _bool0; bool1 = _bool1; num0 = _num0; num1 = _num1; } } contract Contract1 { bool bool0 = false; bool bool1 = true; uint256 num0 = 100; uint256 num1 = 200; function accessTightlyPacked( bool _bool0, bool _bool1, uint256 _num0, uint256 _num1 ) public { bool0 = _bool0; bool1 = _bool1; num0 = _num0; num1 = _num1; } }
โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโฌโโโโโโโโฌโโโโโโโโโฌโโโโโโโโฌโโโโโโโโโโฎ โ src/test/GasTest.t.sol:Contract0 contract โ โ โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโโชโโโโโโโโชโโโโโโโโโชโโโโโโโโชโโโโโโโโโโก โ Deployment Cost โ Deployment Size โ โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโผโโโโโโโโผโโโโโโโโโผโโโโโโโโผโโโโโโโโโโค โ 122268 โ 334 โ โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโผโโโโโโโโผโโโโโโโโโผโโโโโโโโผโโโโโโโโโโค โ Function Name โ min โ avg โ median โ max โ # calls โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโผโโโโโโโโผโโโโโโโโโผโโโโโโโโผโโโโโโโโโโค โ accessNonTightlyPacked โ 32774 โ 32774 โ 32774 โ 32774 โ 1 โ โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโดโโโโโโโโดโโโโโโโโโดโโโโโโโโดโโโโโโโโโโฏ โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโฌโโโโโโโโฌโโโโโโโโโฌโโโโโโโโฌโโโโโโโโโโฎ โ src/test/GasTest.t.sol:Contract1 contract โ โ โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโโชโโโโโโโโชโโโโโโโโโชโโโโโโโโชโโโโโโโโโโก โ Deployment Cost โ Deployment Size โ โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโผโโโโโโโโผโโโโโโโโโผโโโโโโโโผโโโโโโโโโโค โ 126247 โ 356 โ โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโผโโโโโโโโผโโโโโโโโโผโโโโโโโโผโโโโโโโโโโค โ Function Name โ min โ avg โ median โ max โ # calls โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโผโโโโโโโโผโโโโโโโโโผโโโโโโโโผโโโโโโโโโโค โ accessTightlyPacked โ 15476 โ 15476 โ 15476 โ 15476 โ 1 โ โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโดโโโโโโโโดโโโโโโโโโดโโโโโโโโดโโโโโโโโโโฏ
You can mark public or external functions as payable to save gas. Functions that are not payable have additional logic to check if there was a value sent with a call, however, making a function payable eliminates this check. This optimization should be carefully considered due to potentially unwanted behavior when a function does not need to accept ether.
contract GasTest is DSTest { Contract0 c0; Contract1 c1; function setUp() public { c0 = new Contract0(); c1 = new Contract1(); } function testGas() public { c0.isNotPayable(); c1.isPayable(); } } contract Contract0 { function isNotPayable() public view { uint256 val = 0; val++; } } contract Contract1 { function isPayable() public payable { uint256 val = 0; val++; } }
โญโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโฌโโโโโโฌโโโโโโโโโฌโโโโโโฌโโโโโโโโโโฎ โ Contract0 contract โ โ โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโโชโโโโโโชโโโโโโโโโชโโโโโโชโโโโโโโโโโก โ Deployment Cost โ Deployment Size โ โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโผโโโโโโผโโโโโโโโโผโโโโโโผโโโโโโโโโโค โ 32081 โ 190 โ โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโผโโโโโโผโโโโโโโโโผโโโโโโผโโโโโโโโโโค โ Function Name โ min โ avg โ median โ max โ # calls โ โโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโผโโโโโโผโโโโโโโโโผโโโโโโผโโโโโโโโโโค โ isNotPayable โ 198 โ 198 โ 198 โ 198 โ 1 โ โฐโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโดโโโโโโดโโโโโโโโโดโโโโโโดโโโโโโโโโโฏ โญโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโฌโโโโโโฌโโโโโโโโโฌโโโโโโฌโโโโโโโโโโฎ โ Contract1 contract โ โ โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโโชโโโโโโชโโโโโโโโโชโโโโโโชโโโโโโโโโโก โ Deployment Cost โ Deployment Size โ โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโผโโโโโโผโโโโโโโโโผโโโโโโผโโโโโโโโโโค โ 29681 โ 178 โ โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโผโโโโโโผโโโโโโโโโผโโโโโโผโโโโโโโโโโค โ Function Name โ min โ avg โ median โ max โ # calls โ โโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโผโโโโโโผโโโโโโโโโผโโโโโโผโโโโโโโโโโค โ isPayable โ 174 โ 174 โ 174 โ 174 โ 1 โ โฐโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโดโโโโโโดโโโโโโโโโดโโโโโโดโโโโโโโโโโฏ
#0 - c4-sponsor
2022-12-22T22:54:34Z
mehtaculous marked the issue as sponsor disputed
#1 - eugenioclrc
2023-01-02T13:53:07Z
This report is similar to the one "selected for report": https://github.com/code-423n4/2022-12-escher-findings/issues/529 Why the "sponsor disputed" label?
#2 - c4-judge
2023-01-04T11:01:34Z
berndartmueller marked the issue as grade-b