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