Platform: Code4rena
Start Date: 11/08/2022
Pot Size: $40,000 USDC
Total HM: 8
Participants: 108
Period: 4 days
Judge: hickuphh3
Total Solo HM: 2
Id: 152
League: ETH
Rank: 20/108
Findings: 3
Award: $105.14
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: itsmeSTYJ
Also found by: 0x1f8b, 0x52, 0xDjango, Ch_301, Chom, KIntern_NA, PwnedNoMore, Treasure-Seeker, auditor0517, byndooa, cccz, csanuragjain, ladboy233, nine9, shenwilly, thank_you, yixxas, zkhorse
42.8343 USDC - $42.83
User can mint more than saleConfig.limitPerAccount
.
if (IERC721(nftContract).balanceOf(msg.sender) + count > saleConfig.limitPerAccount) { if (saleConfig.limitPerAccount == 0) { // Provide a more targeted error if the collection has not been listed. revert NFTDropMarketFixedPriceSale_Must_Have_Sale_In_Progress(); }
Manual Review.
I recommend adding logic that disables transferring NFT until after a certain time.
#0 - HardlyDifficult
2022-08-17T20:59:03Z
🌟 Selected for report: Saw-mon_and_Natalie
Also found by: 0x1f8b, 0x52, 0xDjango, 0xNazgul, 0xSmartContract, 0xSolus, 0xackermann, 0xmatt, 0xsolstars, Aymen0909, Bnke0x0, Chom, Deivitto, DevABDee, Dravee, ElKu, IllIllI, JC, Kumpa, Lambda, LeoS, MiloTruck, PwnedNoMore, ReyAdmirado, Rohan16, Rolezn, Ruhum, Sm4rty, TomJ, Treasure-Seeker, Vexjon, Waze, Yiko, __141345__, apostle0x01, auditor0517, berndartmueller, bin2chen, bobirichman, brgltd, bulej93, c3phas, carlitox477, cccz, cryptphi, csanuragjain, d3e4, danb, delfin454000, durianSausage, erictee, fatherOfBlocks, gogo, iamwhitelights, joestakey, jonatascm, ladboy233, mics, oyc_109, rbserver, ret2basic, robee, rokinot, rvierdiiev, shenwilly, sikorico, simon135, thank_you, wagmi, yash90, zeesaw, zkhorse
41.7141 USDC - $41.71
Finding | Instances | |
---|---|---|
[L-01] | Floating Pragma | 1 |
[L-02] | abi.encodePacked() should not be used with dynamic types when passing the result to a hash function such as keccak256() | 1 |
Finding | Instances | |
---|---|---|
[N-01] | Remove TODOs | 1 |
[N-02] | Function doesn’t currently use try/catch as spec suggests | 1 |
Contract | Total Instances | Total Findings | Low Findings | Low Instances | NC Findings | NC Instances |
---|---|---|---|---|---|---|
MarketFees.sol | 2 | 2 | 0 | 0 | 2 | 2 |
NFTCollection.sol | 1 | 1 | 1 | 1 | 0 | 0 |
NFTCollectionFactory.sol | 1 | 1 | 1 | 1 | 0 | 0 |
A floating pragma might result in contract being tested/deployed with different or inconsistent compiler versions possibly leading to unexpected behaviour. 1 instance of this issue has been found:
[L-01] NFTCollection.sol#L3
pragma solidity ^0.8.12;
abi.encodePacked()
should not be used with dynamic types when passing the result to a hash function such as keccak256()
Use abi.encode()
instead which will pad items to 32 bytes, which will prevent hash collisions (e.g. abi.encodePacked(0x123,0x456)
=> 0x123456
=> abi.encodePacked(0x1,0x23456)
, but abi.encode(0x123,0x456)
=> 0x0...1230...456)
. If there is only one argument to abi.encodePacked()
it can often be cast to bytes()
or bytes32()
instead.
1 instance of this issue has been found:
[L-02] NFTCollectionFactory.sol#L449-L450
return keccak256(abi.encodePacked(creator, nonce));
Please remove TODOs as they harm readability. 1 instance of this issue has been found:
[N-01] MarketFees.sol#L193-L194
// TODO add referral info
Function can be turned into internal
.
1 instance of this issue has been found:
[N-02] MarketFees.sol#L212-L223
/** * @notice **For internal use only.** * @dev This function is external to allow using try/catch but is not intended for external use. * This checks the token creator. */ function internalGetTokenCreator(address nftContract, uint256 tokenId) external view returns (address payable creator) { creator = ITokenCreator(nftContract).tokenCreator{ gas: READ_ONLY_GAS_LIMIT }(tokenId); }
#0 - HardlyDifficult
2022-08-18T18:58:23Z
Use fixed pragma
Disagree. We intentionally use a floating pragma in order to make integrating with contracts easier. Other contract developers are looking to interact with our contracts and they may be on a different version than we use. The pragma selected for our contracts is the minimum required in order to correctly compile and function. This way integration is easier if they lag a few versions behind, or if they use the latest but we don't bump our packages frequently enough, and when we do upgrade versions unless there was a breaking solidity change -- it should just swap in by incrementing our npm package version.
abi.encodePacked() should not be used
It's a gas savings to use encodePacked for the use cases we support here. There does not appear to be a compelling reason to change.
Unresolved TODO comments
Agree, will fix.
[N-02] Function doesn’t currently use try/catch as spec suggests
Invalid, it's used here
🌟 Selected for report: Dravee
Also found by: 0x040, 0x1f8b, 0xDjango, 0xHarry, 0xNazgul, 0xSmartContract, 0xbepresent, 0xkatana, Amithuddar, Aymen0909, Bnke0x0, Chom, CodingNameKiki, Deivitto, DevABDee, Diraco, ElKu, Fitraldys, Funen, IllIllI, JC, LeoS, Metatron, MiloTruck, Noah3o6, ReyAdmirado, Rohan16, Rolezn, Saw-mon_and_Natalie, Sm4rty, SpaceCake, TomJ, Tomio, Trabajo_de_mates, Waze, Yiko, __141345__, ajtra, apostle0x01, bobirichman, brgltd, bulej93, c3phas, cRat1st0s, carlitox477, d3e4, durianSausage, erictee, fatherOfBlocks, gerdusx, gogo, hakerbaya, ignacio, jag, joestakey, ladboy233, medikko, mics, newfork01, oyc_109, pfapostol, robee, rvierdiiev, sach1r0, saian, samruna, sikorico, simon135, wagmi, zeesaw, zkhorse, zuhaibmohd
20.6015 USDC - $20.60
Finding | Instances | |
---|---|---|
[G-01] | for loop increments should be unchecked{} if overflow is not possible | 1 |
[G-02] | Setting variable to default value is redundant | 5 |
[G-03] | array.length should be cached in for loop | 4 |
[G-04] | Implementing return is redundant if function already has a named returns method implemented | 5 |
Contract | Instances | Gas Ops |
---|---|---|
MarketFees.sol | 9 | 4 |
BytesLibrary.sol | 2 | 1 |
NFTDropMarket.sol | 2 | 1 |
NFTDropMarketFixedPriceSale.sol | 1 | 1 |
FoundationTreasuryNode.sol | 1 | 1 |
for
loop increments should be unchecked{}
if overflow is not possibleFrom Solidity 0.8.0
onwards using the unchecked
keyword saves 30 to 40 gas per loop.
Example:
uint length = array.length; for (uint i; i < length;){ uncheck { ++i } }
1 instance of this issue has been found:
[G-01] MarketFees.sol#L198-L199
for (uint256 i = 0; i < creatorShares.length; ++i) {
Setting variable to default value is redundant. 5 instances of this issue have been found:
[G-02] BytesLibrary.sol#L44-L45
for (uint256 i = 0; i < 4; ++i) {
[G-02b] BytesLibrary.sol#L25-L26
for (uint256 i = 0; i < 20; ++i) {
[G-02c] MarketFees.sol#L484-L485
for (uint256 i = 0; i < creatorRecipients.length; ++i) {
[G-02d] MarketFees.sol#L198-L199
for (uint256 i = 0; i < creatorShares.length; ++i) {
[G-02e] MarketFees.sol#L126-L127
for (uint256 i = 0; i < creatorRecipients.length; ++i) {
array.length
should be cached in for
loopCaching the length array would save gas on each iteration by not having to read from memory or storage multiple times. Example:
uint length = array.length; for (uint i; i < length;){ uncheck { ++i } }
4 instances of this issue have been found:
[G-03] MarketFees.sol#L503-L504
for (uint256 i = 1; i < creatorRecipients.length; ) {
[G-03b] MarketFees.sol#L484-L485
for (uint256 i = 0; i < creatorRecipients.length; ++i) {
[G-03c] MarketFees.sol#L198-L199
for (uint256 i = 0; i < creatorShares.length; ++i) {
[G-03d] MarketFees.sol#L126-L127
for (uint256 i = 0; i < creatorRecipients.length; ++i) {
return
is redundant if function already has a named returns
method implementedRedundant return
methods increase gas on deployment and execution.
5 instances of this issue have been found:
[G-04] NFTDropMarketFixedPriceSale.sol#L230-L252
returns (uint256 numberThatCanBeMinted) { (, , uint256 limitPerAccount, uint256 numberOfTokensAvailableToMint, bool marketCanMint) = getFixedPriceSale( nftContract ); if (!marketCanMint) { // No one can mint in the current state. return 0; } uint256 currentBalance = IERC721(nftContract).balanceOf(user); if (currentBalance >= limitPerAccount) { // User has exhausted their limit. return 0; } uint256 availableToMint = limitPerAccount - currentBalance; if (availableToMint > numberOfTokensAvailableToMint) { // User has more tokens available than the collection has available. return numberOfTokensAvailableToMint; } return availableToMint; }
[G-04b] MarketFees.sol#L208-L210
function getRoyaltyRegistry() external view returns (address registry) { return address(royaltyRegistry); }
[G-04c] FoundationTreasuryNode.sol#L59-L61
function getFoundationTreasury() public view returns (address payable treasuryAddress) { return treasury; }
[G-04d] NFTDropMarket.sol#L136-L149
returns (address payable sellerOrOwner) { // Check the current owner first in case it has been sold. try IERC721(nftContract).ownerOf(tokenId) returns (address owner) { if (owner != address(0)) { // Once an NFT has been minted, it cannot be sold through this contract. revert NFTDropMarket_NFT_Already_Minted(); } } catch // solhint-disable-next-line no-empty-blocks { // Fall through } return super._getSellerOf(nftContract, tokenId);
[G-04e] NFTDropMarket.sol#L112-L125
returns (address payable seller) { // Check the current owner first in case it has been sold. try IERC721(nftContract).ownerOf(tokenId) returns (address owner) { if (owner != address(0)) { // If sold, return address(0) since that owner cannot sell via this market. return payable(address(0)); } } catch // solhint-disable-next-line no-empty-blocks { // Fall through } return super._getSellerOf(nftContract, tokenId);
#0 - HardlyDifficult
2022-08-19T00:25:02Z
[G-01] for loop increments should be unchecked{}
getFeesAndRecipients
is a read only function not intended to be used on-chain, but as a best practice we will add unchecked there as well.
Don't initialize variables with default values.
Invalid. This optimization technique is no longer applicable with the current version of Solidity.
Cache Array Length Outside of Loop
May be theoretically valid, but won't fix. I tested this: gas-reporter and our gas-stories suite is reporting a small regression using this technique. It also hurts readability a bit so we wouldn't want to include it unless it was a clear win.
Not using the named return variables when a function returns, wastes deployment gas
Agree for code consistency with other parts of our code. Saves 0.013 bytes on the bytecode size.