2022. 4. 1. 18:07ㆍ개발 잡부/블록체인
여기서부터는 번역이 안 되어있다.
1. Getting Set Up
이 챕터에서는 Truffle, Mocha, Chai를 중심으로
이더리움 스마트 컨트랙트를 살펴볼 것이다.
트러플은 솔리디티와 자바스크립트로 된
테스트를 지원해주지만,
이번 레슨에서는 자바스크립트만 보기로 한다.
정답
touch test/CryptoZombies.js
2. Getting Set Up (cont'd)
스마트 컨트랙트를 컴파일할 때마다,
솔리디티 컴파일러는 JSON파일을 출력한다.
여기에는 이진으로 표현된 컨트랙트를 포함하며,
build/contracts 폴더에 생성된다.
migration을 실행하면 Truffle은 이 파일을 업데이트해준다.
첫번째로 해야 할 일은,
상호작용할 수 있게 컨트랙트의 빌드 아티팩트를 불러오는 것이다.
다음과 같이 불러올 수 있다.
const MyAwesomeContract = artifacts.require(“MyAwesomeContract”);
이렇게 반환된 것을 '계약 추상화 (contract abstraction)'이라고 한다.
자바스크립트 인터페이스를 컨트랙트에 전달하는 것이다.
contract() 함수
Truffle은 테스트를 간단하게 하기 위해 Mocha로 감싸여져있다.
그룹테스트를 하고 싶다면 contract함수를 호출한다.
이 함수는 Mocha의 describe를 확장한 기능이다.
contract함수는 계약 이름과
실제 테스트 코드를 적을 수 있는 callback을 인자로 받는다.
it()함수를 사용하면 함수를 실행할 수 있다.
이러한 테스트 코드를 통해
동작을 확인할 수 있다.
정답
const CryptoZombies = artifacts.require("CryptoZombies");
contract("CryptoZombies", (accounts) => {
it("should be able to create a new zombie", () => {
})
})
3. 첫 테스트 - Creating a New Zombie
로컬 이더리움 네트워크에서 테스트하기 위해
'Ganache(가나슈)'라는 툴을 사용할 것이다.
이 가나슈는 10개의 테스트 계정에 100개의 이더리움을 가지고 시작한다.
이 테스트 계정들을 담은 변수가 contract() callback의 accounts 인자다.
그리고 테스트에서 알기 쉽게 1번과 2번 계좌에 이름을 붙인다
let [alice, bob] = accounts;
새로운 좀비 생성하기
앨리스(alice)가 새 좀비를 갖게하기 위해서,
우리는 createRandomZombie함수를 호출하려고 한다.
정답
...
//1. initialize `alice` and `bob`
let [alice, bob] = accounts;
it("should be able to create a new zombie", async () => { //2 & 3. Replace the first parameter and make the callback async
...
4. 첫 테스트 - Creating a new zombie (cont'd)
이제 이런식으로 테스트를 진행할건데,
테스트는 이런 순서로 진행된다.
1. set up: 초기 상태를 초기화한다.
2. act: 어떤 행동을 취한다
3. assert: 결과를 확인한다.
컨트랙트 추상화를 객체화하기
제목이 좀 어려운데
우리가 artifacts를 통해 가져온 추상화된 컨트랙트를
하나의 JS 객체로 만드려는 것이다.
다음의 방식으로 인스턴스를 생성할 수 있다.
const contractInstance = await MyAwesomeContract.new();
정답
...
// start here
const contractInstance = await CryptoZombies.new();
...
5. 첫 테스트 - Creating a new zombie (cont'd)
컨트랙트 안의 함수를 불러오는 것도
컨트랙트 객체에서 일반 함수 불러오듯 한다.
const result = await contractInstance.createRandomZombie(zombieNames[0], {from: alice});
로그와 이벤트
우리가 artifacts.require를 호출하면
Truffle은 자동으로 로그를 제공해준다.
이 로그를 통해 호출된 함수나 인자를 알 수 있다.
그리고 우리가 컨트랙트의 함수를 호출하고 반환받는 변수에는
해쉬값(result.tx)이나 영수증(result.receipt)등이 있다.
(result.receipt.status가 true면 정상적으로 작동했다는 뜻이다)
결과 비교하기 (assert)
우리는 equal이나 deepEqual함수를 통해
결과와 예측값이 같은지 확인해볼 예정이다.
정답
...
const result = await contractInstance.createRandomZombie(zombieNames[0], {from: alice});
assert.equal(result.receipt.status, true);
assert.equal(result.logs[0].args.name, zombieNames[0]);
...
6. Keeping the Fun in the game
이번 챕터에서는 좀비가 없는 유저에게만 좀비를 생성해주는
코드가 정상 작동하는지 확인하려고 한다.
Mocha의 기능중에는 'hooks'라는게 있는데,
각 테스트가 실행되기 전에 실행할 내용들을 적을 수 있다.
즉, 반복되는 초기 설정을 노가다로 할 필요 없다는 뜻이다.
beforeEach(async () => { });
정답
...
// start here
let contractInstance;
...
beforeEach(async () => {
// let's put here the code that creates a new contract instance
contractInstance = await CryptoZombies.new();
});
...
//define the new it() function
it("should not allow two zombies", async () => {
})
...
7. Keeping the Fun in the game (cont'd)
이 챕터에서는 우리가 두번째 좀비를 생성할 때,
throw를 호출하는지 (에러를 발생하는지) 확인 할 것이다.
친절히도 throw를 던지면 자동으로 확인해주는 유틸 함수를 제공해줬다.
정답
...
// start here
await contractInstance.createRandomZombie(zombieNames[0], {from: alice});
await utils.shouldThrow(contractInstance.createRandomZombie(zombieNames[1], {from: alice}));
...
8. Zombie Transfers (좀비 전송하기)
이제 ERC721로 구성된 좀비를 다른 유저에게
전송할 수 있는지 테스트할 것이다.
이런 꼼꼼한 테스트는 외부에서
어떤 시나리오로 프로젝트가 돌아가는지 알 수 있다.
안정성이 높아지는 것은 덤이다.
context 함수
context함수는 Truffle에서 제공하는 기능이다.
로그를 볼 때, 어떤 맥락에서 테스트가 진행되는지 파악하기 쉽게 해준다.
사용법은 다음과 같다.
context("with the single-step transfer scenario", async () => {
it("should transfer a zombie", async () => {
// TODO: Test the single-step transfer scenario.
})
})
참 쉽죠?
그리고 컨텍스트 안의 테스트를 생략하는 방법은 다음과 같다.
xcontext("with the single-step transfer scenario", async () => {
context앞에 x를 붙여 xcontext를 호출하면 된다.
...
// start here
xcontext("with the single-step transfer scenario", async () => {
it("should transfer a zombie", async () => {
// TODO: Test the single-step transfer scenario.
})
})
xcontext("with the two-step transfer scenario", async () => {
it("should approve and then transfer a zombie when the approved address calls transferFrom", async () => {
// TODO: Test the two-step scenario. The approved address calls transferFrom
})
it("should approve and then transfer a zombie when the owner calls transferFrom", async () => {
// TODO: Test the two-step scenario. The owner calls transferFrom
})
})
...
9. ERC721 Token Transfers - Single Step Scenario
이전 챕터에서 하려 했던 전송 시나리오를 한번 테스트해보자.
1. 앨리스가 좀비를 하나 생성한다.
2. 앨리스가 밥에게 좀비를 전송한다.
3. 밥이 좀비의 새 주인인지 확인한다.
정답
...
// start here.
const result = await contractInstance.createRandomZombie(zombieNames[0], {from: alice});
const zombieId = result.logs[0].args.zombieId.toNumber();;
await contractInstance.transferFrom(alice, bob, zombieId, {from: alice});
const newOwner = await contractInstance.ownerOf(zombieId);
assert.equal(newOwner, bob);
...
10. ERC721 Token Transfer - Two Step Scenario
이제 두개의 시나리오를 해보자.
1. 앨리스가 밥에게 수령을 허가한다(approve) -> 밥이 수령한다.
2. 앨리스가 밥에게 수령을 허가한다(approve) -> 앨리스가 수령한다.
정답
...
// start here
await contractInstance.approve(bob, zombieId, {from: alice});
await contractInstance.transferFrom(alice, bob, zombieId, {from: bob});
...
11. ERC721 Token Transfer - Two Step Scenario (cont'd)
정답
...
it("should approve and then transfer a zombie when the owner calls transferFrom", async () => {
// TODO: start
const result = await contractInstance.createRandomZombie(zombieNames[0], {from: alice});
const zombieId = result.logs[0].args.zombieId.toNumber();
await contractInstance.approve(bob, zombieId, {from: alice});
await contractInstance.transferFrom(alice, bob, zombieId, {from: alice});
const newOwner = await contractInstance.ownerOf(zombieId);
assert.equal(newOwner,bob);
})
...
12. 좀비 공격
이제 좀비 공격을 테스트할건데,
다음의 순서대로 할 예정이다.
1. 앨리스랑 밥이 각자 좀비를 한개씩 만든다.
2. 앨리스가 밥을 공격한다
3. result.receipt.status가 true인지 확인한다.
여기서 문제점은 좀비는 생성 후 하루동안 공격을 못 한다는 것이다.
그래서 시간을 스킵해주는 함수를 배워보자.
Time Traverlling
가나슈(Ganache)는 두가지 함수를 통해
시간을 스킵할 수 있게 해준다.
evm_increaseTime: 다음 블록의 시간을 증가시킨다.
evm_mine: 새 블록을 마이닝(채굴)한다.
한마디로 시간을 증가시키는 블록을 생성하고,
그 블록을 채굴해서 적용시키는 것이다.
(물론 이건 메인넷에서는 안 된다)
정답
...
//TODO: increase the time
await time.increase(time.duration.days(1));
...
13. Chai를 이용한 신기한 테스트 기능들
지금까지는 assert 모듈을 썼지만 그렇게 좋지는 않다.
그래서 더 좋은 Chai를 써보자.(미드 차이)
Chai Assertion Libary (Chai 검증 라이브러리)
이 튜토리얼에서는 세가지 검증 방식을 써 볼 것이다.
expect: 자연어를 검증할 수 있다.
should: expect와 비슷하지만 should로 시작한다.
assert: node.js와 비슷하게 쓸 수 있다.
expect(zombieName).to.equal('My Awesome Zombie');
정답
...
//TODO: import expect into our project
var expect = require('chai').expect;
...
//TODO: replace with expect
expect(result.receipt.status).to.equal(true);
expect(result.logs[0].args.name).to.equal(zombieNames[0]);
...
// 나머지도 똑같이 expect().to.equal();로 바꿔주자.
14. Testing Against Loom ('Loom'에서 테스트하기)
Loom은 테스트넷의 이름이다.
이더리움보다 더 빠르고 싸게 상호작용하기 때문에,
게임이나 DAppChain을 만들기 유리하다.
보안요소
이 truffle.js에 privateKey를 직접 작성하지 말자.
dotenv나 다른 방법을 이용해 코드 외의 방식으로 처리하자.
정답
...
// TODO: Replace the line below
const loomTruffleProvider = new LoomTruffleProvider(chainId, writeUrl, readUrl, privateKey);
loomTruffleProvider.createExtraAccountsFromMnemonic(mnemonic, 10);
return loomTruffleProvider;
...
후기
영어로 써져있어서 평소보다 오래걸리기는 했지만
나름대로 굉장히 유용한 내용이었다.
학교에서 테스트하는 방법을 배우기는 했지만
프로젝트용 땜빵이었고, 이렇게 써보니 뭔가 더 재미있는 느낌이다.
'개발 잡부 > 블록체인' 카테고리의 다른 글
Truffle 알아보기 - Truffle QuickStart (0) | 2022.04.01 |
---|---|
Transaction Receipt 내용보기 (0) | 2022.04.01 |
나만의 후원사이트 만들기 (cheers-coffee) 1 (0) | 2022.04.01 |
OpenZeppelin에서 msg.sender를 사용하지 않는 이유 (0) | 2022.04.01 |
address에서 send, transfer를 호출할 수 없는 이슈 (0) | 2022.04.01 |