3
0

More than 1 year has passed since last update.

SolidityでNFTを作成しFoundryを利用してローカルのテストネットにデプロイする

Last updated at Posted at 2023-04-23

概要

スマートコントラクト開発環境のFoundryを利用し、SoidityでNFTを作成する手順と、作成したNFTをローカルのテストネットで動作確認する手順です。

コントラクトの開発環境はTruffleRemix Ideなどが有名だが、今回利用するFoundryはRust製の開発環境で、他のツールと比較して高速で使いやすい点で注目されている。

続編

リポジトリ(一部フォルダ構成は異なりますが、コマンドは同じものが利用可能です。)

利用技術

  • Foundry
  • Solidity
  • OpenZeppelin

事前準備

環境

  • macOS Ventura(v13.0)
  • forge 0.2.0

Foundryのインストール

はじめに、開発環境のFoundryをインストールする。

Foundryはスマートコントラクトの開発に必要なツールチェーンを提供してくれており、デプロイやテストコマンド、テストネットの起動を簡単に実行できる。

Foundryのインストールのために、下記のコマンドを実行する。

curl -L https://foundry.paradigm.xyz | bash
foundryup

上記のコマンド実行時、下記の様なエラーが出た場合は、brew install libusbを実行してlibusbをインストールする。

dyld[32719]: Library not loaded: /usr/local/opt/libusb/lib/libusb-1.0.0.dylib

インストール完了後、コントラクト開発に必要なforge, cast, anvilコマンドを実行できる様になる。

各コマンドの概要は以下のとおり。

コマンド 概要
forge スマートコントラクトのテスト、ビルド、デプロイを行うコマンド
cast デプロイされているスマートコントラクトのメソッドを実行するコマンド
anvil 作成したテストネットの検証様に、テストネットを起動するために利用するコマンド

開発

1. プロジェクトの作成

はじめに、forge initでプロジェクトを作成する。

forge init foundry-nft-sample

その後、作成したプロジェクトに移動し、foundryが提供する汎用ライブラリであるfoundry-rs/forge-stdのインストールを行う。

cd foundry-nft-sample
forge install foundry-rs/forge-std

インストールが完了後、サンプルプロジェクトのビルドとテストを実行する。

$ forge build
[⠢] Compiling...
[⠒] Compiling 6 files with 0.8.19
[⠆] Solc 0.8.19 finished in 886.83ms
Compiler run successful

$ forge test
[⠢] Compiling...
No files changed, compilation skipped

Running 2 tests for test/Counter.t.sol:CounterTest
[PASS] testIncrement() (gas: 28334)
[PASS] testSetNumber(uint256) (runs: 256, μ: 27398, ~: 28409)
Test result: ok. 2 passed; 0 failed; finished in 9.09ms

ビルドとテスト実行が正常に完了すれば、環境構築は完了。

2. NFTの作成

2-1. ライブラリのインストール

Solidityを利用してNFTを作成する場合、OpenZeppelinのライブラリを利用する。OpenZeppelinは、ERC721やERC20準拠のコントラクトを作成するインターフェースを提供しているため、基本的なNFTを作成する場合は、こちらのAPIを利用して作成するのが簡単である。

forge-stdをインストールした際と同様に、forge installコマンドでインストールを行う。

$ forge install openzeppelin/openzeppelin-contracts
Installing openzeppelin-contracts in "/Users/sey323/Workspace/programs/tmp/foundry-nft-sample/lib/openzeppelin-contracts" (url: Some("https://github.com/openzeppelin/openzeppelin-contracts"), tag: None)
    Installed openzeppelin-contracts v4.8.3

remappings.txtの作成

forgeではremappings.txtを作成することで、インストールしたライブラリを呼び出す際に、別名を宣言できる。

今回は同じコードをremixなどでも実行できる様に、@記法でライブラリを呼び出す様にする。

remappings.txt
ds-test/=lib/forge-std/lib/ds-test/src/
forge-std/=lib/forge-std/src/
@openzeppelin/=lib/openzeppelin-contracts/

2-2. 開発

次にNFTを作成する。今回はNFTを発行する際に、所有者のアドレスとNFTに記録したい文章を引数として渡すことで、その文章が記録されたNFTを作成する。

src/SampleNFT.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.14;

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

contract ProposalNFT is ERC721Enumerable {
    mapping(uint256 => bytes) private _sentences;

    constructor() ERC721("SampleNFT", "SPLNFT") {}

    /**
     * NFTのmint
     */
    function mintNft(address ownerAddress, string memory sentence) public {
        // 最新のトークンIDを取得する。
        uint256 tokenId = totalSupply();
        // トークンIDに文章を紐付け
        _sentences[tokenId] = bytes(sentence);
        // NFTの発行
        _mint(ownerAddress, tokenId);
    }

    /**
     * トークンIDに紐づく文章を表示する
     */
    function getSentence(uint256 tokenId) public view returns (string memory) {
        return string(_sentences[tokenId]);
    }
}

