---
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)