---
# System prepended metadata

title: Garrick信仲 | C0051202 Cryptocamp第5期Solidity工程師實戰營作業

---

# Garrick信仲 | C0051202 Cryptocamp第5期Solidity工程師實戰營作業

## 第一週課程作業
<details>
    
### 1.請簡單描述錢包助記詞，私鑰，公鑰之間的關係
#### 錢包、助記詞、公鑰、私鑰
錢包就是一個身份認證或是一把鑰匙，而錢包助記詞的標準是BIP-39，助記詞先產生私鑰，再透過私鑰產生公鑰，公鑰產生公鑰雜湊，再取20Byte為錢包地址（例如Metamask上看到的位址）, 公鑰雜湊不可以反推出公鑰，公鑰也沒辦法反推出私鑰。

### 2.用 Remix 部署智能合約(Local London)
![](https://i.imgur.com/CeOrzGT.png)

## 基本題
### 3.Solidity TodoList 延伸
* 將寫好的合約部署到 Görli / Goerli 測試鏈上，並 verify 開源程式碼
![](https://i.imgur.com/U3CYiDO.png)

* 請貼上合約地址
```
0xc0eb0907C53E30AEAf7363e3f85CbA922fA26d40
```

* #### Todo List 增加 Pending 功能
  請參考以下程式碼區塊
* 提示：多新增一個 Pending 狀態，string[] public pending;


## 進階題

* 撰寫此功能測試
* 增加清空 Completed 功能
* 增加 Pending 最多滯留 n 秒（當 TODO 搬移到 PENDING 後，要記錄時間，當時間超過 n 秒，就不可以再搬回 TODO）
       - Array
       - Mapping
       - Struct (懷恩💜熱情推薦)

下方有TodoList和TestTodoList合約，可以使用TestTodoList來和TodoList互動，先佈署TodoList後複製地址再佈署TestTodoList時貼上地址。
測試案例：
1. 先在addTodo依序輸入工作項目，例如：w1,w2,w3,w4,w5,w6,w7等工作項目，現有功能需分7次輸入尚未能一次貼上。
2. 可使用getAllTodo列出所有工作項目。
3. 執行setCompleted給值 1，並執行getAllCompleted可看到完成的工作項目增加 w2，再執行getAllTodo可發現工作項目 w2已不在列表中。
4. 本項執行前請問注意被轉到pending的工作項目10秒後就不能再resume，執行setPending給值 3，並執行getAllPending可看到完成的工作項目增加 w5，再執行getAllTodo可發現工作項目 w5已不在列表中。
5. 可執行emptyCompleted清掉所有完成的工作項目。
6. deleteTodo功能執行後在array裡元素還是存在但是值變成空，這個元素還是可以被轉到completed或是pending，可以將該元素移至array最後方再用pop移除（此功能暫時未實現）。

```
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

contract TodoList {
    struct TodoItem {
        string todoName;
        uint256 updateTimestamp;
    }
    TodoItem[] public todos;
    TodoItem[] public todoCompleted;
    TodoItem[] public todoPending;
    uint256 public startTime;
    

    constructor() {
        startTime = block.timestamp;
    }

    // 永久儲存是storage，短暫使用就用memory，calldata傳入後只能參考不能改變的data type 
    //--------------Todo------------------------------------------------------------------------------------------
    function addTodo(string memory _todoName) external {
        pushTodoItem(_todoName);
    }

    function pushTodoItem(string memory _todoName) internal {
        todos.push(TodoItem(_todoName, block.timestamp));
    }

    function getTodoItem(uint256 index) internal view returns (string memory) {
        return todos[index].todoName;
    }

    function getTodo(uint256 _index) external view returns (string memory) {
        return getTodoItem(_index);
    }

    function deleteTodo(uint256 index) external {
        delete todos[index];
    }

    function getAllTodo() external view returns (string memory) {
        string memory todosName;
        todosName = "The Todo List: ";
        for (uint256 i = 0; i <= todos.length - 1; i++){
            todosName = string(abi.encodePacked(todosName, " ", todos[i].todoName));
        }
        return todosName;
    }

    //--------------Todo end------------------------------------------------------------------------------------------

    //--------------Complete------------------------------------------------------------------------------------------

    function setCompleted(uint256 index) external {
        TodoItem memory compeltedTodo = todos[index];
        
        for (uint256 i = index; i < todos.length - 1; i++){
            todos[i] = todos[i + 1];
        }
        delete todos[todos.length - 1];
        todos.pop();

        compeltedTodo.updateTimestamp = block.timestamp;
        todoCompleted.push(compeltedTodo);
    }

    function setUncompleted(uint256 index) external {
        TodoItem memory uncompeltedTodo = todoCompleted[index];
        require(block.timestamp<(uncompeltedTodo.updateTimestamp+86400), "The time is waiting too long to restore the Todo Item.");
        for (uint256 i = index; i < todoCompleted.length - 1; i++){
            todoCompleted[i] = todoCompleted[i + 1];
        }
        delete todoCompleted[todoCompleted.length - 1];
        todoCompleted.pop();

        todos.push(uncompeltedTodo);
    }

    function getCompletedTodoItem(uint256 index) internal view returns (string memory) {
        return todoCompleted[index].todoName;
    }

    function getCompleted(uint256 _index) external view returns (string memory) {
        return getCompletedTodoItem(_index);
    }

    function getAllCompleted() external view returns (string memory) {
        string memory completedTodosName;
        completedTodosName = "The Completed Todo List: ";
        for (uint256 i = 0; i <= todoCompleted.length - 1; i++){
            completedTodosName = string(abi.encodePacked(completedTodosName, " ", todoCompleted[i].todoName));
        }
        return completedTodosName;
    }

    function emptyCompleted() external returns (uint256) {
        delete todoCompleted;
        return todoCompleted.length;
    }

    //--------------Complete end------------------------------------------------------------------------------------------

    //--------------Pending------------------------------------------------------------------------------------------

    function setPending(uint256 index) external {
        TodoItem memory pendingTodo = todos[index];
        
        for (uint256 i = index; i < todos.length - 1; i++){
            todos[i] = todos[i + 1];
        }
        delete todos[todos.length - 1];
        todos.pop();

        pendingTodo.updateTimestamp = block.timestamp;
        todoPending.push(pendingTodo);
    }

    function setResume(uint256 index) external {
        TodoItem memory resumeTodo = todoPending[index];
        require(block.timestamp<(resumeTodo.updateTimestamp+10), "The time is waiting too long to restore the Todo Item.");

        for (uint256 i = index; i < todoPending.length - 1; i++){
            todoPending[i] = todoPending[i + 1];
        }
        delete todoPending[todoPending.length - 1];
        todoPending.pop();

        todos.push(resumeTodo);
    }

    function getPendingTodoItem(uint256 index) internal view returns (string memory) {
        return todoPending[index].todoName;
    }

    function getPending(uint256 _index) external view returns (string memory) {
        return getPendingTodoItem(_index);
    }

    function getAllPending() external view returns (string memory) {
        string memory pendingTodosName;
        pendingTodosName = "The Pending Todo List: ";
        for (uint256 i = 0; i <= todoPending.length - 1; i++){
            pendingTodosName = string(abi.encodePacked(pendingTodosName, " ", todoPending[i].todoName));
        }
        return pendingTodosName;
    }

    //--------------Pending end------------------------------------------------------------------------------------------

    function getTSDisplay() external view returns (uint256) {
        return startTime;
    }
    
}



contract TestTodoList {
    TodoList public cryptoTodoList;

    constructor(address _todoListAddr) {
        cryptoTodoList = TodoList(_todoListAddr);
    }

    function addTodo(string memory _todoName) external {
        cryptoTodoList.addTodo(_todoName);
    }

    function getTodo(uint256 _index) external view returns (string memory) {
        return cryptoTodoList.getTodo(_index);
    }

    function deleteTodo(uint256 index) external {
        cryptoTodoList.deleteTodo(index);
    }

    function getAllTodo() external view returns (string memory) {
        return cryptoTodoList.getAllTodo();
    }

    function setCompleted(uint256 index) external {
        cryptoTodoList.setCompleted(index);
    }

    function setUncompleted(uint256 index) external {
        cryptoTodoList.setUncompleted(index);
    }

    function getCompleted(uint256 _index) external view returns (string memory) {
        return cryptoTodoList.getCompleted(_index);
    }

    function getAllCompleted() external view returns (string memory) {
        return cryptoTodoList.getAllCompleted();
    }

    function emptyCompleted() external returns (uint256){
        return cryptoTodoList.emptyCompleted();
    }
    function setPending(uint256 _index) external {
        cryptoTodoList.setPending(_index);
    }
    function setResume(uint256 _index) external {
        cryptoTodoList.setResume(_index);
    }
    function getPending(uint256 _index) external view returns (string memory) {
        return cryptoTodoList.getPending(_index);
    }
    function getAllPending() external view returns (string memory) {
        return cryptoTodoList.getAllPending();
    }
    function getTSDisplay() external view returns (uint256) {
        return cryptoTodoList.getTSDisplay();
    }

}
```
                                                  
</details>

## 第二週課程作業
 
    
## 基本題

    提醒，作業標題：請填寫你的學號 （Discord KryptoCamp 的名稱），例：Hazel | C0031601
    用 HackMD 繳交作業。如不做進階題可留空白，繳交作業時，請提供 HackMD網址

### 1.引用 OpenZeppelin ERC20 部署一個自定義的 ERC20 Token 在 Goerli 鏈上
*         開源合約並提交合約地址
    
*         實作 mint 和 burn 功能
*         轉移 100 個 Token 到以下地址 0x6e24f0fF0337edf4af9c67bFf22C402302fc94D3
*         轉移 Token 給所有組員

#### 合約地址
    

測試鏈地址 : `0x66081dF82eb1fcC32831B902fA733f92fd67770F`
Symbol : GHC
Decimals : 6
    
合約verify畫面
![](https://i.imgur.com/fwMnrzV.png)


---

#### 合約程式
實作mint和burn功能
```
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract GCERC20Test is ERC20 {
    uint8 private _decimals;
    constructor(string memory name, string memory symbol, uint8 decimals_)     ERC20(name, symbol){
        _decimals = decimals_;
    }

    function decimals() public view override returns (uint8) {
        return _decimals;
    }

    function mint(address to, uint256 amount) external {
        _mint(to, amount);
    }

    function burn(address from, uint256 amount) external {
        _burn(from, amount);
    }

    function transfer(address to, uint256 amount) public override returns (bool) {
        address owner = _msgSender();
        _burn(owner, 1);
        _transfer(owner, to, amount);
        return true;
    }

}
```


---
        
    
#### 轉移結果

轉給`0x6e24f0fF0337edf4af9c67bFf22C402302fc94D3`
![](https://i.imgur.com/ueH7KVi.png)



---
    
RT from Ryan, tGD from 企鵝
![](https://i.imgur.com/88nkT8Z.png)


---


### 2.引用 OpenZeppelin ERC721 部署一個 ERC721 NFT 合約，同時擁有付費 mint 功能
*         開源合約並提交合約地址
*         使用 OpenSea Metadata 標準，並提交 OpenSea頁面
*         將檔案上傳至 ipfs
*         擁有白名單機制並且將組員加入白名單
*         設定總量上限
*         加入至少一種自定義功能，例如：荷蘭拍、盲盒、返佣、融合…

#### 合約地址
`0x7eb9Fa0cCf33eaa6f5a5818799c6851DBf497c2e`
![](https://i.imgur.com/QkzcWNN.png)



---


#### 合約程式
程式說明：
1. 使用batchMint可以一次鑄造多個NFT，給的數量不能超過5但未上線總數限制，需要輸入proof使用Merkle Tree白名單（可見以下測試結果）。
2. 使用setURIState方法輸入1後，可以讓替換baseURI讓切換圖片，類似盲盒功能。



```
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract ERC721FeedAnimal is ERC721 {
    uint256 public tokenId = 1000;
    address public owner;
    bytes32 public root;
    uint256 public uriState;


    using Strings for uint256;

    constructor(string memory _name, string memory _symbol, bytes32 _root) ERC721(_name, _symbol) {
        owner = msg.sender;
        setRoot(_root);
    }

    function mint(uint256 _tokenId, bytes32[] calldata _proof) external verifyProof(_proof){
        require(_tokenId<=5, "The maxium mint number is 5.");
        // tokenId++;
        _safeMint(msg.sender, _tokenId);
    }

    function batchMint(uint256 _quantity, bytes32[] calldata _proof) external verifyProof(_proof){
        // unit256 _tokenId = _quantity + 1001;
        require(_quantity<=5, "The maxium mint amount and number is 5.");
        for(uint256 i=1; i <= _quantity; i++) {
            tokenId++;
            _safeMint(msg.sender, tokenId);
        }
    }

    function _baseURI() internal pure override returns (string memory) {
        return "https://gateway.pinata.cloud/ipfs/QmYgfUPBRgH1KWGAKuirRgCodM78Bpu8XmR9D8LL7a2JPr/";
        // return "https://gateway.pinata.cloud/ipfs/QmRRSVSkKHA93qspxbX9q2vJ8Utmk2B1dY4Nmi8D3gmzQK/";
    }

    function setURIState(uint256 _URIState) external {
        uriState = _URIState;
    }

    function tokenURI(uint256 _tokenId) public view override returns (string memory) {
        _requireMinted(_tokenId);
        string memory baseURI = (uriState == 1? "https://gateway.pinata.cloud/ipfs/QmRRSVSkKHA93qspxbX9q2vJ8Utmk2B1dY4Nmi8D3gmzQK/": _baseURI());
        return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, _tokenId.toString(), ".json")) : "";
    }
    
    modifier verifyProof(bytes32[] memory proof) {
        require(MerkleProof.verify(proof, root, keccak256(abi.encodePacked(msg.sender))), "Invalid proof");
        _;
    }

    // function whitelistMint(bytes32[] calldata _proof) external verifyProof(_proof) {
    //     tokenId++;
    //     _safeMint(msg.sender, tokenId);
    // }

    function verify(bytes32[] memory proof) internal view returns (bool) {
        return MerkleProof.verify(proof, root, keccak256(abi.encodePacked(msg.sender)));
    }

    function setRoot(bytes32 _root) internal {
        require(msg.sender == owner, "Only owner can set root");
        root = _root;
    }

}
```



---

#### 測試結果
在Opensea testnet上可以看到以下的NFT
![](https://i.imgur.com/BjH0Dsd.png)
將
![](https://i.imgur.com/v9LS37d.png)


總共傳了5個圖在Pinata供這個合約使用，現在已經mint了2個NFT，還剩下3個NFT的圖可以使用，測試資料如下：
```
root 0x7ae5b565b6bd62cb8f307d1daf2b64657446d3ca29b31febcee1bc87dfcd4c37
address 0x6e24f0fF0337edf4af9c67bFf22C402302fc94D3
proof ["0xafe7c546eb582218cf94b848c36f3b058e2518876240ae6100c4ef23d38f3e07","0xe2f68cf91ee6803cd4e7305b6918ca7e0cd67a68c79bdaa252022ebf5052f166","0xd0587cdd201c1673cf4f382ae7c88a83f944a0de445ce8fc14518f80a1596ce7"]
```




    
---

## 進階題 (以下佈在Sepolia測試鏈，只是測試mint不會顯示圖片到opensea)

## 1.研究 ERC721A 合約
*         寫下 ERC721A 及 ERC721 差異
*         實際部署 ERC721A 合約比較所花費的 gas fee

#### ERC721在用戶可以一次mint多個NFT的時候，不僅要使用For loop執行_safeMint方法而且都要執行totalSupply()方法取得發行的NFT數量後再加1傳入_safeMint當tokenId，這樣造成許多的gas fee的費用。
```
function _safeMint(address to, uint256 tokenId) internal virtual {
        _safeMint(to, tokenId, "");
}
```

#### ERC721A的_safeMint方法是改成傳入quantity給_mint，就不需要在迴圈中一直對mint總數進行累加計算，在_mint的do...while迴圈中的tokenId不等於end變數的時候持續mint，直到得到輸入的數量，以節省gas fee。
```
    function _safeMint(
        address to,
        uint256 quantity,
        bytes memory _data
    ) internal virtual {
        _mint(to, quantity);

        unchecked {
            if (to.code.length != 0) {
                uint256 end = _currentIndex;
                uint256 index = end - quantity;
                do {
                    if (!_checkContractOnERC721Received(address(0), to, index++, _data)) {
                        _revert(TransferToNonERC721ReceiverImplementer.selector);
                    }
                } while (index < end);
                // Reentrancy protection.
                if (_currentIndex != end) _revert(bytes4(0));
            }
        }
    }
```


---

#### ERC721 mint 100個token花費如下：
Contract `0xD8D0E88bcADB7d7B07CEF12a0E3B6b50c2b99485`
Transaction Hash `0xfff9436efc48519881db810fd67c0cb690fd8029d41971164b93502411e40972`
![](https://i.imgur.com/XzUrz4t.png)

ERC721合約
```
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

contract MyERC721NFT is ERC721 {
    uint256 public tokenId = 1000;
    address public owner;

    using Strings for uint256;

    constructor(string memory _name, string memory _symbol) ERC721(_name, _symbol) {
        owner = msg.sender;
    }

    function mint(uint256 _tokenId) external {
        // tokenId++;
        _safeMint(msg.sender, _tokenId);
    }

    function batchMint(uint256 _quantity) external {
        // unit256 _tokenId = _quantity + 1001;
        for(uint256 i=1; i <= _quantity; i++) {
            tokenId++;
            _safeMint(msg.sender, tokenId);
        }
        
    }

    function _baseURI() internal pure override returns (string memory) {
        return "https://gateway.pinata.cloud/ipfs/QmXzSFXTxCDW12JYQnDXj5N8QUffmycHw4i8cpMYmrBX13/";
    }

    function tokenURI(uint256 _tokenId) public view override returns (string memory) {
        _requireMinted(_tokenId);

        string memory baseURI = _baseURI();
        return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, _tokenId.toString(), ".json")) : "";
    }
}
```

---

#### ERC721A mint 100個token花費如下：
Contract `0xc0eb0907C53E30AEAf7363e3f85CbA922fA26d40`
Transaction Hash `0x242e9d8e2293b4017de22bd8bacdc514567ea94a0eef871e404397e50435db20`
![](https://i.imgur.com/RL3q7Q7.png)

ERC721A合約
```
pragma solidity 0.8.17;

import "erc721a/contracts/ERC721A.sol";

contract MyERC721ANFT is ERC721A {
    // Bypass for a `--via-ir` bug (https://github.com/chiru-labs/ERC721A/pull/364).
 
    // =============================================================
    //                           CONSTANTS
    // =============================================================

    // Mask of an entry in packed address data.
    uint256 private constant _BITMASK_ADDRESS_DATA_ENTRY = (1 << 64) - 1;

    // The bit position of `numberMinted` in packed address data.
    uint256 private constant _BITPOS_NUMBER_MINTED = 64;

    // The bit position of `numberBurned` in packed address data.
    uint256 private constant _BITPOS_NUMBER_BURNED = 128;

    // The bit position of `aux` in packed address data.
    uint256 private constant _BITPOS_AUX = 192;

    // Mask of all 256 bits in packed address data except the 64 bits for `aux`.
    uint256 private constant _BITMASK_AUX_COMPLEMENT = (1 << 192) - 1;

    // The bit position of `startTimestamp` in packed ownership.
    uint256 private constant _BITPOS_START_TIMESTAMP = 160;

    // The bit mask of the `burned` bit in packed ownership.
    uint256 private constant _BITMASK_BURNED = 1 << 224;

    // The bit position of the `nextInitialized` bit in packed ownership.
    uint256 private constant _BITPOS_NEXT_INITIALIZED = 225;

    // The bit mask of the `nextInitialized` bit in packed ownership.
    uint256 private constant _BITMASK_NEXT_INITIALIZED = 1 << 225;

    // The bit position of `extraData` in packed ownership.
    uint256 private constant _BITPOS_EXTRA_DATA = 232;

    // Mask of all 256 bits in a packed ownership except the 24 bits for `extraData`.
    uint256 private constant _BITMASK_EXTRA_DATA_COMPLEMENT = (1 << 232) - 1;

    // The mask of the lower 160 bits for addresses.
    uint256 private constant _BITMASK_ADDRESS = (1 << 160) - 1;

    // The maximum `quantity` that can be minted with {_mintERC2309}.
    // This limit is to prevent overflows on the address data entries.
    // For a limit of 5000, a total of 3.689e15 calls to {_mintERC2309}
    // is required to cause an overflow, which is unrealistic.
    uint256 private constant _MAX_MINT_ERC2309_QUANTITY_LIMIT = 5000;

    // The `Transfer` event signature is given by:
    // `keccak256(bytes("Transfer(address,address,uint256)"))`.
    bytes32 private constant _TRANSFER_EVENT_SIGNATURE =
        0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;

    // =============================================================
    //                            STORAGE
    // =============================================================

    // The next token ID to be minted.
    uint256 private _currentIndex;

    // The number of tokens burned.
    uint256 private _burnCounter;

    // Token name
    string private _name;

    // Token symbol
    string private _symbol;

    // Mapping from token ID to ownership details
    // An empty struct value does not necessarily mean the token is unowned.
    // See {_packedOwnershipOf} implementation for details.
    //
    // Bits Layout:
    // - [0..159]   `addr`
    // - [160..223] `startTimestamp`
    // - [224]      `burned`
    // - [225]      `nextInitialized`
    // - [232..255] `extraData`
    mapping(uint256 => uint256) private _packedOwnerships;

    // Mapping owner address to address data.
    //
    // Bits Layout:
    // - [0..63]    `balance`
    // - [64..127]  `numberMinted`
    // - [128..191] `numberBurned`
    // - [192..255] `aux`
    mapping(address => uint256) private _packedAddressData;

    // Mapping from token ID to approved address.
    mapping(uint256 => TokenApprovalRef) private _tokenApprovals;

    // Mapping from owner to operator approvals
    mapping(address => mapping(address => bool)) private _operatorApprovals;


    // constructor(string memory _name, string memory _symbol) ERC721A(_name, _symbol) {
    //     owner = msg.sender;
    // }

    // =============================================================
    //                          CONSTRUCTOR
    // =============================================================

    constructor(string memory name_, string memory symbol_) ERC721A(name_, symbol_) {
        _name = name_;
        _symbol = symbol_;
        _currentIndex = _startTokenId();
    }

    function mint(uint256 quantity) external {
        _safeMint(msg.sender, quantity);
    }

    function _baseURI() internal pure override returns (string memory) {
        return "https://gateway.pinata.cloud/ipfs/QmVJYFp12YVLtgwtnnD7KmAGL2gEFfTWBhzy2foGeeqcf1/";
    }

    // function tokenURI(uint256 _tokenId) public view override returns (string memory) {
    //     // _requireMinted(_tokenId);

    //     string memory baseURI = _baseURI();
    //     return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, _tokenId.toString(), ".json")) : "";
    // }
    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        if (!_exists(tokenId)) _revert(URIQueryForNonexistentToken.selector);

        string memory baseURI = _baseURI();
        return bytes(baseURI).length != 0 ? string(abi.encodePacked(baseURI, _toString(tokenId), ".json")) : '';
    }

        /**
     * @dev For more efficient reverts.
     */
    function _revert(bytes4 errorSelector) internal pure {
        assembly {
            mstore(0x00, errorSelector)
            revert(0x00, 0x04)
        }
    }
}
```


## 2.Contract Factory 使用合約來部署多個 ERC20 或 ERC721 合約
*         開源合約並提交合約地址
*         每個產出的子合約可以設定不同的參數，name, symbol...

以下import一個合約後new新的合約出來使用：
```
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

import "./ERC721-test.sol";

contract ContractFactory {
    MyERC721NFT[] public erc721TestArray;

    function createNewNFTSeries(string memory name_, string memory symbol_) external payable{
        MyERC721NFT erc721Test = new MyERC721NFT({
            _name : name_,
            _symbol : symbol_
        });
        erc721TestArray.push(erc721Test);
    }

    function getErc721TestArray() external view returns(uint256) {
        return erc721TestArray.length;
    }
}
```
    
    