HackQuest_Foundry_NFT
uwupu 啦啦啦啦啦

使用Solmate的ERC721标准。

Foundry NFT

Usage

  1. 安装依赖
1
forge install transmissions11/solmate Openzeppelin/openzeppelin-contracts
  • transmissions11/solmate :节省gas而优化的ERC721标准实现;
  • Openzeppelin/openzeppelin-contracts:包含了OpenZeppelin提供的智能合约库,这些库广泛用于提供安全性和遵循最佳实践的智能合约开发。。
  1. 编写基本NFT合约
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.20;

// 引入ERC721
import "solmate/tokens/ERC721.sol";
// 其实Strings.sol并非NFT合约的必须引入项
import "openzeppelin-contracts/contracts/utils/Strings.sol";

contract NFT is ERC721 {
uint256 public currentTokenId;//当前TokenId

constructor(
string memory _name,
string memory _symbol
) ERC721(_name, _symbol) {}

//mint一个NFT
function mintTo(address recipient) public payable returns (uint256) {
uint256 newItemId = ++currentTokenId;
_safeMint(recipient, newItemId);//使用ERC721的方法
return newItemId;
}

function tokenURI(
uint256 id
) public view virtual override returns (string memory) {
return Strings.toString(id);
}
}

使用CAST操作合约

铸造NFT

1
cast send --rpc-url=$RPC_URL <contractAddress>  "mintTo(address)" <mintAddress> --private-key=$PRIVATE_KEY

查看NFT归属

1
cast call --rpc-url=$RPC_URL --private-key=$PRIVATE_KEY <contractAddress> "ownerOf(uint256)" 1

测试NFT

这块儿好粗略,就留下代码吧!

src/NFT.sol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.20;

import "solmate/tokens/ERC721.sol";
import "openzeppelin-contracts/contracts/utils/Strings.sol"; //Strings的工具类
import "openzeppelin-contracts/contracts/access/Ownable.sol"; //据说实现了让一些方法实现所有权管理功能,如转移所有权和限制访问。但这里的代码...没有实现

//定义了三个错误
error MintPriceNotPaid();
error MaxSupply();
error NonExistentTokenURI();

//ERC721是NFT的实现
// Ownable是权限管理的实现,这里好像没用到,虽然写了
contract NFT is ERC721, Ownable {
uint256 public currentTokenId;
string public baseURI; //NFT的元数据
uint256 public constant MAX_CAPACITY = 10_000; //最大发行量
uint256 public constant MINT_PRICE = 0.05 ether; //铸造需要的ether
constructor(
string memory _name,
string memory _symbol,
string memory _baseURI
) ERC721(_name, _symbol) Ownable(msg.sender) {
baseURI = _baseURI;
}

function mintTo(address recipient) public payable returns (uint256) {
//检查是否花够了MINT费用
if (msg.value != MINT_PRICE) {
revert MintPriceNotPaid();
}
//创建一个新的ID
uint256 newItemId = ++currentTokenId;
//查询发行量是否超出
if (newItemId > MAX_CAPACITY) {
revert MaxSupply();
}
//调用ERC721的mint
_safeMint(recipient, newItemId);
return newItemId;
}

function tokenURI(
uint256 id
) public view virtual override returns (string memory) {
//查询该ID是否有效
if (ownerOf(id) == address(0)) {
revert NonExistentTokenURI();
}
//返回元数据
if (bytes(baseURI).length > 0) {
return string(abi.encodePacked(baseURI, Strings.toString(id)));
} else {
return "";
}
}
}

test/NFT.t.sol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
pragma solidity 0.8.20;
import "forge-std/Test.sol";
import "../src/NFT.sol";
contract NFTTest is Test {
NFT private nft;

//初始化
function setUp() public {
nft = new NFT("NFT_tutorial", "TUT", "baseUri");
}

//测试未支付的情况下的铸造操作
function test_RevertMintWithoutValue() public {
vm.expectRevert(MintPriceNotPaid.selector);
nft.mintTo(address(1));
}

//测试铸造的币数量错误
function test_MintPricePaid() public {
nft.mintTo{value: 0.08 ether}(address(1));
}

//测试0地址铸造
function test_RevertMintToZeroAddress() public {
vm.expectRevert("INVALID_RECIPIENT");
nft.mintTo{value: 0.08 ether}(address(0));
}
}

 评论