일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 쉘스크립트
- 노드
- 머클루트
- 변수
- 리액트
- 우분투
- 자바스크립트
- 솔리디티
- Sequelize
- 환경변수
- 라우터
- 시퀄라이즈
- npm
- wget
- 리눅스
- Docker
- 깃허브
- wsl
- 이더리움
- 전역설치
- node.js 교과서 따라하기
- 머클트리
- immer
- centos
- 리액트를 다루는 기술
- 설치
- MariaDB
- 벨로포터
- 블록체인
- 일반유저
- Today
- Total
코드코코
[솔리디티][디앱] lottery - Lottery Bet 테스트 본문
test/lottery.test.js
const Lottery = artifacts.require('Lottery');
//[deployer,user1,user2] : ganache-cli에서 생성한 주소가 차례대로 들어옴.
contract('Lottery', function ([deployer, user1, user2]) {
let lottery;
beforeEach(async () => {
console.log('Before each');
//테스트용 배포
lottery = await Lottery.new()
})
// it('Basic test', async () => {
// console.log('Basic test');
// let owner = await lottery.owner();
// //let value = await lottery.getSomeValue();
// console.log(`owner : ${owner}`);
// console.log(`value : ${value}`);
// assert.equal(value, 5);
// })
//it.only : 특정 케이스만 테스틑 할 때
it('getPot should return current pot', async () => {
let pot = await lottery.getPot();
// 처음에는 팟머니가 없는 상황이라 0
assert.equal(pot, 0);
})
describe('Bet', function () {
it.only('should fail when the bet money is not 0.005 ETH', async () => {
// Fail transaction
// 바이트 하나 보내고, 트랜잭션 오브젝트
await lottery.bet('0xab', { from: user1, value: 4000000000000000 })
// transaction object {chainId, value, to, from, gas(Limit), gasPrice}
// chainId : 블록체인마다, 네트워크 마다 다른 체인아이디
// value : 이더
// to : address
// from : 누가 보냇는지
});
it('should put the bet to the bet queue with 1 bet', async () => {
// bet
// check contract balance === 0.005
// check bet info
// check log
})
})
})
테스트하기 위해 가나슈로 테스트서버열기
ganache-cli -d -m tutorial
테스트 실행
truffle test test/lottery.test.js
조건에 미충족 시 에러 확인
조건 충족시 통과 확인
open zeppelin
솔리디티 기반의 스마트 컨트렉트를 개발하는 표준 프레임워크. truffle과 유사하지만, truffle 보다 기능이 다양함
open zeppelin의 test-helper 기능을 참고하여 에러 핸들링 테스트 코드 작성
test/assertRevert.js
module, exports = async (promise) => {
try {
//promise니까 await 걸기.
await promise;
// promise를 기다렸는데 에러가 catch문 쪽으로 넘어가지 않으면 문제가 있으므로,
// 리버트가 예상되었으나 일어나지 않았다는 문구를 작성.
assert.fail('Expected revert not received')
} catch (error) {
//원하는 대로 에러가 캐치문으로 온다면, 에러 메세지를 받아 search한다. search는 revert의 인덱스를 구할 수 있게 함.
const revertFound = error.message.search('revert') >= 0;
assert(revertFound, `Expected "revert", get ${error} instead`);
}
}
test/lottery.test.js 파일 상단에 assertRevert.js 불러오기
테스크 코드가 올바르게 작동하는 지 확인
truffle test test/lottery.test.js
성공한 트랜잭션 테스트 코드 작성
contractAddress : 새로운 스마트 컨트랙트를 만드는 트랜잭션일 경우 값이 나옴.
args: logs의 이벤트의 파라미터들도 확인가능.
코드 추가 하여 테스트
상단에 betAmount 선언 및 할당 후, value : 5000000000000000를 value: betAmount로 변경
betInfo test 코드 작성
위에서 확인했던,
test/expectEvent.js 파일 생성 및 작성
npm i chai
test/lottery.test.js 에서 expectEvent 불러오기
테스트 코드 추가
WIN으로 바꿔서 테스트 해보면 실패가 뜨는 것을 확인 할 수 있음.
bet 부분만 전체 테스트 : only 키워드 사용
test/lottery.test.js 전체코드
const Lottery = artifacts.require('Lottery');
const assertRevert = require('./assertRevert')
const expectEvent = require('./expectEvent');
//[deployer,user1,user2] : ganache-cli에서 생성한 주소가 차례대로 들어옴.
contract('Lottery', function ([deployer, user1, user2]) {
let lottery;
let betAmount = 5 * 10 ** 15;
let bet_block_interval = 3;
beforeEach(async () => {
//console.log('Before each');
//테스트용 배포
lottery = await Lottery.new()
})
// it('Basic test', async () => {
// console.log('Basic test');
// let owner = await lottery.owner();
// //let value = await lottery.getSomeValue();
// console.log(`owner : ${owner}`);
// console.log(`value : ${value}`);
// assert.equal(value, 5);
// })
//it.only : 특정 케이스만 테스틑 할 때
it('getPot should return current pot', async () => {
let pot = await lottery.getPot();
// 처음에는 팟머니가 없는 상황이라 0
assert.equal(pot, 0);
})
describe.only('Bet', function () {
it('should fail when the bet money is not 0.005 ETH', async () => {
// Fail transaction
// assertRevert의 인자가 보내는 에러를 assertRevert가 받아서 try-catch 문으로 받는다.
// 에러이므로 catch 문으로 들어온다. 에러문에 revert 단어가 있는지 없는지 확인하고 revert 단어가 있다면, 에러를 제대로 확인 하였다는 의미.
await assertRevert(lottery.bet('0xab', { from: user1, value: 4000000000000000 })) // queue의 0번
// transaction object {chainId, value, to, from, gas(Limit), gasPrice}
// chainId : 블록체인마다, 네트워크 마다 다른 체인아이디
// value : 이더
// to : address
// from : 누가 보냇는지
});
it('should put the bet to the bet queue with 1 bet', async () => {
// bet
// 바이트 하나 보내고, 트랜잭션 오브젝트
let receipt = await lottery.bet('0xab', { from: user1, value: betAmount })
//console.log(receipt);
let pot = await lottery.getPot();
assert.equal(pot, 0)
// check contract balance === 0.005
// 트러플에서 web3가 주입되어 있음.
let contractBalance = await web3.eth.getBalance(lottery.address);
assert.equal(contractBalance, betAmount);
// check bet info
let currentBlockNumber = await web3.eth.getBlockNumber();
bet = await lottery.getBetInfo(0);
assert.equal(bet.answerBlockNumber, currentBlockNumber + bet_block_interval);
assert.equal(bet.bettor, user1);
assert.equal(bet.challenges, '0xab');
// check log
await expectEvent.inLogs(receipt.logs, "BET");
})
})
})
contracts/Lottery.sol 전체코드
pragma solidity >=0.4.22 <0.9.0;
contract Lottery {
struct BetInfo {
uint256 answerBlockNumber;
address payable bettor; //payable : 해당 주소로 트랜스퍼 하기 위해서 붙임.
bytes challenges; // 0Xab..
}
//tail이 증가하면서 값을 넣어줌
uint256 private _tail;
//값을 검증할 때는 _head 부터 차례대로 값을 가져와서 검증
uint256 private _head;
//자료구조 queue
mapping(uint256 => BetInfo) private _bets;
//주소를 오너로 설정
//public : 자동으로 getter를 만들어 준다, 스마트 컨트랙트 외부에서 바로 오너의 값을 알 수 있다.
address public owner;
// 블록해시로 확인할 수 있는 제한 256으로 고정
uint256 constant internal BLOCK_LIMIT = 256;
// +3 번째블럭으로 고정
uint256 constant internal BET_BLOCK_INTERVAL = 3;
// 배팅금액 0.005eth 고정
uint256 constant internal BET_AMOUNT = 5 * 10 ** 15;
//팟머니
uint256 private _pot;
//이벤트
event BET(uint256 index, address bettor, uint256 amount, bytes challenges, uint256 answerBlockNumber);
//스마트 컨트랙트가 생성될 때,, 배포가 될때, 가장 먼저 실행되는 함수.
constructor() public {
owner = msg.sender;
// 배포가 될 때, 보낸사람을 오너로 저장하겠다.
// msg.sender : 스마트 컨트랙트에서 사용하는 전역변수
}
// 테스트용 코드 주석처리
// function getSomeValue() public pure returns (uint256 value){
// return 5;
// }
// view : 스마트 컨트랙트에 있는 변수를 조회할 때
function getPot() public view returns (uint256 pot){
return _pot;
}
// /**
// * 베팅 함수
// *
// * @dev 베팅을 한다. 유저는 0.005 ETH를 보내야 하고, 베팅용 1 byte 글자를 보낸다.
// * 큐에 저장된 베팅 정보는 이후 distribute 함수에서 해결된다.
// * @param challenges 유저가 베팅하는 글자
// * @return 함수가 잘 수행되었는지 확인하는 bool 값
// *
// */
function bet(bytes memory challenges) public payable returns (bool result){
// Check the proper ether is sent
require(msg.value == BET_AMOUNT, "not enough ETH");
// Push bet to the queue
require(pushBet(challenges),"Fail to add a new Bet Info");
// Emit event
emit BET(_tail - 1, msg.sender, msg.value, challenges, block.number + BET_BLOCK_INTERVAL);
return true;
}
// Save the bet to the queue
// Distribute : 검증
// check the answer
//getter
function getBetInfo(uint256 index) public view returns (uint256 answerBlockNumber, address bettor, bytes memory challenges){
BetInfo memory b = _bets[index];
answerBlockNumber = b.answerBlockNumber;
bettor = b.bettor;
challenges = b.challenges;
}
//queue 이용하니 puch와 pop의 개념이 필요
function pushBet(bytes memory challenges) internal returns (bool) {
BetInfo memory b;
//베터는 보낸사람 , 버전업이 되어 전송하려면 payable 붙여줘야함.
b.bettor = payable(msg.sender); // 20 bytes
//block.number : 현재 트랜잭션에 들어가는 블럭의 수를 가져옴
b.answerBlockNumber = block.number + BET_BLOCK_INTERVAL; // 32 bytes 20000 gas
b.challenges = challenges; // byte // 20000gas 가정
_bets[_tail] = b;
_tail++; // 32 bytes 값 변화 // 20000gas 가정 -> 5000 gas
return true;
}
function popBet(uint256 index) internal returns (bool){
//매핑 이기때문에 리스트에서 삭제하기보다는 단순히 값을 초기화 하자.
//맵에 있는 값을 delete하게 되면, gas를 돌려받게 된다. 이것의 의미는 더이상 데이터를 블록체인 데이터에 저장하지 않겠다의 의미. state database에 저장된 값을 그냥 없앤다
delete _bets[index];
return true;
}
}