3agle
이라는 분이 foundry로 수정된 예제들을 올려줬다. foundry에 목말라 있었는데 너무 잘 됐다는 생각에 감사를 표했다. 가보자고!
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.13;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
contract MyToken is ERC721 {
constructor() ERC721("EthRock", "ROCK") {}
uint256 public constant MAX_SUPPLY = 10000;
uint256 public constant MINT_PRICE = 0.1 ether;
uint256 public counter;
function mint(address to, uint256 tokenId) external payable {
require(counter < MAX_SUPPLY, "Can't exceed MAX_SUPPLY");
require(msg.value >= MINT_PRICE, "Lack of fund");
_mint(to, tokenId);
counter++;
}
}
요런 식으로 작성했다. 다 작성하고 보니 누구나 민팅할 수 있지만 address를 파라미터로 넘겨야 하는 불편함이 있었다. tokenId
를 굳이 입력할 필요도 없었다.
수정본은 다음과 같다.
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.13;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
contract MyToken is ERC721 {
constructor() ERC721("EthRock", "ROCK") {}
uint256 public constant MAX_SUPPLY = 10000;
uint256 public constant MINT_PRICE = 0.1 ether;
uint256 public currentTokenId;
function mint() external payable {
require(currentTokenId < MAX_SUPPLY, "Can't exceed MAX_SUPPLY");
require(msg.value >= MINT_PRICE, "Lack of fund");
_mint(msg.sender, currentTokenId);
currentTokenId++;
}
}
편의상 Re-entrancy guard를 안 했지만 그래도 신경써야 한다. mint()
함수 내부에서 counting 하는 부분이 _mint()
보다 앞에 있는 것이 더 안전하다. 따라서 다음과 같이 바꾸면 좋을 것 같다.
function mint() external payable {
require(currentTokenId < MAX_SUPPLY, "Can't exceed MAX_SUPPLY");
require(msg.value >= MINT_PRICE, "Lack of fund");
currentTokenId++;
_mint(msg.sender, currentTokenId);
}
제공되는 기본 틀은 다음과 같다. 빈칸을 채워보자!
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "forge-std/console.sol";
import "../../src/erc721-1/MyNFT.sol";
/**
@dev run "forge test --match-contract ERC7211"
*/
contract TestERC7211 is Test {
MyNFT public myNft;
address deployer;
address user1;
address user2;
function setUp() public {
deployer = address(1);
user1 = address(2);
user2 = address(3);
vm.deal(deployer, 1 ether);
vm.deal(user1, 1 ether);
myNft = new MyNFT();
}
function test() public {
// TODO: Deployer mints
// TODO: Transfering tokenId 6 from user1 to user2
// TODO: Checking that user2 owns tokenId 6
// TODO: Deployer approves User1 to spend tokenId 3
// TODO: Test that User1 has approval to spend TokenId3
// TODO: Use approval and transfer tokenId 3 from deployer to User1
// TODO: Checking that user1 owns tokenId 3
// TODO: Checking balances after transfer
}
}
foundry는 매우 직관적이라서 비교적 간단하게 작성할 수 있었다. 물론 틀린 부분을 체크해봐야겠지만..
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "forge-std/console.sol";
import "../../src/erc721-1/MyNFT.sol";
/**
@dev run "forge test --match-contract ERC7211"
*/
contract TestERC7211 is Test {
MyNFT public myNft;
address deployer;
address user1;
address user2;
function setUp() public {
deployer = address(1);
user1 = address(2);
user2 = address(3);
vm.deal(deployer, 1 ether);
vm.deal(user1, 1 ether);
myNft = new MyNFT();
}
function test() public {
// TODO: Deployer mints
vm.prank(deployer);
for (uint256 i = 0; i < 5; i++) {
myNft.mint();
}
vm.prank(user1);
for (uint256 i = 0; i < 3; i++) {
myNft.mint();
}
// TODO: Transfering tokenId 6 from user1 to user2
vm.prank(user1);
myNft.transferFrom(user1, user2, 6);
// TODO: Checking that user2 owns tokenId 6
assertEq(user2, myNft.ownerOf(6));
// TODO: Deployer approves User1 to spend tokenId 3
vm.prank(deployer);
myNft.approve(user1, 3);
// TODO: Test that User1 has approval to spend TokenId3
assertEq(user1, myNft.getApproved(3));
// TODO: Use approval and transfer tokenId 3 from deployer to User1
vm.prank(user1);
myNft.safeTransferFrom(deployer, user1, 3);
// TODO: Checking that user1 owns tokenId 3
assertEq(user1, myNft.ownerOf(3));
// TODO: Checking balances after transfer
assertEq(myNft.balanceOf(deployer), 4);
assertEq(myNft.balanceOf(user1), 3);
assertEq(myNft.balanceOf(user2), 1);
}
}
아래 명령어로 테스트를 돌려보자.
forge test --match-contract ERC7211 -vvvv
는 실패 😇
민팅할 때 단순히 mint()
함수를 호출만 해서 그런 것 같다. 아래와 같이 value를 넣어서 함수를 호출할 수 있다.
vm.prank(deployer);
for (uint256 i = 0; i < 5; i++) {
myNft.mint{value: 0.1 ether}();
}
vm.prank(user1);
for (uint256 i = 0; i < 3; i++) {
myNft.mint{value: 0.1 ether}();
}
그런데 또 fail이 났다. 자세히 보니 민팅한 NFT들이 user한테 가는게 아니라 컨트랙트한테 가는 걸 알 수 있다(?!)
알고보니 for
문 안에 vm.prank()
도 같이 넣어줘야 했었다..
이후 테스트 성공!
역시 foundry가 보기도 편하고 깔끔하다.