---
# System prepended metadata

title: 概覽 Demi-Human NFT 合約
tags: [' Cryptocurrency', Notes]

---

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