예제로 배우는 DAPP 개발해보기 - 복권편

이더리움 블록체인 위에 어플리케이션 형태로 올려서 개발할 수 있는 DAPP을 예제로 배워보겠다. 
처음 예제는 복권(Lottery) 이다. 

내용은 최근 세미나 한 자료를 기준으로 진행한다. 


예제 자료는 다음의 위치에서 볼 수 있다. 

https://github.com/bear2u/lottery_exam1


결과물은 오른쪽과 같다. 

지갑을 우선 설치를 해보자. 

https://metamask.io/ 에서 크롬 확장 프로그램인 설치를 하자. 

로그인을 비밀번호를 입력 후 하면 되는데 문자열이 모니크 키값으로 주어지는데 꼭 다른 곳에 저장을 해놓자. 

  

로그인시 보유한 이더와 서버주소를 확인 할 수 있다. 테스트 서버를 우선 설정해보자. 


왼쪽 하단 서버 선택을 Rinkeby Test Net 으로 지정하자. 

개발을 해서 테스트를 할려면 이더(코인)가 필요하다. 

무료 이더를 받을 수 있는 방법은 2가지가 있다. 

  • http://rinkeby-faucet.com : 지갑 계정을 복사를 해서 해당 사이트에 넣으면 0.001 이더를 보내준다. (테스트 서버에 한해)

  • https://faucet.rinkeby.io/ : 소셜을 통해서 약 18이더정도를 받을 수 있다. 사용 방법은 피드에 내 지갑 주소를 올려서 그 해당 링크를 넣으면 된다. 



일반적인 아키텍처와 비교해서 차이점은 이더리움 블록체인 서버에 연결하기 위해 web3 라이버러리를 이용할 수 있다.

현재 예제에서는 1.0.0-beta.26 을 이용한다. 이후 버전 이용시 오류가 다소 나오는 것 같다. '

대소문자를 조심해야 한다. 


오타 : Proof of Work (POW)




http://remix.ethereum.org/ 에서 온라인 컴파일 및 테스팅을 진행 할 수 있다. 


테스팅 환경을 선택을 가능하다. 

우선 로컬로 지정해서 시작한다. 

이 후엔 서버를 선택해서 진행가능하다. 



  • 실행환경 지정

  • 생성 함수를 어떤 식으로 지정 할 수 있는지 지정

  • 각각의 함수를 테스팅 할 수 있다. 





베팅하는 걸 알아보자. 


Account를 기본이나 다른 걸로 설정한다. JVM 설정시 기본 5개는 주어진다. 

1이더 이상 값을 설정한다.

그리고 하단에 Enter를 통해서 베팅한다.




















Account를 다른 걸로 선택해서 Enter를 해서 여러명을 입장한다. 


여러명을 베팅을 시켜서 돈이 다 나갔는지 확인해본다. 

현재 4명이 들어간 상태이다. 



유저가 제대로 들어왔는지 확인해본다. 총 4명인걸 확인한다.



그리고 매니저를(처음 만든 계정) 통해서 pickWinner 클릭한다. 

그럼 랜덤으로 이긴 사람에게 돈이 들어간걸 볼 수 있다. 102.xxxxx 이더로 들어갔다. 

콘솔창으로 오류 및 진행사항들을 확인 할 수 있다.

그리고 이 작성된 솔리디티 스크립트를 함수가 제대로 실행되는지 테스팅 해보자. mocha 로 진행한다.

 

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
const assert = require('assert');
const ganache = require('ganache-cli');
const Web3 = require('web3');
const provider = ganache.provider();
const web3 = new Web3(provider);
 
const { interface, bytecode } = require('../compile');
 
let lottery;
let accounts;
 
beforeEach(async () => {
  accounts = await web3.eth.getAccounts();
 
  lottery = await new web3.eth.Contract(JSON.parse(interface))
      .deploy({ data: bytecode })
      .send({ from: accounts[0], gas: '1000000'});
 
  lottery.setProvider(provider);
});
 