3. デプロイ

作成したコントラクトを、ローカルのテストネットにデプロイする。

3.1 環境変数の設定とシークレットフレーズの作成

ローカルのテストネットにデプロイするアカウントのシークレットフレーズと、デプロイするネットワークのアドレスを環境変数として指定する。

.env
export MNEMONIC="ランダムで作成されたシークレットフレーズ"
export FOUNDRY=http://localhost:8545

シークレットフレーズは下記のサイトなどを利用して発行する。

その後環境変数を設定する。

source .env

3-2. テストネットの起動

はじめに、作成したコントラクトをデプロイする際に利用するテストネットを起動する。

$ anvil -m $MNEMONIC
                             _   _
                            (_) | |
      __ _   _ __   __   __  _  | |
     / _` | | '_ \  \ \ / / | | | |
    | (_| | | | | |  \ V /  | | | |
     \__,_| |_| |_|   \_/   |_| |_|

    0.1.0 (e0afc7c 2023-04-22T00:12:06.184818000Z)
    https://github.com/foundry-rs/foundry

Available Accounts
==================

(0) "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" (10000 ETH)
(1) "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" (10000 ETH)
~ 省略 ~
(9) "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC" (10000 ETH)

Private Keys
==================

(0) 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
(1) 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d

~ 省略 ~
(9) 0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6

Wallet
==================
Mnemonic:          ${.envで$MNEMONICに指定したシークレットフレーズ}
Derivation path:   m/44'/60'/0'/0/


Base Fee
==================

1000000000

Gas Limit
==================

30000000

Genesis Timestamp
==================

1682166468

Listening on 127.0.0.1:8545

3-3. デプロイスクリプトの作成

コントラクトをデプロイするために利用する、デプロイスクリプトを作成する。

デプロイするアカウントは、.envMNEMONICに指定したシークレットフレーズのアカウントを指定している。

src/Deploy.s.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import {Script} from "forge-std/Script.sol";
import {SampleNFT} from "../src/SampleNFT.sol";

contract Deploy is Script {
    address internal deployer;
    SampleNFT internal sampleNFT;

    function setUp() public virtual {
        (deployer, ) = deriveRememberKey(vm.envString("MNEMONIC"), 0);
    }

    function run() public {
        vm.startBroadcast(deployer);
        sampleNFT = new SampleNFT();
        vm.stopBroadcast();
    }
}

3-4. デプロイ

3-2. テストネットの起動でテストネットを起動した時とは別のターミナルを開き、デプロイ処理を実行する。

$ source .env # 別ターミナルなので再度読み込み
$ forge script Deploy --broadcast --rpc-url $FOUNDRY 
[⠢] Compiling...
[⠢] Compiling 14 files with 0.8.19
[⠆] Solc 0.8.19 finished in 879.12ms
Compiler run successful
Script ran successfully.

## Setting up (1) EVMs.

==========================

Chain 31337

Estimated gas price: 5 gwei

Estimated total gas used for script: 2000068

Estimated amount required: 0.01000034 ETH

==========================

###
Finding wallets for all the necessary addresses...
##
Sending transactions [0 - 0].
⠁ [00:00:00] [#################################################################################] 1/1 txes (0.0s)
Transactions saved to: /Users/sey323/Workspace/programs/tmp/qiita-foundry-nft-sample/broadcast/Deploy.s.sol/31337/run-latest.json

##
Waiting for receipts.
⠉ [00:00:00] [#############################################################################] 1/1 receipts (0.0s)
##### anvil-hardhat
✅ Hash: 0x6b69612773390a2c0cbce75cb891ebd47b449ea46c65eeb91070892fb1cc0e5e
Contract Address: 0x0c2065e8bf691b057f41a193ad0bf04f8c305428
Block: 1
Paid: 0.006154056 ETH (1538514 gas * 4 gwei)


Transactions saved to: /Users/sey323/Workspace/programs/tmp/qiita-foundry-nft-sample/broadcast/Deploy.s.sol/31337/run-latest.json



==========================

ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.
Total Paid: 0.006154056 ETH (1538514 gas * avg 4 gwei)

Transactions saved to: /Users/sey323/Workspace/programs/tmp/qiita-foundry-nft-sample/broadcast/Deploy.s.sol/31337/run-latest.json

実行後トランザクションの結果が表示される。実行結果の詳細は、broadcast/Deploy.s.sol/31337/run-latest.jsonに保存される。

3-2. テストネットの起動のターミナルを確認するとトランザクションログが表示される。例えばこのうちのContract created:が、デプロイされたスマートコントラクトのコントラクトアドレスとなる。

~省略~
eth_getBlockByNumber
eth_feeHistory
eth_sendRawTransaction
eth_getTransactionReceipt

    Transaction: 0x6b69612773390a2c0cbce75cb891ebd47b449ea46c65eeb91070892fb1cc0e5e
    Contract created: 0x0c2065e8bf691b057f41a193ad0bf04f8c305428 # コントラクトアドレス
    Gas used: 1538514

    Block Number: 1
    Block Hash: 0xba43195d4f617bc69823efd109e17595251de8c9c19b108df55b865035501e2b
    Block Time: "Sat, 22 Apr 2023 13:06:21 +0000"

eth_getTransactionByHash
~省略~

今回の場合は0x0c2065e8bf691b057f41a193ad0bf04f8c305428が、デプロイしたNFTのコントラクトアドレスとなる。

注意
anvil -m $MNEMONICコマンドで起動したテストネットを停止すると、デプロイしたSampleNFTの情報が消えます。
再起動した場合は、再度コントラクトのデプロイを実行し、コントラクトアドレスを取得してください。

4. 実行

テストネットにデプロイしたコントラクトに対して、castコマンドを利用し、transactionやcallリクエストを実行して動作確認する。

4-1. sendリクエストの実行

デプロイしたコントラクトの実行は、foundryのcast sendコマンドを利用する。

$ cast send ${実行するコントラクトのアドレス} \
            "${実行するメソッド}" \
            ${引数1} ${引数2} ... \
            --mnemonic ${実行するアカウントウォレットのシークレットフレーズ} \
            --rpc-url ${コントラクトがデプロイされたチェーンのURL}

上記に従ってコマンドを構築し、Hello Contractという文字列を持つnftを作成する

$ cast send 0x0c2065e8bf691b057f41a193ad0bf04f8c305428 \
            "mintNft(address,string)" \
            "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" \ # 1つ目の引数
            "Hello Contract" \ # 2つ目の引数
            --mnemonic $MNEMONIC \
            --rpc-url $FOUNDRY

blockHash               0x0f1eaea8f37561755e408471f5f9d6d34b546dc2df3f2195e34e6dd37d0bf739
blockNumber             2
contractAddress         
cumulativeGasUsed       123976
effectiveGasPrice       3887820950
gasUsed                 123976
logs                    [{"address":"0x0c2065e8bf691b057f41a193ad0bf04f8c305428","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000000000000000000000000000000000000000000000","0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266","0x0000000000000000000000000000000000000000000000000000000000000000"],"data":"0x","blockHash":"0x0f1eaea8f37561755e408471f5f9d6d34b546dc2df3f2195e34e6dd37d0bf739","blockNumber":"0x2","transactionHash":"0xf59d5ecc1594441e03641bd03bb05c5903a8c4a339be157298572789b8c73e27","transactionIndex":"0x0","logIndex":"0x0","transactionLogIndex":"0x0","removed":false}]
logsBloom               0x00000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000020000000000000100000800000000000000000000000010000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000200000000000000000000000002000000000000000000020000000000000000000000000000000000000000000000000000000000000000000
root                    
status                  1
transactionHash         0xf59d5ecc1594441e03641bd03bb05c5903a8c4a339be157298572789b8c73e27
transactionIndex        0
type                    2

4-2. callリクエスト

callリクエストを実行し、NFTに文章が刻まれていることを確認する。cast callコマンドは下記の様に利用する。

$ cast call ${実行するコントラクトのアドレス} \
            "${実行するメソッド}" \
            ${引数1} ${引数2} ... \
            --mnemonic ${実行するアカウントウォレットのシークレットフレーズ} \
            --rpc-url ${コントラクトがデプロイされたチェーンのURL}

1つ目のNFTはtokenId=0となる。0を指定してgetSentence()を実行し、4-1で記録したNFTの文章を下記のコマンドで確認する。

$ cast call 0x0c2065e8bf691b057f41a193ad0bf04f8c305428 \
            "getSentence(uint256)(string)" \ # solidityで作成したメソッド
            0 \ # 対象のtokenIdを指定する
            --mnemonic $MNEMONIC \
            --rpc-url $FOUNDRY
Hello Contract

Hello Contractが表示され、正しくNFTが発行されたことを確認できた。

参考資料

https://book.getfoundry.sh/
https://www.openzeppelin.com/

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0