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: 33/243
Findings: 7
Award: $396.76
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: btk
Also found by: 00xSEV, 0x175, 0x180db, 0x3b, 0xAlix2, 0xJuda, 0xpiken, 0xraion, 3th, 836541, Al-Qa-qa, AvantGard, Aymen0909, Beosin, ChrisTina, DarkTower, DeFiHackLabs, EricWWFCP, Kose, Kow, KupiaSec, MrPotatoMagic, Neo_Granicen, PENGUN, PetarTolev, Ruhum, Soul22, SovaSlava, SpicyMeatball, Talfao, The_Kakers, Toshii, Tricko, VAD37, Viktor_Cortess, ZdravkoHr, _eperezok, alexxander, audityourcontracts, ayden, bird-flu, bronze_pickaxe, codynhat, critical-or-high, danielles0xG, degensec, droptpackets, evmboi32, fibonacci, flacko, gumgumzum, ilchovski, immeas, innertia, jacopod, joesan, ke1caM, kk_krish, mojito_auditor, nuthan2x, phoenixV110, pontifex, r0ck3tz, sces60107, seeques, sl1, smiling_heretic, stackachu, t0x1c, trachev, turvy_fuzz, ubl4nk, ustas, xAriextz, xuwinnie, y4y
0.152 USDC - $0.15
Mint function in minter contract: https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/MinterContract.sol#L196-L254 Mint function in core contract: https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/NextGenCore.sol#L189-L200
When minting NFTs, users will using the mint
function. This function will mint a NFT using the _safeMint
function. The problem is that this mint will be done before crucial variables are updated.
Mint before checking the salesOption == 3
conditions are fulfilled in MinterContract.sol
:
for(uint256 i = 0; i < _numberOfTokens; i++) { uint256 mintIndex = gencore.viewTokensIndexMin(col) + gencore.viewCirSupply(col); gencore.mint(mintIndex, mintingAddress, _mintTo, tokData, _saltfun_o, col, phase); } collectionTotalAmount[col] = collectionTotalAmount[col] + msg.value; // control mechanism for sale option 3 if (collectionPhases[col].salesOption == 3) { uint timeOfLastMint; if (lastMintDate[col] == 0) { // for public sale set the allowlist the same time as publicsale timeOfLastMint = collectionPhases[col].allowlistStartTime - collectionPhases[col].timePeriod; } else { timeOfLastMint = lastMintDate[col]; } // uint calculates if period has passed in order to allow minting uint tDiff = (block.timestamp - timeOfLastMint) / collectionPhases[col].timePeriod; // users are able to mint after a day passes require(tDiff>=1 && _numberOfTokens == 1, "1 mint/period"); lastMintDate[col] = collectionPhases[col].allowlistStartTime + (collectionPhases[col].timePeriod * (gencore.viewCirSupply(col) - 1)); }
Mint before updating tokensMintedAllowlistAddress
and tokensMintedPerAddress
in NextGenCore.sol
mint
.
_mintProcessing(mintIndex, _mintTo, _tokenData, _collectionID, _saltfun_o); if (phase == 1) { tokensMintedAllowlistAddress[_collectionID][_mintingAddress] = tokensMintedAllowlistAddress[_collectionID][_mintingAddress] + 1; } else { tokensMintedPerAddress[_collectionID][_mintingAddress] = tokensMintedPerAddress[_collectionID][_mintingAddress] + 1; }
The vulnerability arises during the execution of the _safeMint
function, which involves the usage of _checkOnERC721Received
. Exploiting this, attackers can repeatedly mint NFTs by employing a smart contract that reenters the function upon receiving the call of _checkOnERC721Received
. The minting process occurs prior to updating crucial variables such as tokensMintedAllowlistAddress
and tokensMintedPerAddress
, as well as implementing the control mechanism for sale option 3. This oversight allows attackers to mint the entire collection without proper limitations.
The reentrancy vulnerability exposes the system to significant risks, allowing attackers to exploit the following critical issues:
tokensMintedAllowlistAddress
)tokensMintedPerAddress
)salesOption == 3
), enabling unlimited NFT minting within a specific timeframeIn some cases, attackers can also drain protocol's funds.
Scenario
calculateTokenHash
function in RandomizerVRF.sol
during NFT minting. This involves the usage of Link tokens to generate randomness.Attack Path
mint
function upon NFT receipt using the onERC721Received
function.mint
function using the malicious smart contract.mint
function through onERC721Received
.tokensMintedAllowlistAddress
remains unaltered, the mint amount for the allowlist check is bypassed.This vulnerability results in the attacker being able to mint the whole collection for free and the protocol suffering substantial financial losses, considering the requirement for a significant number of Link tokens to generate the unique hash for all the NFTs. While potential gas limit issues might arise, this scenario effectively demonstrates the concept. Additionally, this vulnerability can be exploited to bypass the mint amount limits during public sales and the control mechanism for sale option 3.
Manual review
To address this vulnerability, it is imperative to update tokensMintedAllowlistAddress
and tokensMintedPerAddress
before executing the _mintProcessing
function within NextGenCore.sol
's mint
function. Furthermore, implement the control mechanism for sale option 3 in the MinterContract.sol
before invoking the mint
function in NextGenCore.sol
.
Reentrancy
#0 - c4-pre-sort
2023-11-16T01:51:39Z
141345 marked the issue as duplicate of #51
#1 - c4-pre-sort
2023-11-26T14:00:00Z
141345 marked the issue as duplicate of #1742
#2 - c4-judge
2023-12-08T16:36:52Z
alex-ppg marked the issue as satisfactory
🌟 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
Function for participating in an auction: https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L57-L61 Function for cancelling a bid: https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L124-L130
participateToAuction
function, where making the currently highest bid is necessary for participation.require(msg.value > returnHighestBid(_tokenid) && block.timestamp <= minter.getAuctionEndTime(_tokenid) && minter.getAuctionStatus(_tokenid) == true);
cancelBid
function.The problem is that bids can be cancelled until the last second of the auction, with any kind of penalization.
Users can manipulate auctions by placing big fake bids, subsequently canceling them to secure the auction victory at minimal cost.
Attack Path
participateToAuction
and becoming the highest bidder.cancelBid
but still remains being the highest bidder.claimAuction
, securing the NFT for a mere 1 wei.It's worth noting that this tactic can also be employed by collection teams to manipulate auctions, creating a facade of high demand or interest by placing fake bids and retracting them if necessary to ensure a favorable outcome.
Manual review.
To address this vulnerability, a comprehensive overhaul of the bid and auction system is advisable, which might involve the removal of the ability to cancel a bid entirely. Another option can be just refunding a % of the bid to users who decide to cancel their bids.
Timing
#0 - c4-pre-sort
2023-11-15T09:09:25Z
141345 marked the issue as duplicate of #962
#1 - c4-judge
2023-12-02T15:13:08Z
alex-ppg marked the issue as not a duplicate
#2 - c4-judge
2023-12-02T15:16:40Z
alex-ppg marked the issue as duplicate of #1784
#3 - c4-judge
2023-12-07T11:49:44Z
alex-ppg marked the issue as duplicate of #1323
#4 - c4-judge
2023-12-08T17:26:33Z
alex-ppg marked the issue as partial-50
#5 - c4-judge
2023-12-08T17:28:17Z
alex-ppg marked the issue as satisfactory
#6 - c4-judge
2023-12-08T18:17:36Z
alex-ppg marked the issue as partial-50
#7 - c4-judge
2023-12-09T00:20:29Z
alex-ppg changed the severity to 3 (High Risk)
🌟 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
Function for claiming an auction: https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L104-L120 Function for cancelling a bid: https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L124-L130
This vulnerability has the potential for varying impacts, given the numerous ways it can be exploited. The one with the highest impact: a malicious user could exploit the vulnerability to win any auction at no cost and steal all of the funds from the contract.
In the claimAuction
function, users receive refunds if their bids fail to secure the auction. However, even after receiving their refunds, users can still cancel their bids due to the failure to update the bid status
during the refund process.
As we can see, claimAuction
can be called when block.timestamp >= minter.getAuctionEndTime
, and cancelBid
can be called when block.timestamp <= minter.getAuctionEndTime
. This means that when block.timestamp == minter.getAuctionEndTime
we can call both of them.
This arises multiple different issues with multiple ways of exploiting it. Here I will explain the one with the highest impact that I have found:
Attack Scenario
block.timestamp == minter.getAuctionEndTime
, with 101 ETH allocated to the NFT owner and a refund of 100 ETH for the unsuccessful bid. Additionally, all other AuctionB participants receive refunds.
4.1. Notably, the contract now holds 201 ETH from AuctionA bids, and the status
of the attacker's bids remains true.block.timestamp == minter.getAuctionEndTime
condition still holds, leveraging the unaltered status
during the claim process to execute the cancellation.Through this attack, the attacker succeeds in securing the NFT and steals all the funds from the contract, completely draining it. Additionally, similar attacks allow for the theft of various fund amounts, depending on the specific bid configurations. For instance, an attacker could deploy multiple bids of 5 ETH, securing the auction with a smaller bid and stealing even more funds from AuctionA.
Manual review.
Implement an update mechanism to set the bid status to false during the claim process for all the bids of the corresponding auction. Also, change the check in claimAuction
from block.timestamp >= minter.getAuctionEndTime
to block.timestamp > minter.getAuctionEndTime
.
Invalid Validation
#0 - c4-pre-sort
2023-11-15T09:06:34Z
141345 marked the issue as duplicate of #962
#1 - c4-pre-sort
2023-11-15T09:06:38Z
141345 marked the issue as insufficient quality report
#2 - c4-pre-sort
2023-11-15T09:06:48Z
141345 marked the issue as remove high or low quality report
#3 - c4-judge
2023-12-01T15:50:30Z
alex-ppg marked the issue as not a duplicate
#4 - c4-judge
2023-12-01T15:50:39Z
alex-ppg marked the issue as duplicate of #1788
#5 - c4-judge
2023-12-01T15:53:53Z
alex-ppg marked the issue as not a duplicate
#6 - c4-judge
2023-12-01T15:54:02Z
alex-ppg marked the issue as duplicate of #1788
#7 - c4-judge
2023-12-08T18:17:32Z
alex-ppg marked the issue as satisfactory
71.228 USDC - $71.23
mint function: https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/MinterContract.sol#L196-L254 check in mint function to assure we are in the public sale window: https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/MinterContract.sol#L221 function for getting the price: https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/MinterContract.sol#L530-L570 check in the getPrice to assure we are in the public sale window: https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/MinterContract.sol#L540
Due to an error in the getPrices()
function, users might purchase NFTs at the collectionMintCost
instead of the intended decreased rate, leading to potential financial losses depending on the specific linear descending sale model configuration.
This vulnerability can have a significant impact in instances where a collection utilizes a linear descending sale model with a substantial disparity between the starting and ending prices. Consider the following scenario to understand the potential impact and how users could incur financial losses.
The problem arises because in getPrice
we don't take into account the last second of the public sale for the calculation of the price of the NFT when the sale model is of type 2 (descending sale model). This will make the calculation wrong in that specific case, returning a wrong value.
Context
collectionMintCost
)collectionEndMintCost
)Illustrative Situation Affecting Users
mint
function at block.timestamp == collectionPhases[_collectionId].publicEndTime
.
1.2. Due to the error in getPrice
function, the function will return the collectionMintCost
. This happens because the conditional block.timestamp < collectionPhases[_collectionId].publicEndTime
is not met in the else if
, which will make the call jump to the else
. This shouldn't happen since we are in the public sale time of a Descending Sale model, not a Fixed Price Sale.
1.3. As a result, Bob ends up paying 100 ETH for the NFT, significantly more than the anticipated 1 ETH.Manual review.
Rectify the else if
condition in the getPrices
function from block.timestamp < collectionPhases[_collectionId].publicEndTime
to block.timestamp <= collectionPhases[_collectionId].publicEndTime
to ensure the accurate calculation of NFT prices during the sale.
Timing
#0 - c4-pre-sort
2023-11-16T01:40:19Z
141345 marked the issue as duplicate of #1391
#1 - c4-judge
2023-12-08T21:42:29Z
alex-ppg marked the issue as satisfactory
🌟 Selected for report: bird-flu
Also found by: 00decree, 0xAadi, AS, Audinarey, DeFiHackLabs, Eigenvectors, Fitro, Hama, Kaysoft, Krace, REKCAH, SovaSlava, The_Kakers, Viktor_Cortess, cartlex_, degensec, devival, evmboi32, funkornaut, jacopod, openwide, peanuts, rotcivegaf, smiling_heretic, xAriextz, xiao
25.2356 USDC - $25.24
Function for claiming an auction: https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L104-L120 Line where the highest bid is payed to the wrong address: https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L113
The AuctionDemo.sol
contract erroneously sends the highest bid to the wrong address during the auction claim process. Consequently, the designated collection owners do not receive the expected ETH upon auction completion.
As indicated in the Main invariants section of the contest page: "The highest bidder will receive the token after an auction finishes, the owner of the token will receive the funds and all other participants will get refunded." Link to the contest page
In the function responsible for auction claiming, the smart contract sends the funds to the contract owner rather than the owner of the token:
(bool success, ) = payable(owner()).call{value: highestBid}("");
This results in the owner of the token not receiving the expected ETH from the auction, potentially leading to a loss of funds and possible complications depending on the identity of the contract owner.
Manual review.
To correct this issue, it is advisable to replace the line:
(bool success, ) = payable(owner()).call{value: highestBid}("");
with
(bool success, ) = payable(ownerOfToken).call{value: highestBid}("");
to ensure that the funds are accurately directed to the owner of the token.
ETH-Transfer
#0 - c4-pre-sort
2023-11-15T09:07:51Z
141345 marked the issue as duplicate of #245
#1 - c4-judge
2023-12-08T22:27:53Z
alex-ppg marked the issue as satisfactory
#2 - c4-judge
2023-12-09T00:22:20Z
alex-ppg changed the severity to 2 (Med Risk)
🌟 Selected for report: The_Kakers
Also found by: 0xblackskull, BugzyVonBuggernaut, Draiakoo, Stryder, VAD37, alexfilippov314, mrudenko, rotcivegaf, xAriextz, xuwinnie, zach
275.7777 USDC - $275.78
Function for signing a collection: https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/NextGenCore.sol#L257-L263 Function for setting collection's artist address: https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/NextGenCore.sol#L147-L166
The setCollectionData()
function allows the owner of a collection to set the data of the collection. As we can see , the collectionArtistAddress
is only able to be set in the beginning and won't be able to be changed if the collection is already signed:
else if (artistSigned[_collectionID] == false) { collectionAdditionalData[_collectionID].collectionArtistAddress = _collectionArtistAddress;
The artistSignature()
function allows an artist of a collection to sign it. This will increase the credibility in the collection since the artist is assuring that he is in fact the artist by signing it with his wallet. Once its signed, the wallet shouldn't be able to be changed:
function artistSignature(uint256 _collectionID, string memory _signature) public { require(msg.sender == collectionAdditionalData[_collectionID].collectionArtistAddress, "Only artist"); require(artistSigned[_collectionID] == false, "Already Signed"); artistsSignatures[_collectionID] = _signature; artistSigned[_collectionID] = true; }
The problem is that a collection owner can trick this checks, and give a false impression of the collection being signed by anyone he wants.
The vulnerability allows for the bypass of the verification checks in NextGenCore.sol
that assure the artist of a collection and its signature. Consequently, malevolent actors can manipulate the appearance of a collection, misleading users into believing that a specific address has signed the collection when, in reality, it has not. Even if collection admins are trusted, there are checks for preventing this scenario, so bypassing them is still an issue.
Attack Scenario
setCollectionData
, assigning their own address as the collectionArtistAddress
and setting collectionTotalSupply
to 0.artistSignature
.setCollectionData
, this time specifying a famous and trusted address, such as Vitalik's address, as the collectionArtistAddress
. Additionally, they configure the required _collectionTotalSupply
.
3.1. This is possible because the collectionTotalSupply == 0
, but the collection has already been signed, which is not controlled with the checks.Consequently, the collection falsely appears to have been signed by Vitalik's address, when in actuality, it has not. This deceptive practice can be utilized to manipulate users into purchasing the NFTs from the misrepresented collection.
Even if collections admins are trusted, existing checks are in place to prevent such occurrences. However, this attack path effectively bypasses these security measures. Hence, I consider the severity level for this vulnerability to be a medium, rather than high.
Manual review
It is crucial to implement a validation process ensuring that the _collectionTotalSupply
is always greater than 0 within the setCollectionData
function to mitigate this vulnerability effectively.
Invalid Validation
#0 - c4-pre-sort
2023-11-16T01:50:19Z
141345 marked the issue as sufficient quality report
#1 - c4-sponsor
2023-11-23T09:32:26Z
a2rocket (sponsor) disputed
#2 - a2rocket
2023-11-23T09:32:37Z
Admins are considered fully trusted.
#3 - c4-judge
2023-12-05T23:39:32Z
alex-ppg marked the issue as duplicate of #478
#4 - c4-judge
2023-12-08T21:57:02Z
alex-ppg marked the issue as satisfactory
🌟 Selected for report: HChang26
Also found by: 0x3b, 0xMAKEOUTHILL, 0xSwahili, 0xarno, ABA, DeFiHackLabs, Eigenvectors, Haipls, Kow, MrPotatoMagic, Neon2835, Nyx, Zac, alexfilippov314, ayden, c3phas, immeas, innertia, lsaudit, merlin, mojito_auditor, oakcobalt, ohm, oualidpro, peanuts, phoenixV110, sces60107, t0x1c, tnquanghuy0512, ubl4nk, volodya, xAriextz
10.9728 USDC - $10.97
Timestamp check when bidding: https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L58 Timestamp check when claiming auction: https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L105
participateToAuction
function, which permits bidding until block.timestamp <= minter.getAuctionEndTime()
:require(msg.value > returnHighestBid(_tokenid) && block.timestamp <= minter.getAuctionEndTime(_tokenid) && minter.getAuctionStatus(_tokenid) == true);
claimAuction
function, which allows claiming until block.timestamp >= minter.getAuctionEndTime()
.require(block.timestamp >= minter.getAuctionEndTime(_tokenid) && auctionClaim[_tokenid] == false && minter.getAuctionStatus(_tokenid) == true);
In auctions where users place last-second bids, there is a potential for these users to lose the auction to the current highest bidder who claims it just before them, and lose the funds they used to bid.
In such auctions, it is common practice for bidders to place their bids at the last moment to prevent other bidders from reacting and outbidding them.
Example Illustrating the Issue
participateToAuction
function with a msg.value of 2 ETH, just when block.timestamp == minter.getAuctionEndTime
.claimAuction
when block.timestamp == minter.getAuctionEndTime
and receiving the NFT as the current highest bidder.auctionInfoData
array yet. Additionally, note there is no way of withdrawing Bob's funds from the contract even for the admin's, resulting in these funds being stuck in the contract forever.Alice becomes the winner, while Bob, who should have won, loses the auction, leading to an inequitable auction process. Also, Bob will lose the funds he used to bid.
Another problem that arises from the same check is that if an admin calls claimAuction
in block.timestamp == minter.getAuctionEndTime(_tokenid)
and a user decides to cancel the bid at the same time, he won't be able to do it.
Manual review.
Modify the check in the claimAuction
function from block.timestamp >= minter.getAuctionEndTime(_tokenid)
to block.timestamp > minter.getAuctionEndTime(_tokenid)
.
Invalid Validation
#0 - c4-pre-sort
2023-11-14T23:33:41Z
141345 marked the issue as duplicate of #962
#1 - c4-judge
2023-12-02T15:33:28Z
alex-ppg marked the issue as not a duplicate
#2 - c4-judge
2023-12-02T15:35:37Z
alex-ppg marked the issue as duplicate of #1926
#3 - c4-judge
2023-12-08T18:52:36Z
alex-ppg marked the issue as satisfactory
#4 - c4-judge
2023-12-09T00:21:41Z
alex-ppg changed the severity to 2 (Med Risk)
🌟 Selected for report: The_Kakers
Also found by: 00xSEV, 0x3b, Arabadzhiev, DeFiHackLabs, Fulum, Madalad, MrPotatoMagic, SpicyMeatball, Tadev, ZanyBonzy, ZdravkoHr, alexfilippov314, audityourcontracts, cheatc0d3, devival, dy, evmboi32, immeas, lsaudit, mrudenko, oakcobalt, oualidpro, pipidu83, r0ck3tz, rishabh, rotcivegaf, tpiliposian, xAriextz
13.3948 USDC - $13.39
Link: https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L58
In AuctionDemo.sol we can place bids in an auction by calling participateToAuction
. This function requires that msg.value > returnHighestBid
. This means that any new bids which is bigger than the previous one will be accepted, even if the bid is only 1 wei bigger. This approach doesn't seem to lead to big problems, but in my opinion this is an unfair system for the users. I think that for being able to place a bid you should consider forcing the new bid to be at least a % bigger than the current biggest bid. For example: if the biggest bid right now is 1 ETH, for placing a bid users need to send at least 1.01 ETH, making it 1% bigger than the last bid.
returnHighestBidder
doesn't update highBid
Link to returnHighestBidder
: https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L87-L100
This function is for getting the highest bidder in a certain auction. As we can see, it iterates through the array and checks if the bid done is bigger than highBid
. This check will always pass since highBid
is not updated and it's value is always 0. This doesn't represent bigger problems since the array is already sorted and the status
is checked for getting the index with the biggest bid.
Link: https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L112
When an auction is claimed using claimAuction
the NFT is transferred from the owner of the NFT directly to the highest bidder. Even if the address that owns that NFT is trusted, I think a better approach would be sending the NFT directly to the AuctionDemo.sol contract when using mintAndAuction
in MinterContract.sol, instead of sending it to a trusted address.
Some examples:
Even if this issue was already in the automated findings, I think it's worth explaining the impact this could have in this specific context.
Users get their ETH refunded when not winning an auction. Also, they can cancel their bids before an auction is finished and get their ETH refunded. Not checking that the ETH was correctly transferred from AuctionDemo.sol
to the users will result in this ETH getting stuck in the contract, since there is no possibility of withdrawing it in any other way. This happens because even if the ETH transfer doesn't work the transaction will still succeed. In the case of cancelling a bid, the status
of the bid is still updated and users won't be able to call the function again. In the case of claiming an auction, we have a similar case, since auctionClaim
will still be updated.
I want to remark that there is no way of getting the stuck ETH back even for the admins, because there are no functions like an emergencyWithdraw
or similar. This will result in a loss of funds for the users, having their ETH stuck in the contract.
For fixing this issue we should check that success
is true after making the ETH transfer using the call
.
maxCollectionPurchases
for the collections can be bypassed by minting with another walletLink: https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/MinterContract.sol#L224
This check isn't really doing anything, since the same user could just use another wallet for minting more NFTs. There is no possible way of checking how many NFTs each user buys. In my opinion, this check will hurt the users and the protocol. The reason is that "normal" and unexperienced users wouldn't think about changing wallets for buying more NFTs, and more experienced users would take advantage of this. If there is a maxCollectionPurchases == 1
in a collection with 1000 NFTs, users would think that there are 1000 holders. This is in fact not true, since an experiences user could use a script or manually buy 500 NFTs by simply using different wallets. In my opinion it is an unnecessary check, because you will only limit the unexperienced users.
Example:
Even if this issue was already in the automated findings, I think it's worth explaining the impact this could have in this specific context.
As there is no function for withdrawing excess funds in MinterContract.sol
, all these funds will get stuck forever in the contract. I would advise to change the check from msg.value >= getPrice()
to msg.value == getPrice()
to prevent any lose of funds.
Links:
In order to have better precision when calculating the funds to be sent to the artists, I would suggest using Basis Points for assigning the percentages. For instance, 100% would be represented as 10_000. This will prevent possible rounding errors and less funds being distributed. This won't be a problem when there is a lot of ETH to be sent, but for example if there is only 1 ETH to be distributed across the 5 wallets, with some of them having very low percentages, this could be a bigger issue.
Link: https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/NextGenCore.sol#L170-L174 Even if a collection has been frozen, it's randomizer can still be changed. Even if this function would be called by a trusted role, I think it shouldn't be possible. Freezing a collection gives users the certainty that collection's importan info, etc. won't be changed. Nonetheless, this will entail users' getting different token hashes than they thought.
Link: https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/NextGenCore.sol#L170-L174 Even if a collection has been frozen, it's randomizer can still be changed. Even if this function would be called by a trusted role, I think it shouldn't be possible. Freezing a collection gives users the certainty that collection's importan info, etc. won't be changed. Nonetheless, this will entail users' getting different token hashes than they thought.
Link: https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/RandomizerVRF.sol#L28
Even if the value returned from Chainlink's VRFv2 won't have a big impact in the particular context of this project, it is advised to use a value which is bigger than the common length of the block reorgs that can happen in the particular chain. As this project is going to be deployed only in Ethereum Mainnet, changing it to requestConfirmations = 10
would be more than enough. Reorgs have already happened in Ethereum. One of the biggest ones was 7 blocks, not so long ago.
#0 - 141345
2023-11-25T08:22:22Z
712 xAriextz l r nc 0 2 1
L 1 n L 2 d dup of https://github.com/code-423n4/2023-10-nextgen-findings/issues/586 L 3 d dup of https://github.com/code-423n4/2023-10-nextgen-findings/issues/364 L 4 i bot L 5 i bot L 6 d dup of https://github.com/code-423n4/2023-10-nextgen-findings/issues/728 L 7 i bot L 8 r L 9 d dup of https://github.com/code-423n4/2023-10-nextgen-findings/issues/1163 L 10 i L 11 r
#1 - c4-pre-sort
2023-11-25T08:27:46Z
141345 marked the issue as sufficient quality report
#2 - alex-ppg
2023-12-08T15:30:07Z
The Warden's QA report has been graded B based on a score of 10 combined with a manual review per the relevant QA guideline document located here.
The Warden's submission's score was assessed based on the following accepted findings:
#3 - c4-judge
2023-12-08T15:30:13Z
alex-ppg marked the issue as grade-b