describe('Lottery Contract', () => {
  it('deploys a contract', () => {
    assert.ok(lottery.options.address);
  });
 
  it('allows one account to enter', async () => {
    await lottery.methods.enter().send({
      from: accounts[0],
      value: web3.utils.toWei('0.02','ether'//'2000000000000000'
    });
 
    const players = await lottery.methods.getPlayers().call({
      from: accounts[0]
    });
 
    //저장된 사람이 계정에 있는지 체크
    assert.equal(accounts[0], players[0]);
    //계정 사이즈 체크
    assert.equal(1, players.length);
  });
 
  it('allows multiple accounts to enter', async () => {
    await lottery.methods.enter().send({
      from: accounts[0],
      value: web3.utils.toWei('0.02','ether'//'2000000000000000'
    });
 
    await lottery.methods.enter().send({
      from: accounts[1],
      value: web3.utils.toWei('0.02','ether'//'2000000000000000'
    });
 
    await lottery.methods.enter().send({
      from: accounts[2],
      value: web3.utils.toWei('0.02','ether'//'2000000000000000'
    });
 
    const players = await lottery.methods.getPlayers().call({
      from: accounts[0]
    });
 
    //저장된 사람이 계정에 있는지 체크
    assert.equal(accounts[0], players[0]);
    assert.equal(accounts[1], players[1]);
    assert.equal(accounts[2], players[2]);
    //계정 사이즈 체크
    assert.equal(3, players.length);
  });
 
  it('requires a minimum amount of ether to enter', async () => {
    try{
      await lottery.methods.enter().send({
        from: accounts[0],
        value: 0
      });
      assert(false);
    } catch (err) {
      assert.ok(err);
    }
  });
 
  it('only manager can call pickWinner' , async () => {
    try{
      await lottery.methods.pickWinner().send({
        from: accounts[1]
      });
      assert(false);
    }catch(err) {
      assert(err);
    }
  });
 
  if('sends money to the winnder and resets the players array', async () => {
    await lottery.methods.enter().send({
      from: accounts[0],
      value: web3.utils.toWei('2''ether')
    });
 
    const initialBalance = await Web3.eth.getBalance(accounts[0]);
 
    await lottery.methods.pickWinner().send({ from: accounts[0] });
 
    const finalBalance = await web3.eth.getBalance(accounts[0]);
 
    const difference = finalBalance - initialBalance;
    console.log(finalBalance - initialBalance);
    assert(difference > web3.utils.toWei('1.8''ether'));
 
  });
});
 
cs


이제 컴파일 해보자. 

1
2
3
4
5
6
7
8
9
const path = require('path');
const fs = require('fs');
const solc = require('solc');
 
const lotteryPath = path.resolve(__dirname, 'contracts''Lottery.sol');
const source = fs.readFileSync(lotteryPath, 'utf8');
 
module.exports = solc.compile(source, 1).contracts[':Lottery'];
 
cs


그리고 배포를 해보자. 배포를 하면 시간이 다소 걸린다. 실제 서버로 올리기 때문이다. 

ABI 내용도 나오는 걸 볼 수 있다. 

1
2
3
4
5
6
7
8
node compile
node deploy
 
//결과
Attempting to deploy from account 0xBC9aAd0B1598a9388f3535272376006eeaF1eea2 // contract address
[{"constant":true,"inputs":[],"name":"manager","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"pickWinner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getPlayers","outputs":[{"name":"","type":"address[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"enter","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"players","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]
Contract deployed to 0x43eD67e834c9588bd8552BbC54Bc1d565C6C8Cf7
 
cs



그리고 잘 배포가 되었는지 확인해보자. 

https://rinkeby.etherscan.io/

에서 계약 주소를 넣어서 확인 할 수 있다.  물론 현재 서버는 RINKEBY 테스트 서버이다.  

이제 웹 클라이언트 화면을 만들 차례이다. 

현재 지갑을 가져온다. 

1
2
3
4
5
6
7
import Web3 from 'web3';
 
const web3 = new Web3(window.web3.currentProvider);
 
export default web3;
 
 
cs


여기에서 지갑 가져오는 위치를 다른 곳으로 바꿀 수도 있다. 

1
2
3
4
5
6
7
8
if (typeof web3 !== 'undefined') {
  web3 = new Web3(web3.currentProvider);
else {
  // set the provider you want from Web3.providers
  web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
}
 
 
cs


그리고 lottery.js 을 통해 현재 솔리디티로 작성된 내용을 가져온다. 계약 주소와 abi 내용을 입력하면 된다.

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import web3 from './web3';
 
const address = '0x6D02E4aD1E1870E96E41E5735A2ddb02165C213b';
 
const abi = [
  {
    "constant"true,
    "inputs": [
      
    ],
    "name""manager",
    "outputs": [
      {
        "name""",
        "type""address"
      }
    ],
    "payable"false,
    "stateMutability""view",
    "type""function"
  },
  {
    "constant"false,
    "inputs": [
      
    ],
    "name""pickWinner",
    "outputs": [
      
    ],
    "payable"false,
    "stateMutability""nonpayable",
    "type""function"
  },
  {
    "constant"true,
    "inputs": [
      
    ],
    "name""getPlayers",
    "outputs": [
      {
        "name""",
        "type""address[]"
      }
    ],
    "payable"false,
    "stateMutability""view",
    "type""function"
  },
  {
    "constant"false,
    "inputs": [
      
    ],
    "name""enter",
    "outputs": [
      
    ],
    "payable"true,
    "stateMutability""payable",
    "type""function"
  },
  {
    "constant"true,
    "inputs": [
      {
        "name""",
        "type""uint256"
      }
    ],
    "name""players",
    "outputs": [
      {
        "name""",
        "type""address"
      }
    ],
    "payable"false,
    "stateMutability""view",
    "type""function"
  },
  {
    "inputs": [
      
    ],
    "payable"false,
    "stateMutability""nonpayable",
    "type""constructor"
  }
];
 
 
 
export default new web3.eth.Contract(abi, address);
 
 
cs








+ Recent posts