# Foundry NFT實作教學/介紹 (2)開發並部署合約 # 前言 上篇: [Foundry NFT實作教學/介紹 (1)你的第一個Foundry專案](https://hackmd.io/@RainFox/HyFJFaaJ2) 在上次我們已經建立了一個Foundry的專案,再來我們就要開始開發NFT的合約,這邊的部署我們會以Localhost為主,如果需要部署到Sepolia/Goerli 請自行更換RPC-URL # 開始開發 ### 1. 安裝套件 openzeppelin contracts ``` forge install OpenZeppelin/openzeppelin-contracts ``` 如果成功你就會看到.gitmodules 多了下面這個 ``` [submodule "lib/openzeppelin-contracts"] path = lib/openzeppelin-contracts url = https://github.com/OpenZeppelin/openzeppelin-contracts branch = v4.8.2 ``` 如果安裝失敗,並出現下面訊息,代表你需要先去清空你的working跟stage area,這部分是git的相關問題,因為一般狀況下只要安裝一個套件他就幫你自動做一個commit,如果你不清楚git的相關操作,那麼你就先把你現在的改動都先commit,再`forge install OpenZeppelin/openzeppelin-contracts` 即可 ``` Error: The target directory is a part of or on its own an already initialized git repository, and it requires clean working and staging areas, including no untracked files. Check the current git repository's status with `git status`. Then, you can track files with `git add ...` and then commit them with `git commit`, ignore them in the `.gitignore` file, or run this command again with the `--no-commit` flag. If none of the previous steps worked, please open an issue at: https://github.com/foundry-rs/foundry/issues/new/choose ``` > 若在安裝上有其他疑問跟想嘗試其他功能,可以參考 [forge-install](https://book.getfoundry.sh/reference/forge/forge-install) > > Foundry安裝方法是採用git submodule,所以只要是git上的專案都可以抓下來使用,而forge install會自動幫你加入進lib資料夾中,但你也可以採用一般git submodule方法來裝套件 ### 2. Remapping 這並非開發的中的必要步驟,但是這個是Foundry提供的有趣功能,讓你在contract做import時,可以自定義import的方式,而不用把所有的路徑都打上去 ``` forge remappings ``` Output: ``` ds-test/=lib/forge-std/lib/ds-test/src/ forge-std/=lib/forge-std/src/ openzeppelin-contracts/=lib/openzeppelin-contracts/ ``` 這邊會顯示你所有的套件跟路徑,以`forge-std`來說,`forge-std`的實際位置在lib/forge-std/src/,假設我們的contract想要import forge-std的Test.sol進來做使用,那麼就只需要 `import "forge-std/Test.sol"`即可完成import,不需要把所有路徑都打上去 上面的Output出來的是預設的remapping方式,但是你也可以另外生成一個remapping的檔案來去自定義你喜歡的方式 ``` forge remappings > remappings.txt ``` 執行完上面指令你就會發現專案中多了一個`remappings.txt`的檔案,可以藉由更改裡面的資訊來完成自定義 可參考 [forge-remappings](https://book.getfoundry.sh/reference/forge/forge-remappings) ### 3.創建合約 在`src`資料夾創建一個`NFT.sol`的新檔案,並將程式碼複製上去 ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import {ERC721} from "openzeppelin-contracts/contracts/token/ERC721/ERC721.sol"; contract NFT is ERC721 { constructor() ERC721("NFT", "NFT") {} function mint(address to, uint256 tokenId) public { _mint(to, tokenId); } } ``` 這邊就不解釋solidity相關的語法,唯一要注意的地方在於import的部分要去注意你的remapping有沒有改動,有的話上面的import路徑也要隨之修改 ### 4. Deploy 這個步驟我們需要把我們的合約部署到本地鏈上 所以我們必須先開啟本地鏈 ``` anvil ``` anvil是Foundry裡面其中一個套件,負責鏈上等等的功能,類似於`npx hardhat node` 開啟後我們需要先創建.env檔,用來儲存等下部署需要的參數 ``` LOCALHOST="http://localhost:8545" PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" ``` > 如果要部署在本地鏈上,私鑰請用anvil裡面提供的 接著輸入以下指令 ``` source .env ``` 都設定好之後,就可以來部署合約了,部署指令如下 ``` forge create --rpc-url=$LOCALHOST --private-key=$PRIVATE_KEY NFT ``` 上面指令由左而右我們分別設定RPC-URL,以及部署者的private key跟要部署的合約 > 如果constructor有需要設定參數,指令內要使用`--constructor-args`指定參數,可參考 [forge-create](https://book.getfoundry.sh/reference/forge/forge-create) Output: ``` [⠔] Compiling... [⠆] Compiling 11 files with 0.8.18 [⠔] Solc 0.8.18 finished in 684.41ms Compiler run successful Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3 Transaction hash: 0x8c876aea8faeb25d18d376db491dfbb82ef33ca9bc3814615464a98bab458d46 ``` 若成功部署可以看到上面有部署的相關資訊,而anvil上面也會有交易紀錄 ``` // On anvil eth_sendRawTransaction Transaction: 0x8c876aea8faeb25d18d376db491dfbb82ef33ca9bc3814615464a98bab458d46 Contract created: 0x5fbdb2315678afecb367f032d93f642f64180aa3 Gas used: 1139494 Block Number: 1 Block Hash: 0x71e51ecf4deaebaf20509be115d8ef4604bcb6b6341b4dcfcc77618324197b42 Block Time: "Wed, 12 Apr 2023 05:58:53 +0000" eth_getTransactionByHash ``` ### 5. 確認部署結果 為了確定我們的合約是不是真的部署到localhost上了,所以我們實際來調用合約來mint一個tokenId為1的NFT 試試 ``` cast send --private-key $USER_PRIVATE_KEY $CONTRACT "mint(address,uint256)" $USER_ADDR 1 ``` Output: ``` //On anvil blockHash 0x9903daae0e200f79fc952fec16aeb0f55f8f65a253d7e3990294731da27b5441 blockNumber 2 contractAddress cumulativeGasUsed 68758 effectiveGasPrice 3884495783 gasUsed 68758 logs [{"address":"0x5fbdb2315678afecb367f032d93f642f64180aa3","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000000000000000000000000000000000000000000000","0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8","0x0000000000000000000000000000000000000000000000000000000000000001"],"data":"0x","blockHash":"0x9903daae0e200f79fc952fec16aeb0f55f8f65a253d7e3990294731da27b5441","blockNumber":"0x2","transactionHash":"0x60363e078c8b2c95814535d5099e2fb29cf8023eedce7640e6558bf8a18a5843","transactionIndex":"0x0","logIndex":"0x0","transactionLogIndex":"0x0","removed":false}] logsBloom 0x00000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000008000000000000000000040000000000000000000000000840020000000000000000000800000000000000000000000010000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000042000000000000000000000000000000000000000000000000000060000000000000000000000000000000000001000000000000000000000000000000 root status 1 transactionHash 0x60363e078c8b2c95814535d5099e2fb29cf8023eedce7640e6558bf8a18a5843 transactionIndex 0 type ``` 這邊我們用的cast是Foundry的其中一個套件,可以把他想成provider,負責獲取一些鏈上的資訊,跟鏈上做互動等等,我們這邊做的是送一個交易到鏈上,所以使用的是`cast send` 上面`$USER_PRIVATE_KEY` `$USER_ADDR`都是後面新增的,可隨意替換成自己想要的address(address跟private key必須對應) `$CONTRACT` 指得是你部署的合約地址,以上面的deploy出來的資訊來看,`$CONTRACT` 就是 0x5FbDB2315678afecb367f032d93F642f64180aa3 > 上述這些是環境變數的用法,也可以直接輸入值進去 mint完之後,我們必須確定這個交易是不是真的有成功更改到合約的狀態,除了可以像deploy那樣查看anvil上面的資訊外,我們可以選擇調用ownerOf這個function,查看tokenId 1 的擁有者是不是為你剛剛輸入的`$USER_ADDR` ``` cast call $CONTRACT "ownerOf(uint256)" 1 ``` Output: ``` 0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8 ``` 這邊確實有返回一個值給我,且確認這個值是不是跟你剛剛輸入的`$USER_ADDR`一樣 # 結尾 到這邊都沒問題的話,恭喜你! Foundry的基本操作你已經掌握一部分了,但有些人此時可能會發現,指令比起hardhat相比還要繁瑣,要下的參數又更多,因此這邊只能透過撰寫shell script來解決 下一篇我們會介紹 [Foundry NFT實作教學/介紹 (3)Unit Test](https://hackmd.io/@RainFox/ByVFmR7Gn) 若在製作上有問題,可以參考原始碼: [hello-foundry](https://github.com/LI-YONG-QI/hello-foundry)