즐코

스마트 컨트랙트로 초간단 투표 dApp 만들기 본문

BlockChain

스마트 컨트랙트로 초간단 투표 dApp 만들기

YJLEE_KR 2022. 7. 20. 18:09

지금까지 배운 솔리디티 개념을 바탕으로 매우 간단한 투표 dApp을 아래의 흐름대로 만들 수 있다.

이번에도 간단하게 truffle을 이용하여 스마트 컨트랙트를 작성하고 ganache 테스트 네트워크 상에 배포 및 컨트랙트 실행도 해보려고 한다. (물론 테스트로만) 

 

크게 아래의 흐름으로 솔리디티 코드를 짤 것이다.

 

0. 상태변수 선언 : 후보자 리스트, 후보자별 기록된 득표수  

1. 후보자 초기화 - 스마트 컨트랙트 배포 시 후보자들 등록하기

2. 후보자에 대한 투표 기능

3. 후보자에 대한 득표수 확인 기능

4. 테스트 코드 작성

 

0. 상태변수 선언

후보자 리스트 (candidate) 및 후보자별 기록된 득표수 (votesRcvd) 를 상태변수값으로 가지게 만든다.

두 변수 다 public 접근제한자로서 getter 함수가 자동으로 만들어지게끔 해준다. 

 

1. 후보자 초기화 

저번에도 공부했듯이 스마트 컨트랙트에 대한 인스턴스는 딱 한번 생성되는데 그건 바로 스마트 컨트랙트를 배포할 때이다.

따라서, 배포 시점에 정해진 후보자들을 인자값으로 넣기 위해 생성자 함수인 constructor() 를 사용하여 후보자리스트를 받아오자..

스마트 컨트랙트를 배포하는 시점에 후보자들을 등록해주는 것이다.

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.15;

contract Voting {
    string[] public candidateList;
    mapping(string => uint8) public votesRcvd;
    
    // 컨트랙트 배포 시점에 후보자군을 넣어준다
    construct(string[] memory candidateName){
    	candidataList = candidateName;
    }
    
}

 

migration 파일 작성 시 후보자군을 인자로 넣어준다.

// migration/2_deploy_Voting.js

const Voting = artifacts.require("Voting");

module.exports = function (deployer){
    deployer.deploy(Voting, ["피카츄", "파이리", "꼬부기", "푸린"]);
}

 

2. 후보자에 대한 투표 기능

 

우선, 투표 시 무효표 방지를 위해, 투표할 때 받는 후보자명이 배포 시 등록했던 후보자 명단에 꼭 있어야 한다. 

물론 투표 실행 함수에서 이를 걸러줘도 되지만 검증을 위한 목적으로만 함수를 따로 만들어주면 재사용성도 올라가고 하나의 함수가 하나의 기능만을 하기 때문에 더 좋을 듯 하다. 검증용 함수인 validCandidate는 굳이 public으로 할 필요가 없으므로 private으로 만들어준다. 

function validCandidate(string memory candidate) private view returns (bool) {
    for(uint i=0; i < candidateList.length; i++){
    	if(keccak256(abi.encodePacked(candidateList[i])) == keccak256(abi.encodePacked(candidate))){
            return true;
        }
    }
    return false;
}

function voteForCandidate(string memory candidate) public {
    require(validCandidate(candidate), "ERROR! - Invalid candidate");
    votesRcvd[candidate] += 1;
}

 

3. 후보자에 대한 득표수 확인 기능

여기서도 인자로 후보자 이름을 받으므로 이 후보자가 후보자 명단에 있는 후보인지 확인을 거치기 위해 validCandidate 함수를 사용해준다. 득표수는 간단히 상태변수 mapping으로 확인 가능하다.

function totalVotes(string memory candidate) public view returns (uint8) {
    require(validCandidate(candidate), "ERROR! - Invalid candidate");
    return votesRcvd[candidate];
}

 

4. 테스트 코드 작성

이제 테스트 코드를 작성해서 잘 돌아가는 컨트랙트인지 확인해보면 된다.

 

후보자 리스트를 가져올 때 deployed.candidateList.call(0), deployed.candidateList.call(1)... 으로 배열 인덱스 하나하나를 불러와서 list 라는 변수에 담아주었는데, 이는 이더리움 네트워크에서는 배열 전체를 한꺼번에 가져오는 게 불가능해서라고 한다. 따라서 배열을 가져오려면 상태변수 배열의 요소 하나 하나에 대한 요청 (call) 을 보내서 값을 가져오는 방식으로 전체를 가져오는 수밖에 없다.

// test/Voting.test.js

const Voting = artifacts.require("Voting");

// describe.only면 truffle test 시 해당 테스트들만 돌아간다!
describe.only("Voting", () => {
    let deployed, list;
    
    it("deployed", async () => {
    	deployed = await Voting.deployed();
    });
    
    it("candidateList", async () => {
    // const candidate1 = await deployed.candidateList.call(0);
    // const candidate2 = await deployed.candidateList.call(1);
    // const candidate3 = await deployed.candidateList.call(2);
    // const candidate4 = await deployed.candidateList.call(3);
    // 각 1초씩이라고 치면 총 4초가 걸림 => Promise.all 을 사용하자!
       const request = [
            deployed.candidateList.call(0), 
            deployed.candidateList.call(1),
            deployed.candidateList.call(2),
            deployed.candidateList.call(3),
       ]; 
     list = await Promise.all(request);
     console.log("후보자 명단", list);
    });
    
    it("voteForCandidate", async () => {
     // 투표하고
        await Promise.call([
           deployed.voteForCandidate(list[0]),
           deployed.voteForCandidate(list[1]),
           deployed.voteForCandidate(list[2]),
           deployed.voteForCandidate(list[2]),
           deployed.voteForCandidate(list[3]),
           deployed.voteForCandidate(list[3]),
           deployed.voteForCandidate(list[3]),
        ])
	 // 득표수 확인
    for (const candidate of list) {
    	let count = await deployed.totalVotes.call(candidate);
        console.log(`${candidate}:${count}`);
    }
    });
});

 

테스트 결과는 아래와 같다! 푸린 3표로 승.

 

Comments