로메오의 블로그

[DApp] 투표하기 화면 구현 본문

Backend/Python & Blockchain

[DApp] 투표하기 화면 구현

romeoh 2019. 7. 7. 20:20
반응형

우리는 위와 같은 화면을 구현합니다.

[DAPP] SOLIDITY FRONT-END ELECTION에서 후보자 읽어오기

위 포스트에 이어서 투표하기 코드를 추가하겠습니다.

 

먼저 Election.sol 에서 constructor에서 3명의 후보자를 추가하겠습니다.

/contracts/Election.sol

pragma solidity ^0.5.0;

contract Election {
    
    ....

    // constructor
    constructor() public {
        addCandidate('romeoh');
        addCandidate('doraemong');
        addCandidate('pororo');
    }

    ....
}

constructor에 위와 같이 코드를 추가합니다.

전체 코드는 아래와 같습니다.

pragma solidity ^0.5.0;

contract Election {
    
    // 후보자 모델
    struct Candidate {
        uint id;
        string name;
        uint voteCount;
    }

    // 후보자 기호 변수
    uint public candidatesCount;

    // 후보자 반환하기
    mapping(uint => Candidate) public candidates;

    // 투표에 참여한 ID 기록
    mapping(address => bool) public voters;
 
    // 후보자 등록하기
    function addCandidate (string memory _name) public {
        candidatesCount++;
        candidates[candidatesCount] = Candidate(candidatesCount, _name, 0);
    }

    // constructor
    constructor() public {
        addCandidate('romeoh');
        addCandidate('doraemong');
        addCandidate('pororo');
    }

    // 투표하기
    function vote(uint _candidateId) public {
        
        // 중복투표를 하면 오류를 발생 시킨다.
        require(!voters[msg.sender]);

        // 목록에 없는 후보자에게 투표하면 오류를 발생시킨다.
        require(_candidateId > 0 && _candidateId <= candidatesCount);

        // 투표에 참여한 ID를 기록해서 두번 투표하지 못하도록 한다.
        voters[msg.sender] = true;

        // 득표수를 +1 한다.
        candidates[_candidateId].voteCount ++;
    }
}

Build 하기

$ truffle migrate --reset

/src/index.html

index.html 파일에 후보자를 선택하고, 투표하기 버튼을 추가합니다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>투표 결과</title>
</head>

<body>
    <h1>투표 결과</h1>

    <!-- 로딩바 -->
    <div id="loader">
        <p>로딩중...</p>
    </div>

    <div id="content" style="display: none;">

        <!-- 투표결과 표시 -->
        <table class="table" style="width: 100%">
            <colgroup>
                <col width="60">
                <col width="*">
                <col width="100">
            </colgroup>
            <thead>
                <tr>
                    <th scope="col">기호</th>
                    <th scope="col">이름</th>
                    <th scope="col">득표수</th>
                </tr>
            </thead>
            <tbody id="candidatesResults">
            </tbody>
        </table>
        <hr>

        <!-- 투표하기 -->
        <h2>투표 참여</h2>
        <select id="candidateSelect">
            <option value="">후보작를 선택하세요.</option>
        </select>
        <button id="btnVote">투표하기</button>
        <hr>

        <!-- 계정 정보 표시 -->
        <p id="accountAddress"></p>
    </div>

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
    <script src="js/web3.min.js"></script>
    <script src="js/truffle-contract.js"></script>
    <script src="js/app.js"></script>
</body>

</html>

 

/src/js/app.js

var App = {
    web3Provider: null,
    contracts: {}
}

$(window).load(function () {

    // web3Provider 생성하기
    if (typeof web3 !== 'undefined') {
        // MetaMask가 설치되어 있어서 web3 인스턴스가 이미 생성되어 있음
        App.web3Provider = web3.currentProvider;
        web3 = new Web3(web3.currentProvider);
    } else {
        // MetaMask가 설치되지 않았을 경우 기본 인스턴스를 지정함
        App.web3Provider = new Web3.providers.HttpProvider('http://localhost:7545');
        web3 = new Web3(App.web3Provider);
    }

    // Election.json을 가지고 온다.
    $.getJSON('Election.json', function (election) {
        // Truffle 계약을 초기화 함
        App.contracts.Election = TruffleContract(election);
        // 인스턴스에 접속함
        App.contracts.Election.setProvider(App.web3Provider);

        render();
    });

    // 화면구현
    function render() {

        // 계정 정보 읽어오기
        web3.eth.getCoinbase(function (err, account) {
            if (err === null) {
                App.account = account;
                $('#accountAddress').html('나의 계정: ' + account);
            }
        });

        // 계약 정보를 읽어온다.
        App.contracts.Election.deployed().then(function (instance) {
            electionInstance = instance;
            return electionInstance.candidatesCount();
        }).then(function (candidatesCount) {
            for (var i = 1; i <= candidatesCount; i++) {
                electionInstance.candidates(i).then(function (candidate) {
                    var id = candidate[0];
                    var name = candidate[1];
                    var voteCount = candidate[2];

                    // 투표결과 html 파싱
                    var candidateTemplate = '<tr><th>' + id + '</th><td>' + name + '</td><td>' + voteCount + '</td></tr>'
                    $('#candidatesResults').append(candidateTemplate);

                    // 후보자 목록 표시
                    var candidateOption = '<option value="' + id + '">' + name + '</option>'
                    $('#candidateSelect').append(candidateOption);
                });
            }

            // 후보자 화면 표시
            $('#loader').hide();
            $('#content').show();
        }).catch(function (error) {
            console.warn(error);
        });
    }

    // 투표하기
    $('#btnVote').on('click', function() {
        var candidateId = $('#candidateSelect').val()
        if (!candidateId) {
            return alert('후보자를 선택하세요.')
        }
        App.contracts.Election.deployed()
            .then(function(instance) {
                return instance.vote(candidateId, {from: App.account})
            })
            .then(function(result) {
                if (result.receipt) {
                    alert('성공적으로 투표했습니다.')
                    location.reload();
                }
            })
            .catch(function(error) {
                alert(error.message)
            })
    })
});

render() 함수에 후보자 목록 표시 부분에 <select> 부분에 후보자 목록을 추가합니다.

그리고 투표하기 버튼 이벤트를 추가합니다.

 

lite server 구동

새로운 터미널을 열어서 npm 으로 lite server를 구동합니다.

$ npm run dev

 

투표하기에 후보자 목록이 표시됩니다.

투표하기

후보자를 선택하고 투표하기 버튼을 누릅니다.

MetaMask에서 결제 승인창이 뜹니다.

Gas 비용도 표시됩니다.

승인을 눌러서 투표합니다.

 

투표가 성공적으로 이루어졌습니다.

 

다시 투표를 해봅니다.

이번에는 MetaMask에서 투표 실패가 표시됩니다.

100 ETH에서 가스비용이 차감된 것도 알 수 있습니다.

화면에서도 오류가 표시됩니다.

 

계정 변경

MetaMask 계정을 변경해서 다시 투표해보겠습니다.

Ganache에서 index 5번 계정의 연쇄 아이콘을 누릅니다.

Private key를 복사합니다.

MetaMask에서 계정 가져오기를 선택합니다.

복사했던 private key를 개인키에 입력하고 가져오기 버튼을 누릅니다.

브라우저 새로고침 하면 계정이 변경됩니다.

투표하면 정상적으로 투표가 이루어 집니다.

반응형
Comments