Platform: Code4rena
Start Date: 30/10/2023
Pot Size: $49,250 USDC
Total HM: 14
Participants: 243
Period: 14 days
Judge: 0xsomeone
Id: 302
League: ETH
Rank: 41/243
Findings: 2
Award: $262.69
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: smiling_heretic
Also found by: 00decree, 00xSEV, 0x180db, 0x3b, 0x656c68616a, 0xAadi, 0xAleko, 0xAsen, 0xDetermination, 0xJuda, 0xMAKEOUTHILL, 0xMango, 0xMosh, 0xSwahili, 0x_6a70, 0xarno, 0xgrbr, 0xpiken, 0xsagetony, 3th, 8olidity, ABA, AerialRaider, Al-Qa-qa, Arabadzhiev, AvantGard, CaeraDenoir, ChrisTina, DanielArmstrong, DarkTower, DeFiHackLabs, Deft_TT, Delvir0, Draiakoo, Eigenvectors, Fulum, Greed, HChang26, Haipls, Hama, Inference, Jiamin, JohnnyTime, Jorgect, Juntao, Kaysoft, Kose, Kow, Krace, MaNcHaSsS, Madalad, MrPotatoMagic, Neon2835, NoamYakov, Norah, Oxsadeeq, PENGUN, REKCAH, Ruhum, Shubham, Silvermist, Soul22, SovaSlava, SpicyMeatball, Talfao, TermoHash, The_Kakers, Toshii, TuringConsulting, Udsen, VAD37, Vagner, Zac, Zach_166, ZdravkoHr, _eperezok, ak1, aldarion, alexfilippov314, alexxander, amaechieth, aslanbek, ast3ros, audityourcontracts, ayden, bdmcbri, bird-flu, blutorque, bronze_pickaxe, btk, c0pp3rscr3w3r, c3phas, cartlex_, cccz, ciphermarco, circlelooper, crunch, cryptothemex, cu5t0mpeo, darksnow, degensec, dethera, devival, dimulski, droptpackets, epistkr, evmboi32, fibonacci, gumgumzum, immeas, innertia, inzinko, jasonxiale, joesan, ke1caM, kimchi, lanrebayode77, lsaudit, mahyar, max10afternoon, merlin, mrudenko, nuthan2x, oakcobalt, openwide, orion, phoenixV110, pontifex, r0ck3tz, rotcivegaf, rvierdiiev, seeques, shenwilly, sl1, slvDev, t0x1c, tallo, tnquanghuy0512, tpiliposian, trachev, twcctop, vangrim, volodya, xAriextz, xeros, xuwinnie, y4y, yobiz, zhaojie
0 USDC - $0.00
https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L125 https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L135 https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L105
An auction winner can receive the prize (the token) and get their bid ETH refunded, essentially winning the auction without having to pay for it.
In the AuctionDemo.sol
contract, there's an option to cancel your bid through two functions: cancelBid()
and cancelAllBids()
. Both functions verify that the current timestamp is less than or equal to the auctionEndTime
:
require(block.timestamp <= minter.getAuctionEndTime(_tokenid), "Auction ended");
The claimAuction()
function is meant to be used after the auction concludes. It checks the highest bid and bidder, mints the token for the highest bidder, and refunds all other bids. The following require statement ensures that this function cannot be called until the auction has ended:
require(block.timestamp >= minter.getAuctionEndTime(_tokenid) && auctionClaim[_tokenid] == false && minter.getAuctionStatus(_tokenid) == true);
Yet, there's a critical edge case that could severely impact the auction's outcome. The crucial similarity between the require statements of the cancel bid functions and the claimAuction()
function is the condition block.timestamp == AuctionEndTime
.
A malicious bidder could potentially create a smart contract that initiates a cancelBid()
on their winning bid and immediately follows it with a call to the claimAuction()
function.
If the block.timestamp
precisely matches the AuctionEndTime
, both require statements in these functions would pass, resulting in the bidder receiving a refund for their bid and acquiring the token from the claimAuction()
.
VSCode, Echidna
Change the require statement in both cancelBid()
and cancelAllBids()
functions condition from this:
require(block.timestamp <= minter.getAuctionEndTime(_tokenid), "Auction ended");
To this:
require(block.timestamp < minter.getAuctionEndTime(_tokenid), "Auction ended");
Modifying the require statement prevents a bidder from canceling their bid if block.timestamp
is equal to AuctionEndTime
, effectively averting the possibility of the mentioned edge case.
Timing
#0 - c4-pre-sort
2023-11-14T14:26:31Z
141345 marked the issue as duplicate of #962
#1 - c4-judge
2023-12-01T15:33:58Z
alex-ppg marked the issue as not a duplicate
#2 - c4-judge
2023-12-01T15:34:07Z
alex-ppg marked the issue as duplicate of #1788
#3 - c4-judge
2023-12-08T18:13:14Z
alex-ppg marked the issue as partial-50
🌟 Selected for report: MrPotatoMagic
Also found by: 3th, Fulum, JohnnyTime, K42, Kose, SAAJ, Toshii, catellatech, cats, clara, digitizeworx, dimulski, dy, glcanvas, hunter_w3b, ihtishamsudo, niki, peanuts
262.6934 USDC - $262.69
NextGen is a series of smart contracts designed to explore experimental aspects of generative art and expand the use of 100% on-chain NFTs beyond art-related applications.
NextGen combines the features of a traditional on-chain generative contract with the minting approach of The Memes, featuring phase-based, allowlist-based, and delegation-based minting philosophies.
Additionally, it allows for the customization of outputs by passing specific data to the contract for particular addresses. It offers various minting models that can be associated with different phases. NextGen comprises four primary smart contracts: Core, Minter, Admin, and Randomizer, collectively providing adaptable and scalable functionality for creating multiple ERC721 collections within a single smart contract address.
Throughout the analysis process, my primary goal was to achieve a comprehensive understanding of the codebase, with the ultimate goal of creating well-informed recommendations to enhance its operational efficacy.
The examined contracts in-scope were:
1. NextGenCore.sol 2. MinterContract.sol 3. NextGenAdmin.sol 4. RandomizerRNG.sol 5. RandomizerVRF.sol 6. RandomizerNXT.sol
I initiated the analysis by first covering the Core contract, followed by an examination of the Minter contracts, which constitute the primary logic within the system. Subsequently, I proceeded to assess the remaining contracts.
This approach allowed me to work with the contract housing the most critical logic, often referred to as the "Father Contract", to gain a thorough understanding of the system's primary logic and operational flow. From there, I delved into the other smaller "helper" contracts.
I invested more than 60 hours during the 14-day contest to conduct a comprehensive audit of the codebase. My objective is to add value to the project team with an exhaustive report that affords them a broader perspective, enhancing the value of my research by fortifying the security, user-friendliness, and efficiency of the protocol.
NextGenCore.sol: The Core contract is the central hub for minting ERC721 tokens. It encompasses all the fundamental functions of the ERC721 standard and includes extra setter and getter functions. This contract stores essential collection data, including the collection's name, artist's name, library, script, and total supply. Moreover, it collaborates with other NextGen contracts to deliver adaptable and scalable functionality.
NextGenMinter.sol: The Minter contract is responsible for minting ERC721 tokens for a collection hosted on the Core contract, adhering to predefined requirements. This contract manages crucial information about upcoming drops, such as phase start and end times, Merkle roots, sales models, funds, and the primary and secondary addresses of artists.
Randomizer Contracts: The Randomizer contracts play a pivotal role in generating a random hash for each token during the minting process. Once the hash is generated, it is transmitted to the Core contract, where it is stored for use in generating generative art tokens.
NextGen offers three distinct Randomizer contracts: one that utilizes Chainlink VRF (RandomizerVRF.sol), another that uses ARRNG (RandomizerRNG.sol), and a custom-made implementation (RandomizerNXT.sol) of the Randomizer contract.
NextGenAdmin.sol: The Admin contract handles the addition or removal of global or function-specific administrators who have authorization to execute certain functions within both the Core and Minter contracts.
In general, I find the NextGen codebase to be of moderate quality. This assessment is based on various factors that I will now outline:
The code comments were not helpful and made it tough to understand what the code does.
Function comments, like this one:
// mint and auction function mintAndAuction(address _recipient, string memory _tokenData, uint256 _saltfun_o, uint256 _collectionID, uint _auctionEndTime) public FunctionAdminRequired(this.mintAndAuction.selector) { require(gencore.retrievewereDataAdded(_collectionID) == true, "Add data"); uint256 collectionTokenMintIndex;
Aren't very useful, especially when dealing with long and confusing functions where the parameter names aren't always easy to grasp.
It appears that the developers did create comprehensive Natspec comments for the functions, but oddly, they included them in the function documentation on the website, not within the actual codebase:
This required me to manually go through each function on the website and copy the Natspec documentation into my local codebase in VSCode, significantly extending the auditing process's duration and reducing its efficiency.
Furthermore, in some instances, the code comments are inaccurate and fail to align with the current data or functionality. An example from the AuctionDemo.sol
contract:
I strongly advise incorporating the detailed Natspec comments directly into the codebase. This will benefit developers, auditors, and users by providing clear insights into each function's purpose. Additionally, blockchain explorers often rely on these Natspec comments, making it easier for users to interact with smart contracts via blockchain explorers.
In addition to that, make sure all the code comments are correct.
The codebase contains numerous instances of redundant code, where the same code is re-implemented and repeated multiple times. This is generally regarded as poor coding practice for several reasons:
Here are some examples of redundant code that can benefit from improvement:
updateAdminsContract
function is duplicated in all other smart contracts within the system. A more efficient approach would involve making the NextGenAdmin.sol
contract upgradable.mint
and burnOrSwapExternalToMint
functions could be consolidated into an external function “checkDelegation” for improved code organization and efficiency:if (msg.sender != ownerOfToken) { bool isAllowedToMint; isAllowedToMint = dmc.retrieveGlobalStatusOfDelegation( ownerOfToken, 0x8888888888888888888888888888888888888888, msg.sender, 1 ) || dmc.retrieveGlobalStatusOfDelegation( ownerOfToken, 0x8888888888888888888888888888888888888888, msg.sender, 2 ); if (isAllowedToMint == false) { isAllowedToMint = dmc.retrieveGlobalStatusOfDelegation( ownerOfToken, _erc721Collection, msg.sender, 1 ) || dmc.retrieveGlobalStatusOfDelegation( ownerOfToken, _erc721Collection, msg.sender, 2 ); } require(isAllowedToMint == true, "No delegation"); }
Additionally, the check for one minting per period, which is found in both the mint
and mintAndAuction
functions, can be abstracted into an external function named "enforceOnePerPeriod" for more streamlined code management:
uint timeOfLastMint; // check 1 per period if (lastMintDate[_collectionID] == 0) { // for public sale set the allowlist the same time as publicsale // @audit-info first time timeOfLastMint = collectionPhases[_collectionID].allowlistStartTime - collectionPhases[_collectionID].timePeriod; } else { timeOfLastMint = lastMintDate[_collectionID]; } // uint calculates if period has passed in order to allow minting uint tDiff = (block.timestamp - timeOfLastMint) / collectionPhases[_collectionID].timePeriod; // users are able to mint after a day passes require(tDiff >= 1, "1 mint/period");
// Exist in many functions in the Core contract require(collectionFreeze[tokenIdsToCollectionIds[_tokenId]] == false, "Data frozen");
// Exist in 2 functions in the Minter contract require(setMintingCosts[_collectionID] == true, "Set Minting Costs");
// Exist in 3 functions in the Minter contract require(gencore.retrievewereDataAdded(_collectionID) == true, "Add data");
// Exist in 5 functions in the Minter contract require(collectionTokenMintIndex <= gencore.viewTokensIndexMax(_collectionID), "No supply" );
setCollectionData()
function, which could be made more straightforward. I suggest to simplify such implementations, for instance, the current implementation appears as follows:function setCollectionData( uint256 _collectionID, address _collectionArtistAddress, uint256 _maxCollectionPurchases, uint256 _collectionTotalSupply, uint _setFinalSupplyTimeAfterMint ) public CollectionAdminRequired(_collectionID, this.setCollectionData.selector) { require( (isCollectionCreated[_collectionID] == true) && (collectionFreeze[_collectionID] == false) && (_collectionTotalSupply <= 10000000000), "err/freezed" ); if ( collectionAdditionalData[_collectionID].collectionTotalSupply == 0 ) { collectionAdditionalData[_collectionID] .collectionArtistAddress = _collectionArtistAddress; collectionAdditionalData[_collectionID] .maxCollectionPurchases = _maxCollectionPurchases; collectionAdditionalData[_collectionID] .collectionCirculationSupply = 0; collectionAdditionalData[_collectionID] .collectionTotalSupply = _collectionTotalSupply; collectionAdditionalData[_collectionID] .setFinalSupplyTimeAfterMint = _setFinalSupplyTimeAfterMint; collectionAdditionalData[_collectionID] .reservedMinTokensIndex = (_collectionID * 10000000000); collectionAdditionalData[_collectionID].reservedMaxTokensIndex = (_collectionID * 10000000000) + _collectionTotalSupply - 1; wereDataAdded[_collectionID] = true; } else if (artistSigned[_collectionID] == false) { collectionAdditionalData[_collectionID] .collectionArtistAddress = _collectionArtistAddress; collectionAdditionalData[_collectionID] .maxCollectionPurchases = _maxCollectionPurchases; collectionAdditionalData[_collectionID] .setFinalSupplyTimeAfterMint = _setFinalSupplyTimeAfterMint; } else { collectionAdditionalData[_collectionID] .maxCollectionPurchases = _maxCollectionPurchases; collectionAdditionalData[_collectionID] .setFinalSupplyTimeAfterMint = _setFinalSupplyTimeAfterMint; } }
I recommend changing it to a simpler implementation, such as:
function setCollectionData( uint256 _collectionID, address _collectionArtistAddress, uint256 _maxCollectionPurchases, uint256 _collectionTotalSupply, uint _setFinalSupplyTimeAfterMint ) public CollectionAdminRequired(_collectionID, this.setCollectionData.selector) { require( (isCollectionCreated[_collectionID] == true) && (collectionFreeze[_collectionID] == false) && (_collectionTotalSupply <= 10000000000), "err/freezed" ); // Brand new collection if (collectionAdditionalData[_collectionID].collectionTotalSupply == 0) { collectionAdditionalData[_collectionID].collectionCirculationSupply = 0; collectionAdditionalData[_collectionID].collectionTotalSupply = _collectionTotalSupply; collectionAdditionalData[_collectionID].reservedMinTokensIndex = (_collectionID * 10000000000); collectionAdditionalData[_collectionID].reservedMaxTokensIndex =(_collectionID * 10000000000) + _collectionTotalSupply - 1; wereDataAdded[_collectionID] = true; } // Artist didn't sign yet if (artistSigned[_collectionID] == false) { collectionAdditionalData[_collectionID].collectionArtistAddress = _collectionArtistAddress; } // These attributes will be updated anyhow collectionAdditionalData[_collectionID].maxCollectionPurchases = _maxCollectionPurchases; collectionAdditionalData[_collectionID].setFinalSupplyTimeAfterMint = _setFinalSupplyTimeAfterMint; }
updateAdminsContract()
function from all contracts and transition the NextGenAdmins.sol
contract to an upgradable upgradable contract.Utilizing constants instead of hardcoded values in Solidity smart contracts holds paramount importance for several reasons:
During my in-depth analysis of the code, I uncovered several instances where redundant hardcoded values were employed instead of utilizing constant variables. This practice not only hampers the code's clarity and readability but also poses a risk of introducing errors or inconsistencies. Here are some examples:
Within the minter contract, there are delegation checks that involve the NFTDelegate smart contract. These checks include a parameter of the collection contract address, and a _useCase
. At first glance, the meaning of these values wasn't immediately apparent, necessitating clarification from the sponsor in our Discord chat:
Following the sponsor's explanation, it became clear that when the address is set to 0x000...
, it denotes the absence of access, while 0x8888...
indicates access to all collections. To enhance clarity and avoid such confusion, it's advisable to replace these values with constant variables bearing more comprehensible names, like NO_DELEGATION_ACCESS
for 0x000...
and ALL_COLLECTIONS_DELEGATION_ACCESS
for 0x8888…
:
isAllowedToMint = dmc.retrieveGlobalStatusOfDelegation( _delegator, 0x8888888888888888888888888888888888888888, msg.sender, 1 ) || dmc.retrieveGlobalStatusOfDelegation( _delegator, 0x8888888888888888888888888888888888888888, msg.sender, 2 );
Another example can be found in the core contract, where the number 10000000000
recurs throughout the code. This number serves as the "gap" between token IDs among different collections, with each collection reserving a range of 10000000000
IDs:
collectionAdditionalData[_collectionID].reservedMinTokensIndex = (_collectionID * 10000000000);
Last but not least are the values 999
and 1000
within the core contract. These values play a crucial role in the updateCollectionInfo
function, guiding how collection attributes are updated:
if (_index == 1000) { collectionInfo[_collectionID].collectionName = _newCollectionName; collectionInfo[_collectionID].collectionArtist = _newCollectionArtist; collectionInfo[_collectionID].collectionDescription = _newCollectionDescription; collectionInfo[_collectionID].collectionWebsite = _newCollectionWebsite; collectionInfo[_collectionID].collectionLicense = _newCollectionLicense; collectionInfo[_collectionID].collectionLibrary = _newCollectionLibrary; collectionInfo[_collectionID].collectionScript = _newCollectionScript; } else if (_index == 999) { collectionInfo[_collectionID].collectionBaseURI = _newCollectionBaseURI; } else { collectionInfo[_collectionID].collectionScript[_index] = _newCollectionScript[0]; }
Constants.sol
. In this file include all the constants of the protocol.Constants.sol
contract.Constants.sol
contract into all the protocol's contracts and utilize the constants values.This approach will yield substantial enhancements in code readability and, importantly, serve as a safeguard against potential bugs and errors.
The documentation, particularly the smart contract documentation and function descriptions, provides valuable insights. However, it's worth noting that there are certain functions currently lacking documentation, here is the list of the most important functions which lack documentation:
NextGenCore.sol
airDropTokens
mint
burn
burnToMint
_mintProcessing
addMinterContract
updateAdminContract
setDefaultRoyalties
getTokenName
MinterContract.sol
mintAndAuction
setPrimaryAndSecondarySplits
updateCoreContract
updateAdminContract
emergencyWithdraw
NextGenAdmin.sol
registerBatchFunctionAdmin
I strongly suggest adding documentation for the following important functions in the protocol documentation.
The rationale behind separating the minter and core contracts remains unclear, as both contain functionalities related to creating and setting collection data, as well as minting and burning tokens.
This distribution of functions across both contracts complicates understanding the distinct roles of the Minter and Core contracts. Furthermore, due to this division, it's challenging to determine where minting occurs and where the creation and management of collections take place. Clarity and distinction in the contracts' purposes would greatly benefit the codebase.
mint()
function in both the Minter and Core contracts where the mint()
function within the Core contract can only be accessed by the Minter contract.NextGenCore.sol
(setCollectionData
, freezeCollection
, setTokenHash
, setFinalSupply
, setDefaultRoyalties
, updateCollectionInfo
, changeTokenData
, updateImagesAndAttributes
, freezeCollection
, ..) and in the MinterContract.sol
(setCollectionCosts
, setCollectionPhases
, updateDelegationCollection
, setPrimaryAndSecondarySplits
, proposePrimaryAddressesAndPercentages
, proposeSecondaryAddressesAndPercentages
, acceptAddressesAndPercentages
)Some of the collection and token data is stored and handled by the NextGenCore.sol
contract, for instance: collectionInfo
, isCollectionCreated
, wereDataAdded
, tokenToHash
, tokenIdsToCollectionIds
, collectionAdditionalData
, …
And some of it is managed and stored in the MinterContract.sol
contract, for instance: setMintingCosts
, collectionPhases
, ..)
There are numerous unnecessary state variables and mappings in both MinterContract.sol
and NextGenCode.sol
contracts. These primarily consist of mappings of uint256 ⇒ bool
, storing data about the status of collections and tokens.
Here's a list of mappings that can be removed, as their use case can be achieved by checking whether the data exists in another mapping: NextGenCore.sol:
isCollectionCreated
can be removed, instead a simple check such as collectionInfo[collectionIndex]._collectionName ≠ “”
can be used.wereDataAdded
can be removed, instead a simple check such as collectionAdditionalData[collectionIndex]._collectionArtistAddress ≠ address(0)
can be usedartistSigned
can be removed, instead a simple check such as artistsSignatures[_collectionID] ≠ “”
can be used.onchainMetadata
and collectionFreeze
mappings can be removed, and the booleans values can be moved of the collectionAdditionalData
mapping.MinterContract.sol:
setMintingCosts
can be removed, instead a simple check such as collectionPhases[collectionIndex].collectionMintCost ≠ 0
can be used.mintToAuctionStatus
can be removed, instead a simple check such as mintToAuctionData[mintIndex] ≠ 0
can be used.MinterContract.sol
and NextGenCore.sol
contracts: either merge them into one contract or divide them into smaller contracts, ensuring no redundancy in logic or functionality.NextGenStorage.sol
, responsible for managing the protocol storage. Both Minter and Core contracts can utilize its getters and setters for storage management.The existing test coverage for the protocol contracts is insufficient. Some contracts lack tests entirely, while others are deficient in function tests. The images below shows the current test coverage of the codebase using the Hardhat coverage module:
Testing smart contracts thoroughly and achieving a 100% test coverage is crucial for making sure they work correctly and are secure. This helps find and fix potential issues early on, reducing the risk of problems later. Full test coverage ensures that every part of the smart contracts is checked. It also makes it easier to maintain and update the protocol.
When working on a project or submitting a project for an audit or for an external developer review, following conventions is crucial. For Hardhat projects, it's best to provide the entire project structure with contracts and tests.
Organizing contracts into subfolders within the contracts\
directory, distinguishing between core logic, helper contracts, interfaces, and libraries, is also important.
In the NextGen repository, the presence of redundant hardhat
and smart-contracts
folders containing the same contracts caused confusion.
Additionally, not organizing smart contracts into these subfolders as recommended led to less organized code management. Following these practices simplifies the audit process and fosters collaboration among developers.
Create a single well-organized folder encompassing all project-related files, eliminating the redundancy of having two folders containing the same smart contracts. Additionally, within the contracts\
directory, further organization can be achieved by categorizing contracts into separate folders according to their respective roles, as previously outlined.
Ownable.sol
ImportsIn some of the smart contracts there are imports the Ownable.sol
contract which is not being utilized, here is a list of all the unused imported contracts in the protocol:
NextGenRandomizerNXT.sol
contract imports Ownable.sol
without using it nor inheriting from it.Ownable.sol
and inherit the Ownable
contract without using it (there is no use of the owner
variable or the onlyOwner
modifier).
NextGenRandomizerVRF.sol
RandomizerRNG.sol
MinterContract.sol
NextGenCore.sol
Remove all the imports and the inheritance of the Ownable
contract in all the contracts mentioned above.
Certain function names could be improved. they don't always effectively reflect the function's purpose and functionality. My suggestion is to meticulously review all functions and align their names more closely with their intended roles. Following this, carry out a comprehensive code refactoring to integrate these new, more fitting function names. Here are a few examples to illustrate:
addMinterContract()
in the NextGenCore.sol
contract refers to setting and updating the minter contract address. A name such as setMinterContract()
or updateMinterContract()
is more suitable..addRandomizer()
in the NextGenCore.sol
contract refers to setting and updating the randomizer for a collection. A name such as setCollectionRandomizer()
or updateCollectionRandomizer()
is more suitable..For each function, consider the most fitting name that accurately reflects its purpose and functionality. Proceed to refactor all function names accordingly.
The access control system in the protocol relies on the NextGenAdmin.sol contract. This contract comprises three distinct levels of access control:
updateAdminContract
FunctionFurthermore, within each contract in the protocol, there exists a function for the modification of the admin contract address (updateAdminContract
). This function effectively enables the "upgradability" of the admin contract. However, this function is safeguarded by the FunctionAdminRequired
modifier, granting authorization to both the global admin and accounts with specific function access permissions. Nonetheless, this introduces potential vulnerabilities and error-prone scenarios, with two primary risks:
updateAdminContract
function selector. Subsequently, they could update the admin contract to a non-functional state, resulting in a breakdown of the protocol.updateCoreContract
FunctionAn additional function susceptible to vulnerabilities and potential errors is the updateCoreContract
function within the Minter contract. This function serves as another central point of failure in the protocol. A mistake or malicious action could result in an update to a core contract that is non-functional, subsequently causing a complete breakdown in the functionality of the entire protocol.
addMinterContract
FunctionAn additional function susceptible to vulnerabilities and potential errors is the addMinterContract
function within the Core contract. This function serves as another central point of failure in the protocol. A mistake or malicious action could result in an update to a minter contract that is non-functional, subsequently causing a complete breakdown in the functionality of the entire protocol.
A minor note regarding the function name: it doesn't accurately convey its intended purpose. Given that there is only one Minter contract, it would be more appropriate to use names like updateMinterContract
or setMinterContract
to better align with the function's functionality.
Modifying the logic in the functions as suggested below can significantly reduce the gas usage:
In the burnOrSwapExternalToMint()
the following validation (require statement) can be moved to the top of the function to save gas in case there is issue with the _tokenId
parameter:
require( _tokenId >= burnOrSwapIds[externalCol][0] && _tokenId <= burnOrSwapIds[externalCol][1], "Token id does not match" );
The subsequent check ensuring that the highest bid status is true can be omitted, as another redundant check occurs a few lines earlier within the for loop. Each bid is already examined for a status == true
, determining whether it becomes the highestBid and index variables.
This function is inefficient and may consume a lot of gas. It might not work properly when there are many bids due to its design.
The function goes through the bidders array, which is flexible and can grow as more people bid on a token. This flexibility is risky because someone could exploit it to launch a Denial of Service (DOS) attack by bidding many times on a token, causing the bids array to expand excessively. This problem was highlighted in the bot report, so I won't go into more details.
Additionally, the number of for loops can be significantly reduced for better efficiency.
Currently, the function goes through the bidders array four times:
WinnerOrAdminRequired
modifier using returnHighestBidder
.returnHighestBid(_tokenid)
.address highestBidder = returnHighestBidder(_tokenid)
.This could be made more efficient with just 2 for loops by:
returnHighestBidder
and returnHighestBid
into a single function that goes through the array once and provides both the highest bidder and bid.WinnerOrAdminRequired
modifier and using a require statement directly in the claimAuction()
function.Another approach could reduce it to just 1 for loop by eliminating the use of returnHighestBidder
and returnHighestBid
functions and executing all the logic in a single for loop within the claimAuction()
function.
In essence, the Nextgen project holds the potential to be a compelling protocol that captures the interest of the NFT community and addresses intriguing challenges. However, it is crucial to tackle the identified risks and implement measures to safeguard the protocol against potential malicious attacks. A substantial refactor of the protocol implementation and robust testing are important.
Furthermore, enhancing documentation and code comments is recommended to foster better understanding and collaboration among developers and security researchers.
50 hours
#0 - c4-pre-sort
2023-11-27T14:58:18Z
141345 marked the issue as sufficient quality report
#1 - c4-judge
2023-12-07T16:50:36Z
alex-ppg marked the issue as grade-a