--- lang: zh-tw tags: Notes, Cryptocurrency date: 20220506 robots: noindex, nofollow license: GPL-3.0 --- 概覽 Demi-Human NFT 合約 === - 地址 - `0xa6916545a56f75acd43fb6a1527a73a41d2b4081` - VSCode viewer - https://etherscan.deth.net/address/0xa6916545a56f75acd43fb6a1527a73a41d2b4081 - 交易所頁面 - OpenSea - https://opensea.io/collection/demihuman - Rarible - https://rarible.com/collection/0xa6916545a56f75acd43fb6a1527a73a41d2b4081 - LooksRare - https://looksrare.org/collections/0xa6916545A56f75ACD43fb6A1527A73a41d2b4081 雖然整份 Demi-Human NFT 合約是由數個 `.sol` 檔案編譯而成,但是其中最主要的關鍵合約只有「`DemiHuman.sol`」一份,以下將針對這份檔案的內容做深入解釋。 此筆記開放所有讀者均可留言,歡迎留下您對於內文任何段落的看法。 DemiHuman.sol --- ### IPFS 的部分 - Blind box URI - QmWXS6mYZfiiizYGyRCSyUgchSXzYqavfywTAiGivGKHav - 因為 metadata 內容有問題(大小寫錯字),所以後來有再重新設定一次 - QmRciTiT2Rsv9fyV8qbkTeApTyFgkQgFdfLcHxEraGfHiS - Base URI - CID-v0 - QmSFS7KAmi2gsAA6najjDr7t4JbP6zHYue9QWMyMEBekbs - CID-v1 - bafybeib2dcmncbgmblvl5cbscqlrzpfq3uo66cghj3kquleu4dmswsy3pi <img src="https://i.imgur.com/rMR0kya.png" width="500"/> ### 總體來說 - 函數、變數的命名有點混亂 - `mint_presaled1()` 和 `getTokenByOwner()` - `_owner` 和 `NUM_TOKENS_MINT` 和 `bayc_balance` - Solidity `>= 0.8.0` 不需要額外手動引入 SafeMath - for loop 可以再進化 ```solidity for(uint8 i = 0; i < upper_bound;) { unchecked{i++;} } ``` - 最後一個 Public mint transaction - `0x2acf6641a1800c17cab47424ed894364537de155f38ebda3ecfbba386ffc2ea8` ### L18-28 - 應該使用 `interface BAYCFriend {}` 就好 - 不用寫三份,寫一份就好 ```solidity interface OtherNFT { function balanceOf(address owner) external view returns (uint256 balance); } contract DemiHuman is ERC721, ERC721Enumerable, Ownable { //... OtherNFT(NFT_address).balanceOf(msg.sender); //... } ``` ### L32-54 - 所有變數的預設值一定會是 binary zero (i.e., false) - 所有具備預先賦值的變數,都可以「考慮」設為 `constant` 或 `immutable` - 變數的順序可以調整一下,盡量讓東西塞滿 256-bit slot - boolean:1B、address:20B、contract instance 等同 address ### L56-57 - `_allowList` 和 `_blackList` 可以合併成同一份結構 ```solidity struct Property { bool _white; bool _black; } mapping(address => Property) _specialList; ``` ### L64-67 - 檢查目前合約 caller 是不是 EOA ### L77 `constructor()` - 傳入三個地址,然後把 NFT project 命名為 DemiHuman ### L84 `addToAllowList()` - 把新的地址清單添加到 `_allowList` 裡面 - 選擇 `calldata` 很棒! - 只有 `_owner` 可以上傳新增清單,所以其實不用阻擋 `address(0)`,或使用 `if()` 就好 - Openzeppelin NFT 的設計無法鑄造新 NFT 給 `address(0)` ### L91 `removeFromAllowList()` - 做跟前面函數顛倒的行為 ### L98 `onAllowList()` - 查詢地址有沒有在 `_allowList` 當中 ### L102 `startPreSaleD1()` - 設定第一階段的 pre-sale 開跑 - 其實可以跟下一個函數合併一起寫 ```solidity function setPreSaleD1(bool status) public onlyOwner { _isPreSaleD1Active = status; if(status) { emit PreSaleD1_Started(); } else { emit PreSaleD1_Stopped(); } } ``` ### L107 `pausePreSaleD1()` - 做跟前面函數顛倒的行為 ### L112 `isPreSaleD1Active()` - 把 `_isPreSaleD1Active` 設定成 `public` 也可以 - 目前的數值為 `True` - 另一種設定販售階段的方式可以善用 `enmu` ```solidity enum SalePhase { Preparation, PreSaleD1Active, PreSaleD1Inactive, PreSaleD2Active, PreSaleD2Inactive, PublicSaleActive, PublicSaleInactive } SalePhase public current_phase; function setPhase(SalePhase phase) public onlyOwner { current_phase = phase; } function foo() public { //... if(current_phase == SalePhase.PreSaleD1Active) { //... } //... } ``` ### L116 `startPreSaleD2()` - Trivial ### L121 `pausePreSaleD2()` - Trivial ### L126 `isPreSaleD2Active()` - Trivial - 目前的數值為 `True` ### L130 `startPublicSale()` - Trivial ### L135 `pausePublicSale()` - Trivial ### L140 `isPublicSaleActive()` - Trivial - 目前的數值為 `False` ### L145 `withdraw()` - s1~s4 其中一個地址呼叫之後,會把合約內的 $$ 分成四份傳送出去 - `.call()` 搭配 re-entrancy guard 會是比較好的方式 - 要小心除以四的這個語法,因為 `mint_*()` 函數並沒有限制要傳送多少 ETH(只有限制最低量),所以有可能會卡一點點錢在合約裡面領不出來(幾個 wei) ### L153 `getTotalSupply()` - 不用這個函數,ERC721Enumerable 就有 `totalSupply()` 了 ### L157 `getTokenByOwner()` - 利用查詢 `_ownedTokens` 來回傳某個 `_owner` 所持有的所有 `tokenId` 清單 ### L166 `mint_presaled1()` - L168 不用使用 SafeMath 的語法 - L171 不可能是負的 ### L178 `mint_presaled2()` - L181 出乎意料的是 `_blackList` 並不是用來紀錄黑名單,而是紀錄 BAYC 持有人有沒有鑄造過 - L187 不可能是負的 - 雖然說查詢三遍 token balance 是完全沒問題,不過可以再進化一下寫法 ```solidity // require(bayc.balanceOf(msg.sender) > 0, "You must hold at least one BAYC."); require(mayc.balanceOf(msg.sender) > 0, "You must hold at least one MAYC."); require(cool.balanceOf(msg.sender) > 0, "You must hold at least one CoolCats."); ``` ### L194 `mint_public()` - L196 不太確定為什麼這邊是用 9612 當上限 - L198 不可能是負的 ### L204 `reserve()` - 似乎是用做補足剩下沒鑄造光光的份額 ### L210 `airdrop()` - 似乎是用做補足剩下沒鑄造光光的份額 ### L216 `airdropToMany()` - 似乎是用做補足剩下沒鑄造光光的份額 ### L223 `_mint()` - 用到 function overloading,Openzeppelin 本身也有 `_mint()` 函數 - 這邊怎麼就沒檢查地址是不是 `address(0)` 了www ### L230 `setRevealTimestamp()` - 設定開啟 blind box 的時間 ### L234 `setBaseURI()` - 設定 base URI 的值 ### L238 `_baseURI()` - Openzeppelin 有這個函數,所以要 `override` ### L242 `setPreRevealURI()` - 設定 blind box 的圖的 URI ### L246 `tokenURI()` - `totalSupply() >= MAX_SUPPLY` 條件不明,而且也不可能成立 - `offsetIndex` 用意不明(為了洗牌嗎?) ### L258 `_beforeTokenTransfer()` - 因為繼承 ERC721, ERC721Enumerable,所以要有這個 override 函數 ### L266 `supportsInterface()` - 因為繼承 ERC721, ERC721Enumerable,所以要有這個 override 函數 - 支援 EIP-165,ERC721(0x80ac58cd)、ERC20(0x36372b07)