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: 120/243
Findings: 3
Award: $11.44
🌟 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#L124-L130 https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L134-L143
When block.timestamp == minter.getAuctionEndTime(_tokenid)
, user can call to both 2 function claimAuction()
and cancelBid()
. This one second overlapped issue can lead to malicious users use cross-function reentrancy to get double funds he/she bids. If the fund is big enough, it can drain all the fund in this contract.
The hack contract will look something like this:
contract Hack { bool called; uint256 tokenid; function bid(uint256 _tokenid) external payable { tokenid = _tokenid; auctionDemo.participateToAuction{value: msg.value}(_tokenid); } receive() external payable { if (called) return; called = true; auctionDemo.cancelAllBids(tokenid); } }
Manual Review
function cancelBid(uint256 _tokenid, uint256 index) public { - require(block.timestamp <= minter.getAuctionEndTime(_tokenid), "Auction ended"); + require(block.timestamp < minter.getAuctionEndTime(_tokenid), "Auction ended"); ... }
function cancelAllBids(uint256 _tokenid) public { - require(block.timestamp <= minter.getAuctionEndTime(_tokenid), "Auction ended"); + require(block.timestamp < minter.getAuctionEndTime(_tokenid), "Auction ended"); ... }
Reentrancy
#0 - c4-pre-sort
2023-11-15T04:57:43Z
141345 marked the issue as duplicate of #962
#1 - c4-judge
2023-12-04T21:42:28Z
alex-ppg marked the issue as duplicate of #1323
#2 - c4-judge
2023-12-08T17:42:33Z
alex-ppg marked the issue as partial-50
🌟 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
In AuctionDemo.claimAuction()
, the contract will tranfer the NFT to the highest bidder of that NFT by using ERC721.safeTransferFrom()
method. The issue here is, if the highest bidder is a contract and doesn't have onERC721Received()
function or not return the magic number correctly, the function AuctionDemo.claimAuction()
will get DOS forever. Moreover, all the bidders can't withdraw their fund using cancelBid()
or cancelAllBids()
because the auction end time was passed. And because this contract doesn't have an emergency function, the NFT and all the bid will get stuck in it.
function claimAuction(uint256 _tokenid) public WinnerOrAdminRequired(_tokenid,this.claimAuction.selector){ require(block.timestamp >= minter.getAuctionEndTime(_tokenid) && auctionClaim[_tokenid] == false && minter.getAuctionStatus(_tokenid) == true); ... 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 {} } }
Manual Review
- IERC721(gencore).safeTransferFrom(ownerOfToken, highestBidder, _tokenid); + IERC721(gencore).transferFrom(ownerOfToken, highestBidder, _tokenid);
DoS
#0 - c4-pre-sort
2023-11-17T00:09:04Z
141345 marked the issue as duplicate of #486
#1 - c4-judge
2023-12-01T22:10:20Z
alex-ppg marked the issue as not a duplicate
#2 - c4-judge
2023-12-01T22:10:35Z
alex-ppg marked the issue as duplicate of #1759
#3 - c4-judge
2023-12-08T22:07:49Z
alex-ppg marked the issue as partial-50
#4 - c4-judge
2023-12-09T00:23:12Z
alex-ppg changed the severity to 2 (Med Risk)
🌟 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
https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L105 https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L58
Notice that when block.timestamp == minter.getAuctionEndTime(_tokenid)
, users can call to both 2 contract participateToAuction()
and claimAuction()
. Because of that, this scenario can happens:
block.timestamp == minter.getAuctionEndTime(_tokenid)
, he call to claimAuction()
to rightfully claim his NFT XparticipateToAuction()
to bid the highest price for NFT X when block.timestamp == minter.getAuctionEndTime(_tokenid)
cancelBid()
to withdraw his bid, but he can't because the time is already surpass minter.getAuctionEndTime(_tokenid)
Notice that user can call to both 2 function participateToAuction()
and claimAuction()
when block.timestamp == minter.getAuctionEndTime(_tokenid)
function participateToAuction(uint256 _tokenid) public payable { @> require(msg.value > returnHighestBid(_tokenid) && block.timestamp <= minter.getAuctionEndTime(_tokenid) && minter.getAuctionStatus(_tokenid) == true); ... }
function claimAuction(uint256 _tokenid) public WinnerOrAdminRequired(_tokenid,this.claimAuction.selector){ @> require(block.timestamp >= minter.getAuctionEndTime(_tokenid) && auctionClaim[_tokenid] == false && minter.getAuctionStatus(_tokenid) == true); ... }
Manual Review
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); ... }
Other
#0 - c4-pre-sort
2023-11-14T10:28:01Z
141345 marked the issue as duplicate of #1935
#1 - c4-pre-sort
2023-11-14T14:21:38Z
141345 marked the issue as duplicate of #962
#2 - c4-judge
2023-12-02T15:32:58Z
alex-ppg marked the issue as not a duplicate
#3 - c4-judge
2023-12-02T15:34:45Z
alex-ppg marked the issue as duplicate of #1926
#4 - c4-judge
2023-12-08T18:48:14Z
alex-ppg marked the issue as satisfactory