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: 23/243
Findings: 5
Award: $593.25
🌟 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.038 USDC - $0.04
Severity : Medium
MinterContract.mint
function, when public sale is happening, there's a check shown below to check for max allowed to mint per address. But it can bypassed to mint more per address.require(gencore.retrieveTokensMintedPublicPerAddress(col, msg.sender) + _numberOfTokens <= gencore.viewMaxAllowance(col), "Max");
Gencore.mint
functionManual review & foundry testing
function mint(uint256 mintIndex, address _mintingAddress , address _mintTo, string memory _tokenData, uint256 _saltfun_o, uint256 _collectionID, uint256 phase) external { require(msg.sender == minterContract, "Caller is not the Minter Contract"); collectionAdditionalData[_collectionID].collectionCirculationSupply = collectionAdditionalData[_collectionID].collectionCirculationSupply + 1; if (collectionAdditionalData[_collectionID].collectionTotalSupply >= collectionAdditionalData[_collectionID].collectionCirculationSupply) { - _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; } + _mintProcessing(mintIndex, _mintTo, _tokenData, _collectionID, _saltfun_o); } }
Reentrancy
#0 - c4-pre-sort
2023-11-18T07:36:05Z
141345 marked the issue as duplicate of #51
#1 - c4-pre-sort
2023-11-26T14:03:40Z
141345 marked the issue as duplicate of #1742
#2 - c4-judge
2023-12-08T16:21:41Z
alex-ppg marked the issue as satisfactory
#3 - c4-judge
2023-12-08T16:21:49Z
alex-ppg marked the issue as partial-25
#4 - c4-judge
2023-12-09T00:18:52Z
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
Severity : High (Ether loss)
When Auction ends (block.timestamp == AuctionEndTime
),
cancelBid
function to steal the ether.cancelAllBids
reentered.Here's he exact steps to execute this attack:
block.timestamp == AuctionEndTime
, by attacker being a highest bidder, call claimAuction
cancelAllBids
function to claimhis bid.block.timestamp == AuctionEndTime
, by attacker being a highest bidder, call claimAuction
cancelAllBids
to also claim extra ether.Manual review & foundry testing
Follow any of these mitigation:
function claimAuction(uint256 _tokenid) public WinnerOrAdminRequired(_tokenid,this.claimAuction.selector){ - require(block.timestamp >= minter.getAuctionEndTime(_tokenid) && auctionClaim[_tokenid] == false && minter.getAuctionStatus(_tokenid) == true); + require(block.timestamp > minter.getAuctionEndTime(_tokenid) && auctionClaim[_tokenid] == false && minter.getAuctionStatus(_tokenid) == true); auctionClaim[_tokenid] = true; uint256 highestBid = returnHighestBid(_tokenid); address ownerOfToken = IERC721(gencore).ownerOf(_tokenid); address highestBidder = returnHighestBidder(_tokenid); for (uint256 i=0; i< auctionInfoData[_tokenid].length; i ++) { if (auctionInfoData[_tokenid][i].bidder == highestBidder && auctionInfoData[_tokenid][i].bid == highestBid && auctionInfoData[_tokenid][i].status == true) { IERC721(gencore).safeTransferFrom(ownerOfToken, highestBidder, _tokenid); (bool success, ) = payable(owner()).call{value: highestBid}(""); emit ClaimAuction(owner(), _tokenid, success, highestBid); } else if (auctionInfoData[_tokenid][i].status == true) { (bool success, ) = payable(auctionInfoData[_tokenid][i].bidder).call{value: auctionInfoData[_tokenid][i].bid}(""); emit Refund(auctionInfoData[_tokenid][i].bidder, _tokenid, success, highestBid); } else { } } } ## Assessed type Reentrancy
#0 - c4-pre-sort
2023-11-15T07:25:48Z
141345 marked the issue as duplicate of #962
#1 - c4-judge
2023-12-04T21:41:15Z
alex-ppg marked the issue as duplicate of #1323
#2 - c4-judge
2023-12-08T18:02:13Z
alex-ppg marked the issue as satisfactory
557.1267 USDC - $557.13
Severity : High Likelihood : low
lastMintDate
on salesOption 3 depends on circulating supply which doesnt count in burnAmount
. So circulating supply can be inflated so high due to the if block on genCore.mint
will not trigger when total supply is exceeded, but circulating supply is updated without minting.
So, after inflating, if salesOption is changed from 1 or 2 to 3. The lastMint date is hugely inflated now, we cant mint new tokens. Ofcourse the collection owner should change the following to let this happen:
Manual review & foundry testing
Make last mint calulation to be depended on previous last mint data.
Modify MinterContract.mint's if block
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)); + lastMintDate[col] += timeperiod }
Context
#0 - c4-pre-sort
2023-11-18T07:35:33Z
141345 marked the issue as duplicate of #688
#1 - c4-judge
2023-12-01T21:06:57Z
alex-ppg marked the issue as not a duplicate
#2 - c4-judge
2023-12-07T10:44:29Z
alex-ppg marked the issue as duplicate of #881
#3 - c4-judge
2023-12-07T12:01:48Z
alex-ppg marked the issue as duplicate of #2012
#4 - c4-judge
2023-12-07T12:25:22Z
alex-ppg changed the severity to 3 (High Risk)
#5 - c4-judge
2023-12-08T21:06:44Z
alex-ppg marked the issue as partial-50
35.614 USDC - $35.61
Severity : High (ex: a salesOption that increases price over time, will incur a heavy loss if token id minted at exact publicEndTime
)
MinterContract.mint
function, when minting, MinterContract.getPrice
can be abused for different pricing for same collection. Due to following improper timestamp validation.allowlistStartTime
or publicEndTime
.allowlistStartTime
& publicEndTime
instead of <= and >=.allowlistStartTime
or publicEndTime
and get away with exact collectionMintCost
instead of price increase or decrease due to the time difference, number of mints, rate and other collection costs parametersManual review & foundry testing
function getPrice(uint256 _collectionId) public view returns (uint256) { uint tDiff; if (collectionPhases[_collectionId].salesOption == 3) { // increase minting price by mintcost / collectionPhases[_collectionId].rate every mint (1mint/period) // to get the price rate needs to be set if (collectionPhases[_collectionId].rate > 0) { return collectionPhases[_collectionId].collectionMintCost + ((collectionPhases[_collectionId].collectionMintCost / collectionPhases[_collectionId].rate) * gencore.viewCirSupply(_collectionId)); } else { return collectionPhases[_collectionId].collectionMintCost; } - } else if (collectionPhases[_collectionId].salesOption == 2 && block.timestamp > collectionPhases[_collectionId].allowlistStartTime && block.timestamp < collectionPhases[_collectionId].publicEndTime){ + } else if (collectionPhases[_collectionId].salesOption == 2 && block.timestamp >= collectionPhases[_collectionId].allowlistStartTime && block.timestamp <= collectionPhases[_collectionId].publicEndTime){ tDiff = (block.timestamp - collectionPhases[_collectionId].allowlistStartTime) / collectionPhases[_collectionId].timePeriod; uint256 price; uint256 decreaserate; if (collectionPhases[_collectionId].rate == 0) { price = collectionPhases[_collectionId].collectionMintCost / (tDiff + 1); decreaserate = ((price - (collectionPhases[_collectionId].collectionMintCost / (tDiff + 2))) / collectionPhases[_collectionId].timePeriod) * ((block.timestamp - (tDiff * collectionPhases[_collectionId].timePeriod) - collectionPhases[_collectionId].allowlistStartTime)); } else { if (((collectionPhases[_collectionId].collectionMintCost - collectionPhases[_collectionId].collectionEndMintCost) / (collectionPhases[_collectionId].rate)) > tDiff) { price = collectionPhases[_collectionId].collectionMintCost - (tDiff * collectionPhases[_collectionId].rate); } else { price = collectionPhases[_collectionId].collectionEndMintCost; } } if (price - decreaserate > collectionPhases[_collectionId].collectionEndMintCost) { return price - decreaserate; } else { return collectionPhases[_collectionId].collectionEndMintCost; } } else { // fixed price return collectionPhases[_collectionId].collectionMintCost; } }
Invalid Validation
#0 - c4-pre-sort
2023-11-16T01:41:56Z
141345 marked the issue as duplicate of #1391
#1 - c4-judge
2023-12-08T21:40:31Z
alex-ppg marked the issue as partial-50
#2 - c4-judge
2023-12-09T01:50:25Z
alex-ppg changed the severity to 2 (Med Risk)
🌟 Selected for report: The_Kakers
Also found by: 00decree, 00xSEV, 0x180db, 0x3b, 0xJuda, 0x_6a70, 0xarno, 0xpiken, Arabadzhiev, Bauchibred, BugsFinder0x, BugzyVonBuggernaut, ChrisTina, DeFiHackLabs, Delvir0, HChang26, Haipls, Jiamin, Juntao, KupiaSec, Madalad, Neon2835, Nyx, Ocean_Sky, SpicyMeatball, Talfao, Taylor_Webb, Timenov, Tricko, ZdravkoHr, _eperezok, alexxander, amaechieth, bdmcbri, bronze_pickaxe, circlelooper, crunch, cu5t0mpeo, dimulski, fibonacci, funkornaut, immeas, ke1caM, lsaudit, nuthan2x, r0ck3tz, rotcivegaf, spark, tnquanghuy0512, twcctop, xeros
0.4703 USDC - $0.47
Severity : High (loss of rest bidder funds) likelihood : medium (the highest bidder should be a contract)
When Auction ends, the highest bidder can claim the nft, but being a contractas highest bidder can resfuse to implement onERC721Received
fallback, can cause DOS. So the owner of token will not get his ether. And there's no way to refund the bidders if this attack is executed, since you cannot cancel bids after auction ends.
Here's he exact steps to execute this attack:
minter.mintAndAuction()
which airdrops the token to recipient and created auction status to true.claimAuction
will always revert. And the owner of that tokenId it airdropped to can also refust to accept the ether, by beibng a contract (low likelihood).cancelBid
functions because its past the endTime, and now the ether is stuck.Manual review & foundry testing
In AuctionDemo.participateToAuction
, use transfer instead of safeTrnafer. Th attacker can afford to lose if doesn't implement the ERC721 receiving standard.
function claimAuction(uint256 _tokenid) public WinnerOrAdminRequired(_tokenid,this.claimAuction.selector){ require(block.timestamp >= minter.getAuctionEndTime(_tokenid) && auctionClaim[_tokenid] == false && minter.getAuctionStatus(_tokenid) == true); auctionClaim[_tokenid] = true; uint256 highestBid = returnHighestBid(_tokenid); address ownerOfToken = IERC721(gencore).ownerOf(_tokenid); address highestBidder = returnHighestBidder(_tokenid); for (uint256 i=0; i< auctionInfoData[_tokenid].length; i ++) { if (auctionInfoData[_tokenid][i].bidder == highestBidder && auctionInfoData[_tokenid][i].bid == highestBid && auctionInfoData[_tokenid][i].status == true) { - IERC721(gencore).safeTransferFrom(ownerOfToken, highestBidder, _tokenid); + IERC721(gencore).transferFrom(ownerOfToken, highestBidder, _tokenid); (bool success, ) = payable(owner()).call{value: highestBid}(""); emit ClaimAuction(owner(), _tokenid, success, highestBid); } else if (auctionInfoData[_tokenid][i].status == true) { (bool success, ) = payable(auctionInfoData[_tokenid][i].bidder).call{value: auctionInfoData[_tokenid][i].bid}(""); emit Refund(auctionInfoData[_tokenid][i].bidder, _tokenid, success, highestBid); } else { } } }
ERC721
#0 - c4-pre-sort
2023-11-18T07:33:32Z
141345 marked the issue as duplicate of #486
#1 - c4-judge
2023-12-01T22:28:33Z
alex-ppg marked the issue as not a duplicate
#2 - c4-judge
2023-12-01T22:28:58Z
alex-ppg marked the issue as duplicate of #1759
#3 - c4-judge
2023-12-08T22:12:48Z
alex-ppg marked the issue as partial-50
#4 - c4-judge
2023-12-09T00:23:13Z
alex-ppg changed the severity to 2 (Med Risk)