Hentai Holiday - Smart Contract Audit Report
Summary
Hentai Holiday is deploying a pair of smart contracts to facilitate NFT buying and selling on the Binance Smart Chain.
For this audit we reviewed the project team's HentaiHolidaySingle and HentaiHolidayMultiple contracts using code provided to us by the contract team.
Notes on the HentaiHolidayMultiple Contract:Notes on the HentaiHolidaySingle Contract:
- Users are able to mint any amount of any Hentai NFTs at any time. The user who mints is credited as being the creator and will receive the NFTs minted and the royalties from buying and selling that NFT ID.
- As any user can mint at any time, and there is no check if an NFT ID exists already, a user could mint an NFT ID that already exists. This would cause the NFTs to be minted again and the new user to be credited as the creator of all NFTs of that ID and receive the new NFTs as well as royalties for all NFTs of that ID.
- There is no limit on the number of any type of NFT that can be minted.
- Any valid BEP20 token can be added to the contract as a valid payment token. The team should exercise caution when adding new payment tokens to avoid ERC777-compliant tokens.
- NFT metadata that contains information about the NFT is stored using a URI endpoint. While this endpoint cannot be changed, the information at the endpoint is not stored on the blockchain.
- The owner of the endpoint could change the underlying data thus changing the NFT's information stored there.
- Users are able to buy NFTs using BNB and other approved BEP20 tokens by completing a sell order. Sell orders are created by the NFT owner specifying the price.
- When buying with tokens the amount is measured in number of tokens, this allows the owner to use tokens with very little monetary value. The owner or a user could exploit this to buy NFTs at a much lower price than the seller anticipated.
- Users are able to place a sell order for NFTs they own at any time.
- Users are able to change the price of that order at any time.
- Users are able to cancel their sell orders at any time.
- Users are able to sell NFTs by accepting a bid. A bid is made when the buyer gives the contract allowance in the selected approved token. The seller then accepts the bid and transfers the NFTs.
- Users are able to accept a bid only in approved tokens.
- There is no way to bid for a specific NFT. Any seller is able to accept any bid.
- Accepting bids to sell NFTs does not have the same issues as buying NFTs.
- There are service, admin, and royalty fees for purchasing and selling NFTs.
- The royalty fee is set by the creator of the NFT upon minting. They receive all royalties.
- The service and admin fee are the same amount and are sent to the owner. This value is set by the owner.
- None of the fees are limited and could be set to 100%.
- The remaining value after fees is sent to the seller of the NFT.
- Users are able to burn any amount of any NFTs they own at any time.
- If a user grants the contract full permision, the contract owner is able to burn any amount of their NFTs at any time.
- The owner is able to change the service fee at any time.
- The owner is able to add and remove addresses as a permitted payment token type at any time.
Audit Findings Summary:
- Only the owner is able to mint any amount of any NFT as long as it has not been minted before.
- If a sell price is provided a sell order is automatically created.
- Any valid BEP20 token can be added to the contract as a valid payment token. The team should exercise caution when adding new payment tokens to avoid ERC777-compliant tokens.
- NFT metadata that contains information about the NFT is stored using a URI endpoint. The information at the endpoint is not stored on the blockchain
- The owner of the endpoint could change the underlying data thus changing the NFT's information stored there.
- Users are able to sell NFTs by accepting a bid. A bid is made when the buyer gives the contract allowance in the selected approved token. The seller then accepts the bid and transfers the NFTs.
- Users are able to accept a bid only in approved tokens.
- There is no way to bid for a specific NFT. Any seller is able to accept any bid.
- Users are able to buy NFTs using BNB and other tokens approved by the owner.
- Users are able to buy any other users NFTs with approved tokens at any time without a sell order being placed.
- Users are able to buy other users NFTs using BNB by completing a sell orders.
- An NFT owner places a sell order specifying the sell amount. A user then uses BNB to buy the NFT at the specified amount.
- Buying or selling an NFT will remove the existing sell order for that NFT even if the owner has more of that NFT available to be sold.
- There are service, admin, and royalty fees for purchasing and selling NFTs.
- The royalty fee is set by the creator of the NFT upon minting the NFT. They receive all royalties.
- The service and admin fee are the same and sent to the owner. This value is set by the owner.
- The remaining value is sent to the seller of the NFT.
- Users are able to place a sell order for NFTs they own at any time.
- Users are able to lower the price of that order at any time. To increase the price you must cancel the order and place a new one with a higher price.
- Users are able to cancel their sell order at any time.
- Users are able to burn any NFTs they own as long as they only own 1 of them.
- If an address has approval to transfer NFTs on another's behalf, that address can opt to burn NFTs from the account that has provided the approval as long as the balance of the NFT is 1.
- The owner is able to change the service fee at any time.
- The owner is able to add and remove addresses as a permitted payment token type at any time.
- The owner is able to change the URI endpoint at any time.
- Solidity v0.8.x is used across both contracts to prevent overflows.
Resolved Issues
- Within the HentaiHolidaySingle contract users are able to buy NFTs with tokens without a sell order being placed.
- Ensure trust in the team as they have substantial control in the ecosystem.
- Date: October 7th, 2021.
- Updated: October 25th, 2021 - to resolve an issue with the dumTokenTransfer function.
- The team addressed an issue where NFTs could be transferred without paying in certain circumstances.
Audit Results
Vulnerability Category | Notes | Result |
---|---|---|
Logical Issues | N/A | PASS |
Arbitrary Storage Write | N/A | PASS |
Arbitrary Jump | N/A | PASS |
Delegate Call to Untrusted Contract | N/A | PASS |
Dependence on Predictable Variables | N/A | PASS |
Deprecated Opcodes | N/A | PASS |
Ether Thief | N/A | PASS |
Exceptions | N/A | PASS |
External Calls | N/A | PASS |
Integer Over/Underflow | N/A | PASS |
Multiple Sends | N/A | PASS |
Suicide | N/A | PASS |
State Change External Calls | N/A | PASS |
Unchecked Retval | N/A | PASS |
User Supplied Assertion | N/A | PASS |
Critical Solidity Compiler | N/A | PASS |
Overall Contract Safety | PASS |
Hentai Holiday Multiple Contract
($) = payable function
# = non-constant function
+ Context
- [Int] _msgSender
- [Int] _msgData
+ [Int] IBEP165
- [Ext] supportsInterface
+ BEP165 (IBEP165)
- [Pub] supportsInterface
+ [Int] IBEP1155 (IBEP165)
- [Ext] balanceOf
- [Ext] balanceOfBatch
- [Ext] setApprovalForAll #
- [Ext] isApprovedForAll
- [Ext] safeTransferFrom #
- [Ext] safeBatchTransferFrom #
+ [Int] IBEP1155MetadataURI (IBEP1155)
- [Ext] uri
- [Ext] name
- [Ext] symbol
+ [Lib] Address
- [Int] isContract
- [Int] sendValue #
- [Int] functionCall #
- [Int] functionCall #
- [Int] functionCallWithValue #
- [Int] functionCallWithValue #
- [Int] functionStaticCall
- [Int] functionStaticCall
- [Int] functionDelegateCall #
- [Int] functionDelegateCall #
- [Int] verifyCallResult
+ [Lib] Strings
- [Int] toString
- [Int] toHexString
- [Int] toHexString
+ BEP1155 (Context, BEP165, IBEP1155, IBEP1155MetadataURI)
- [Pub] #
- [Pub] name
- [Pub] symbol
- [Pub] supportsInterface
- [Pub] uri
- [Pub] balanceOf
- [Pub] balanceOfBatch
- [Pub] setApprovalForAll #
- [Pub] isApprovedForAll
- [Pub] safeTransferFrom #
- [Pub] safeBatchTransferFrom #
- [Int] _safeTransferFrom #
- [Int] _safeBatchTransferFrom #
- [Int] _setURI #
- [Int] _mint #
- [Int] _mintBatch #
- [Int] _burn #
- [Int] _burnBatch #
- [Int] _beforeTokenTransfer #
- [Prv] _doSafeTransferAcceptanceCheck #
- [Prv] _doSafeBatchTransferAcceptanceCheck #
- [Prv] _asSingletonArray
+ [Int] IBEP1155Receiver (IBEP165)
- [Ext] onBEP1155Received #
- [Ext] onBEP1155BatchReceived #
+ BEP1155Burnable (BEP1155)
- [Pub] burn #
- [Pub] burnBatch #
+ Ownable (Context)
- [Pub] #
- [Pub] owner
- [Pub] renounceOwnership #
- modifiers: onlyOwner
- [Pub] transferOwnership #
- modifiers: onlyOwner
- [Prv] _setOwner #
+ [Lib] SafeMath
- [Int] add
- [Int] sub
- [Int] sub
- [Int] mul
- [Int] div
- [Int] div
- [Int] mod
- [Int] mod
+ [Int] BEP20
- [Ext] totalSupply
- [Ext] balanceOf
- [Ext] transfer #
- [Ext] allowance
- [Ext] approve #
- [Ext] transferFrom #
+ HentaiholidayMULTIPLE (BEP1155, Ownable, BEP1155Burnable)
- [Pub] #
- modifiers: BEP1155
- [Pub] getServiceFee
- [Pub] serviceFunction #
- modifiers: onlyOwner
- [Pub] addID #
- [Pub] getTokenAddress
- [Pub] addTokenType #
- modifiers: onlyOwner
- [Pub] setApproval #
- [Pub] mint #
- [Pub] mintBatch #
- [Pub] saleWithToken #
- [Pub] saleToken ($)
- [Int] _saleToken #
- [Int] pBEPent
- [Int] calc
- [Int] saleTokenTransfer #
- [Pub] dumTokenTransfer #
- [Pub] acceptBId #
- [Int] _acceptBId #
- [Int] tokenTrans #
- [Pub] orderPlace #
- [Pub] cancelOrder #
- [Pub] changePrice #
- [Pub] burnToken #
Hentai Holiday Single Contract
($) = payable function
# = non-constant function
+ [Int] IBEP165
- [Ext] supportsInterface
+ [Lib] SafeMath
- [Int] add
- [Int] sub
- [Int] sub
- [Int] mul
- [Int] div
- [Int] div
- [Int] mod
- [Int] mod
+ [Int] IBEP721 (IBEP165)
- [Ext] balanceOf
- [Ext] ownerOf
- [Ext] safeTransferFrom #
- [Ext] transferFrom #
- [Ext] approve #
- [Ext] getApproved
- [Ext] setApprovalForAll #
- [Ext] isApprovedForAll
- [Ext] safeTransferFrom #
+ [Int] IBEP721Receiver
- [Ext] onBEP721Received #
+ [Int] IBEP721Metadata (IBEP721)
- [Ext] name
- [Ext] symbol
- [Ext] tokenURI
+ [Lib] Address
- [Int] isContract
- [Int] sendValue #
- [Int] functionCall #
- [Int] functionCall #
- [Int] functionCallWithValue #
- [Int] functionCallWithValue #
- [Int] functionStaticCall
- [Int] functionStaticCall
- [Int] functionDelegateCall #
- [Int] functionDelegateCall #
- [Prv] _verifyCallResult
+ Context
- [Int] _msgSender
- [Int] _msgData
+ [Lib] Strings
- [Int] toString
- [Int] toHexString
- [Int] toHexString
+ BEP165 (IBEP165)
- [Pub] supportsInterface
+ BEP721 (Context, BEP165, IBEP721, IBEP721Metadata)
- [Pub] #
- [Pub] supportsInterface
- [Pub] balanceOf
- [Pub] ownerOf
- [Pub] name
- [Pub] symbol
- [Pub] tokenURI
- [Int] _baseURI
- [Pub] approve #
- [Pub] getApproved
- [Pub] setApprovalForAll #
- [Pub] isApprovedForAll
- [Pub] transferFrom #
- [Pub] safeTransferFrom #
- [Pub] safeTransferFrom #
- [Int] _safeTransfer #
- [Int] _exists
- [Int] _isApprovedOrOwner
- [Int] _safeMint #
- [Int] _safeMint #
- [Int] _mint #
- [Int] _burn #
- [Int] _transfer #
- [Int] _approve #
- [Prv] _checkOnBEP721Received #
- [Int] _beforeTokenTransfer #
+ [Int] IBEP721Enumerable (IBEP721)
- [Ext] totalSupply
- [Ext] tokenOfOwnerByIndex
- [Ext] tokenByIndex
+ BEP721Enumerable (BEP721, IBEP721Enumerable)
- [Pub] supportsInterface
- [Pub] tokenOfOwnerByIndex
- [Pub] totalSupply
- [Pub] tokenByIndex
- [Int] _beforeTokenTransfer #
- [Prv] _addTokenToOwnerEnumeration #
- [Prv] _addTokenToAllTokensEnumeration #
- [Prv] _removeTokenFromOwnerEnumeration #
- [Prv] _removeTokenFromAllTokensEnumeration #
+ BEP721URIStorage (BEP721)
- [Pub] tokenURI
- [Int] _setTokenURI #
- [Int] _burn #
+ BEP721Burnable (Context, BEP721)
- [Pub] burn #
+ Ownable (Context)
- [Pub] #
- [Pub] owner
- [Pub] renounceOwnership #
- modifiers: onlyOwner
- [Pub] transferOwnership #
- modifiers: onlyOwner
- [Prv] _setOwner #
+ [Int] BEP20
- [Ext] totalSupply
- [Ext] balanceOf
- [Ext] transfer #
- [Ext] allowance
- [Ext] approve #
- [Ext] transferFrom #
+ HentaiholidaySingle (BEP721, BEP721Enumerable, BEP721URIStorage, BEP721Burnable, Ownable)
- [Pub] #
- modifiers: BEP721
- [Pub] getServiceFee
- [Pub] serviceFunction #
- modifiers: onlyOwner
- [Pub] addID #
- [Pub] getTokenAddress
- [Pub] addTokenType #
- modifiers: onlyOwner
- [Int] _addTokenType #
- modifiers: onlyOwner
- [Pub] setApproval #
- [Pub] safeMint #
- modifiers: onlyOwner
- [Pub] setBaseURI #
- modifiers: onlyOwner
- [Int] _beforeTokenTransfer #
- [Int] _burn #
- [Pub] tokenURI
- [Pub] supportsInterface
- [Pub] mint #
- [Pub] orderPlace #
- [Int] _orderPlace #
- [Ext] get
- [Int] calc
- [Int] pBEPent
- [Int] SalewithToken #
- [Pub] saleTokenwithToken #
- [Int] newtokenasbid #
- [Int] _acceptBId #
- [Pub] saleToken ($)
- [Int] _saleToken #
- [Int] saleTokenTransfer #
- [Int] tokenTrans #
- [Pub] acceptBId #
- [Pub] cancelOrder #
- [Pub] changePrice #
- [Pub] burnToken #