토큰 컨트랙트 만드는 건 Openzepplin wizard를 이용하면 쉽게 만들 수 있다. 토큰 이름과 심볼을 적어주고 입맛에 맞게 Mintable, Burnerble, Ownable에 체크하고 복붙해주면 끝.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract ExToken is ERC20, ERC20Burnable, Ownable {
constructor() ERC20("ExToken", "EX") {}
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
}
const { ethers } = require('hardhat');
const { expect } = require('chai');
describe('ERC20 Tokens Exercise 1', function () {
let deployer, user1, user2, user3;
// Constants
const DEPLOYER_MINT = ethers.utils.parseEther('100000');
const USERS_MINT = ethers.utils.parseEther('5000');
const FIRST_TRANSFER = ethers.utils.parseEther('100');
const SECOND_TRANSFER = ethers.utils.parseEther('1000');
before(async function () {
/** Deployment and minting tests */
[deployer, user1, user2, user3] = await ethers.getSigners();
// TODO: Contract deployment
// TODO: Minting
// TODO: Check Minting
});
it('Transfer tests', async function () {
/** Transfers Tests */
// TODO: First transfer
// TODO: Approval & Allowance test
// TODO: Second transfer
// TODO: Checking balances after transfer
});
});
제공되는 기본 틀의 빈 공간을 채워보자.
const { ethers } = require("hardhat");
const { expect } = require("chai");
describe("ERC20 Tokens Exercise 1", function () {
let deployer, user1, user2, user3;
// Constants
const DEPLOYER_MINT = ethers.utils.parseEther("100000");
const USERS_MINT = ethers.utils.parseEther("5000");
const FIRST_TRANSFER = ethers.utils.parseEther("100");
const SECOND_TRANSFER = ethers.utils.parseEther("1000");
before(async function () {
/** Deployment and minting tests */
[deployer, user1, user2, user3] = await ethers.getSigners();
// TODO: Contract deployment
const TokenFactory = await ethers.getContractFactory("ExToken", deployer);
this.token = await TokenFactory.deploy();
// TODO: Minting
await this.token.mint(deployer.address, DEPLOYER_MINT);
await this.token.mint(user1.address, USERS_MINT);
await this.token.mint(user2.address, USERS_MINT);
await this.token.mint(user3.address, USERS_MINT);
// TODO: Check Minting
expect(await this.token.balanceOf(deployer.address)).to.equal(
DEPLOYER_MINT
);
expect(await this.token.balanceOf(user1.address)).to.equal(USERS_MINT);
expect(await this.token.balanceOf(user2.address)).to.equal(USERS_MINT);
expect(await this.token.balanceOf(user3.address)).to.equal(USERS_MINT);
});
it("Transfer tests", async function () {
/** Transfers Tests */
// TODO: First transfer
await this.token.connect(user2).transfer(user3.address, FIRST_TRANSFER);
// TODO: Approval & Allowance test
await this.token.connect(user3).approve(user1.address, SECOND_TRANSFER);
expect(await this.token.allowance(user3.address, user1.address)).to.equal(
SECOND_TRANSFER
);
// TODO: Second transfer
await this.token
.connect(user1)
.transferFrom(user3.address, user1.address, SECOND_TRANSFER);
// TODO: Checking balances after transfer
expect(await this.token.balanceOf(deployer.address)).to.equal(
DEPLOYER_MINT
);
expect(await this.token.balanceOf(user1.address)).to.equal(
USERS_MINT.add(SECOND_TRANSFER)
);
expect(await this.token.balanceOf(user2.address)).to.equal(
USERS_MINT.sub(FIRST_TRANSFER)
);
expect(await this.token.balanceOf(user3.address)).to.equal(
USERS_MINT.add(FIRST_TRANSFER).sub(SECOND_TRANSFER)
);
});
});
이제 npm run erc20-1
으로 테스트 해주면 된다. Johnny가 테스트 script를 아래처럼 짜놔서 위 명령어로 테스트가 가능하다. package.json
을 보면 요런식으로 미리 만들어놓은 것을 알 수 있다.
테스트 성공!
//Constants
로 만들어놓았다. 이걸 지나치고 갯수를 바로 입력하려고 하니까 코드가 많이 지저분해졌었다. 다음에 테스트코드를 짤 때는 가이드 받은 것처럼 이더 갯수를 미리 const
로 만들어놓자. // Constants
const DEPLOYER_MINT = ethers.utils.parseEther("100000");
const USERS_MINT = ethers.utils.parseEther("5000");
const FIRST_TRANSFER = ethers.utils.parseEther("100");
const SECOND_TRANSFER = ethers.utils.parseEther("1000");
const
를 쓰려 했다. 정말 부끄럽지만 잘못된 코드를 다시 쓰지 않기 위해 기록해두려 한다. 예를 들어 아래처럼 단순히 토큰을 민팅하는 코드인데도 불구하고 따로 변수에 저장하려고 했다. 내가 쓰는 코드의 목적이 무엇인지 반드시 먼저 생각하고 코드로 옮기려고 하자.const mint1 = await this.token.mint(user1.address, USERS_MINT);
mint()
또는 balanceOf()
에 파라미터 설정을 잘못했었다. address까지 입력해야 하는데 그냥 Singer를 입력했던 것. 더 혼란스러웠던 건 connect()
에는 address 없이 Signer만 입력해야 한다.await this.token.balanceOf(user1.address);
await this.token.connect(user2).transfer(user3.address, FIRST_TRANSFER);
혼란스러울 때는 공식문서를 찾아보는 것이 최고다. Openzeppelin의 IERC20을 보면 파라미터에 address
를 넣으라고 명시되어 있다. 그러니 Signer
를 넣는 것은 애초에 말이 안 되는 행동이었다.
다음은 ethers 공식문서에서 connect
를 찾아봤다. 역시나 address
가 아니라 Signer
를 넣으라고 명시되어 있다. 항상 답은 공식문서에 있다는 것을 잊지 말자.