# Security Audit of `Demotapers` ![](https://i.imgur.com/DreWe7g.png) ## Conclusion This audit was made by [Web3Go](https://web3go.tech) Auditor: Vladimir Smelov <vsmelov.job@gmail.com>. Date: 2022-10-14 In the final contract were **NOT FOUND**: - Backdoors for investor funds withdrawal by anyone. - Bugs allow stealing money from the contract. - Any severe security problems. There were **FOUND**: - Places for beautification (typos, refactoring). - Places for gas optimization. The client was acknowledged about all notes below. All required fixes were done. **The code is ready for use.** ## Scope #### Pre-audit - DemotapersMarket.sol, md5 hash: 15087969b0afbb82ca857566c92b7dad - DemotapersNFT.sol, md5 hash: 8e4f1ab4455ece7321412a6417cfc06e - DemotapersNFTTest.sol, md5 hash: d7fe1a1b2387f27c515eb2137ff27ad4 - ERC165.sol, md5 hash: d336cb34366e7303e3f82b2beba86921 #### Final audit TO BE DONE ## Methodology 1. Blind audit. Understand the structure of the code without reading any docs. 2. Ask questions to developers. 3. Draw the scheme of cross-contracts interactions. 4. Write user stories and usage cases. 5. Run static analyzers. 6. Find problems with: - backdoors; - bugs; - math; - potential leaking of funds; - potential locking of the contract; - validate arguments and events; - others. ## Result ### Critical Not found. ___ ### Major Not found. ___ ### Warning #### WARNING-1. Centralization power - The owner may change metadata. - src/contracts/DemotapersNFT.sol:123 At any moment, the owner may turn all tokens into something completely unusable just by changing the baseURI value. ##### Recommendation Use Gnosis Multisig. Consider the usage of individual tokenURI with immutable decentralized storage (like IPFS). ##### Status ACKNOWLEDGED ___ ### Comment. #### COMMENT-1. Consider the usage of off-chain sales. At: - src/contracts/DemotapersNFT.sol - src/contracts/DemotapersMarket.sol To sell NFT to someone for a specific price, there should be 1. Mint NFT by owner 2. listItems by owner 3. buyItem by user Every operation costs gas and takes time. You can publish to your website (covered by the frontend) ECDSA signatures for not-minted-yet NFT with signing (tokenId, price, deadline). Then if someone wants to get NFT, he calls: ```solidity function buyAndMint(tokenId, price, deadline, ownerSignature) external payable { _verifySignature(tokenId, price, deadline, ownerSignature); require(msg.value == price); require(block.timestamp < deadline); _mint(tokenId, msg.sender); } ``` In that way, the owner pays no gas for minting and listing. Similar mechanics are used in well-known OpenSea and Rarible. ##### Recommendation Consider the usage of listing via off-chain signatures. ##### Status ACKNOWLEDGED The client wants to first mint and then sell NFTs. --- #### COMMENT-2. Calculate signatures at compilation time rather than have them hard-coded. At: - src/contracts/ERC165.sol:9 - src/contracts/DemotapersNFT.sol:32-38 You have hard-coded constants like ```solidity _INTERFACE_ID_ERC165 = 0x01ffc9a7; ``` Any typo in any symbol will lead to an untrackable mistake, which will be impossible to find. ##### Recommendation Consider rewriting to human-readable form: ```solidity _INTERFACE_ID_ERC165 = bytes4(keccak256('supportsInterface(bytes4)')); // 0x01ffc9a7 ``` ##### Status FIXED The double check of "magic" hashes was added. --- #### COMMENT-3. ERC165 implementation is gas-costly. At: - src/contracts/ERC165.sol:17 You read storage to check if the contract supports the interface or not. ```solidity function supportsInterface(bytes4 interfaceId) public view override returns (bool) { return _supportedInterfaces[interfaceId]; } ``` It costs 2100 gas - https://github.com/wolflo/evm-opcodes/blob/main/gas.md#a6-sload ##### Recommendation Consider rewriting to OpenZeppelin style with inheritance and OR operators. ##### Status ACKNOWLEDGED Not a big issue since, interface registry is done only once and view methods are not to be used intensevily. --- #### COMMENT-4. Emit events on important state changes. At: - src/contracts/DemotapersNFT.sol:124 - src/contracts/DemotapersMarket.sol:155 Some important state variable is changed, but you cannot track them since you don't emit an event. ##### Recommendation Emit event on essential state changes. ##### Status ACKNOWLEDGED These actions are done by the contract Owner, so no monitoring is needed. --- #### COMMENT-5. Useless structure. At: - src/contracts/DemotapersMarket.sol:35 The structure has only one attribute, so it's useless. ##### Recommendation Remove the structure. ##### Status FIXED --- #### COMMENT-6. Use `immutable` declarations. At: - src/contracts/DemotapersMarket.sol:39 Declare the variable as `immutable`; it saves gas. ##### Recommendation Use `immutable` declarations. ##### Status FIXED --- #### COMMENT-7. Explicitly state the interface of the variable. At: - src/contracts/DemotapersMarket.sol:39 Declare the variable as `IERC721` for clarity. ##### Recommendation Consider refactoring. ##### Status FIXED --- #### COMMENT-8. Use the `unchecked` declaration for index increment. At: - src/contracts/DemotapersMarket.sol:47 - src/contracts/DemotapersMarket.sol:66 - src/contracts/DemotapersMarket.sol:98 - src/contracts/DemotapersMarket.sol:110 - src/contracts/DemotapersNFT.sol:158 You have default solidity 0.8.x safeMath enabled for an increment: ```solidity= for (uint i=0; i < tokenIds.length; i++) { ... } ``` But you know for sure that `i` will never overflow. ##### Recommendation Consider explicitly unchecking the increment to save gas: ```solidity= for (uint i=0; i < tokenIds.length;) { unchecked{i++;} ... } ``` ##### Status ACKNOWLEDGED Skipped for the code clarity. --- #### COMMENT-9. Avoid double SLOAD. Whenever you call - `cancelListing` - `updateListing - `buyItem` You first read the storage in the modifier; then, you SLOAD the same storage slot in the function itself. ##### Recommendation Consider the removal of modifiers and do all checks in one for-loop with one SLOAD to save gas. ##### Status ACKNOWLEDGED Skipped to avoid the logic duplication. --- #### COMMENT-10. Prevent sending extensive ether. At: - src/contracts/DemotapersMarket.sol:139 It's better to require strictly. ``` if (msg.value != listedItem.price) { revert PriceNotMet(tokenId, listedItem.price); } ``` To prevent user mistakes. ##### Recommendation Consider using a strict check. ##### Status FIXED --- #### COMMENT-11. Never use `payable(...).transfer`. At: - src/contracts/DemotapersMarket.sol:154 You use the old-fashion style of sending the ethers. See - https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/ ##### Recommendation Use `payable{value: value}.call("");` ##### Status FIXED --- #### COMMENT-12. Add recoverERC20 and recoverERC721 to prevent user mistakes. Users often make mistakes, occasionally directly sending wETH to the contract or sending NFT to the contract. ##### Recommendation Consider adding `recoverERC20`, `recoverERC721` to recover tokens sent to the contract by mistake. ##### Status FIXED --- #### COMMENT-13. Don't use `uint256(x) <= 0` - src/contracts/DemotapersMarket.sol:59 - src/contracts/DemotapersMarket.sol:69 - src/contracts/DemotapersMarket.sol:91 - src/contracts/DemotapersMarket.sol:123 uint256 may never be under `0`; it's confusing to check `<= 0`. ##### Recommendation Consider replacing with `== 0`. ##### Status FIXED --- #### COMMENT-14. Remove useless stack variables. - src/contracts/DemotapersMarket.sol:94 You don't need a separate stack variable. ##### Recommendation Consider rewriting: ```solidity if (!IERC721(nft).isApprovedForAll(msg.sender, address(this))) { ``` ##### Status FIXED --- #### COMMENT-15. Import OpenZeppelin code. - src/contracts/DemotapersNFT.sol:16-24 you can import - https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/extensions/IERC721Metadata.sol - https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/IERC721Receiver.sol Also, you can inherit 80% of the code of DemotapersNFT from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol ##### Recommendation Consider importing OpenZeppelin code. ##### Status ACKNOWLEDGED --- #### COMMENT-16. Rename symbol "DMT". - src/contracts/DemotapersNFT.sol:42 "DMT" is a well-known drug (type "DMT" in Google). ##### Recommendation Consider renaming. ##### Status FIXED --- #### COMMENT-17. Unused `_marketAddress` variable. - src/contracts/DemotapersNFT.sol you never use `_marketAddress`, `setMarketAddress`, `getMarketAddress` for any business logic. ##### Recommendation Consider removing. ##### Status FIXED --- #### COMMENT-18. Use `tokenId` rather than `tokenIndex`. - src/contracts/DemotapersNFT.sol You use variables named `tokenIndex`; however, the standard EIP-721 defines them as `tokenId`. See - https://eips.ethereum.org/EIPS/eip-721 ##### Recommendation Consider renaming. ##### Status FIXED --- #### COMMENT-19. Fix typo in `tokenOowner`. - src/contracts/DemotapersNFT.sol ##### Recommendation Consider renaming it to `tokenOwner`. ##### Status FIXED --- #### COMMENT-20. Avoid calling `totalSupply` in a loop. - src/contracts/DemotapersNFT.sol:159 `totalSupply()` is a costly operation. ##### Recommendation Consider replacing with `first+i` ##### Status FIXED --- #### COMMENT-21. Remove the Context usage. - src/contracts/DemotapersNFT.sol You never switch the context, so you don't need to use `_msgSender()` ##### Recommendation Consider replacing it with `msg.sender` to save gas. ##### Status FIXED --- #### COMMENT-22. Optimize `_approve`. - src/contracts/DemotapersNFT.sol:399 You load `owner()`, but you already loaded it when you called the function. ##### Recommendation Avoid double SLOAD. ##### Status ACKNOWLEDGED --- #### COMMENT-23. Move test and mock contracts to a separate folder. - src/contracts/DemotapersNFTTest.sol ##### Recommendation Consider moving this contract to a separate folder, "mocks" or "tests." ##### Status FIXED