// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
contract Voting {
// 후보자 담을 배열 선언
string[] public candidateList;
// 후보자 투표기능을 할 mapping 추가
mapping(string => uint8) public votesReceived;
/*
예시
{
"후보자" : 득표수
}
*/
// 컨트랙트를 배포될 때 후보자 담을 배열에 후보자를 추가하도록 합니다.
constructor(string[] memory candiatenames){
candidateList = candiatenames;
}
}
Voting
이란Contract
를 생성한 뒤 후보자를 담을 배열인candidateList
와 각 후보자의 득표수를 담당할votesReceived
라는 상태변수를 만들어주었습니다.- 해당
Contract
가 처음 배포 될 때 인자값으로 string으로 후보자 배열을 받아 후보자를 담을 배열인candidateList
에 할당하도록 하였습니다.
// 후보자에 대한 투표기능 추가
function voteForcandidate(string memory candidate) public {
// 후보군에 투표하고자 하는 후보자가 있을경우에만 투표실행
require(validCandidate(candidate),"There are no matching candidates.");
votesReceived[candidate]++;
}
// 후보자 검증 기능 추가
function validCandidate(string memory _candidate)private view returns(bool){
// 1. 후보자 리스트 가져오기 candidateList
// 2. 입력한 후보자와 candidateList안에 후보자가 일치하는것이 있는가 확인
for(uint8 i=0; i < candidateList.length; i++){
// string 비교가 안됨;;
// keccak256 가지고 16진수 내용으로 변경후 비교 합니다.
if(keccak256(abi.encodePacked(candidateList[i])) == keccak256(abi.encodePacked(_candidate))){
return true;
}
}
return false;
}
- 작성한 Contract에
voteForcandidate
함수를 추가하여 인자값으로 후보자의 명(candidate
)을 받습니다.- 인자값으로 받은
candidate
가 처음Contract
를 배포할 때 생성한 후보자 리스트(candidateList
)에 포함되어 있는지 검증하는validCandidate
함수를 추가하여 인자값으로 해당 후보자 명을 받은뒤 후보자 리스트를 반복문을 사용하여 인자값으로 받은 후보자명과 후보자 리스트안에 일치하는 후보자가 있는지 검증한뒤 있다면true
, 없다면false
를return
해주었습니다.require()
메서드 안에 후보자를 검증하는validCandidate
함수를 넣어 후보자가 없다면false
를return
받아 즉시 함수가 종료되도록 해주었고 후보자 리스트에 후보자가 있다면 해당 후보자명이란key
값에value
를++
하여 1이 증가하도록 처리하였습니다.
// 후보자의 총 득표수 확인기능 추가
function totalVotesFor(string memory _candidate) public view returns(uint8){
// 후보자의 득표수를 가져올 때 후보군에 후보자가 있을경우에만 투표실행
require(validCandidate(_candidate),"There are no matching candidates.");
// 배열안에 해당 후보자의 득표수를 return
return votesReceived[_candidate];
}
- 마지막으로 후보자의 총 득표수를 확인하기 위해 후보자의 명(
_candidate
)을 인자값으로 받은 뒤 해당 후보자가 후보자리스트에 있는지 검증을 한번하고 해당 후보자명이란key
값의value
를return
하여 해당 후보자가 몇표를 획득했는지를 처리했습니다.
- 그동안
truffle
과ganache
는 세팅을 많이 했으니 생략하도록 하겠습니다.truffle
을 사용하여 작성한Contract
를 배포한 뒤 작업하였습니다.
- npm install web3
import { useEffect, useState } from 'react';
import Web3 from 'web3/dist/web3.min';
const useWeb3 = () => {
const [account, setAccount] = useState('');
const [web3, setWeb3] = useState(null);
useEffect(() => {
(async () => {
if (!window.ethereum) return;
const address = await window.ethereum.request({
method: 'eth_requestAccounts',
});
setAccount(address);
const web3 = new Web3(window.ethereum);
setWeb3(web3);
})();
}, []);
return [web3, account];
};
export default useWeb3;
- 지난번
Counter
dApp
을 만들때와 마찬가지로react
의useEffect
를 활용하여MetaMask
에서 제공한window.etherum
이 있다면request()
메서드를 사용하여 연동된 지갑 주소를 가져오고setAccount
로account
의 상태를 변경해주었습니다.- 마지막으로
web3
와MetaMask
를 연결하는 새로운Web3
인스턴스를 생성하고 마찬가지로setWeb3
를 사용하여web3
의 상태를 변경해주었습니다.
const Voting = () => {
return <div>Hello Voting</div>
}
export default Voting;
- 간단하게 투표 컴포넌트를 생성해주었습니다.
import useWeb3 from './hooks/useWeb3';
import Voting from './components/Voting';
function App() {
const [web3, account] = useWeb3();
if (!account) return <>메타마스크 연결이 필요합니다.</>;
return <Voting web3={web3} account={account} />;
}
export default App;
- 미리 만들어둔
Custom Hook
useWeb3
를 사용하여MetaMask
와 연결된web3
와account
를Voting
컴포넌트에props
로 전달해줍니다.
import { useEffect, useState } from 'react';
import VotingContract from '../contracts/Voting';
const Voting = ({ web3, account }) => {
const [deployed, setDeployed] = useState();
const [candiList, setCandiList] = useState();
const [votesList, setVotesList] = useState();
useEffect(() => {
(async () => {
if (deployed) return;
const networkId = await web3.eth.net.getId();
const ContractAddress = VotingContract.networks[networkId].address;
const deploy = await new web3.eth.Contract(VotingContract.abi, ContractAddress);
const candiData = [deploy.methods.candidateList(0).call(), deploy.methods.candidateList(1).call(), deploy.methods.candidateList(2).call(), deploy.methods.candidateList(3).call()];
const candiList = await Promise.all(candiData);
const votesData = [deploy.methods.totalVotesFor(candiList[0]).call(), deploy.methods.totalVotesFor(candiList[1]).call(), deploy.methods.totalVotesFor(candiList[2]).call(), deploy.methods.totalVotesFor(candiList[3]).call()];
const votesCount = await Promise.all(votesData);
setCandiList(candiList);
setVotesList(votesCount);
setDeployed(deploy);
})();
});
const vote = async (_index) => {
await deployed.methods.voteForcandidate(candiList[_index]).send({ from: account[0] });
const votesData = [deployed.methods.totalVotesFor(candiList[0]).call(), deployed.methods.totalVotesFor(candiList[1]).call(), deployed.methods.totalVotesFor(candiList[2]).call(), deployed.methods.totalVotesFor(candiList[3]).call()];
const votesCount = await Promise.all(votesData);
setVotesList(votesCount);
};
const showList = () => {
return candiList.map((v, k) => {
return (
<div key={k}>
<ul>
<li>
{`${v} : ${votesList[k]} `}
<button
onClick={() => {
vote(k);
}}
>
투표
</button>
</li>
</ul>
</div>
);
});
};
return <>{candiList ? showList() : null}</>;
};
export default Voting;
- 먼저 배포된
Voting Contract
의json
파일을 가져옵니다.useState
를 사용하여 작성한Contract
를 실행할deployed
, 후보자 리스트를 담을candiList
, 후보자의 득표수를 담을votesList
이렇게 세가지 상태를 생성해주었습니다.useEffct
를 사용하여props
로 전달받은 인자값web3
를 사용하여networkId
를 가져옵니다.- 가져온
networkId
를 가지고 배포한Voting.json
파일의 가져온networkId
를 사용하여 연결된network
의contract address
값을 가져옵니다.contract address
값을 가지고 배포한Contract
를 실행할 수 있도록Contract
메서드의 인자값에Voting.json
파일에 있는abi
와CA
값을 넣은 실행값을deploy
에 할당합니다.deploy
안에 있는 배포한Contract
의methods
인candidateList
함수를 사용하여 후보자 리스트를 가져올 것입니다.ethereum network
에서 배열안에 있는 리스트들은 한번에 뽑아오면 지금은 괜찮지만 데이터가 많을 경우ethereum network
에 과부하가 올 수 있어 조금 불편하지만 모든 메서드들을promise
객체로 떨어지게 만들고Promise.all
메서드를 사용하여 전부 백그라운드에 넣어놓고 완료되는 순서대로 테스트 큐에 쌓이면서 콜스택으로 넘어오게끔 처리하였습니다.- 가져온 후보자 리스트 (
candiList
)를setCandiList
메서드를 사용하여candiList
의 상태를 변경해주었고 방금 했던것과 같은 방식으로 후보자의 득표수도 가져온 뒤 상태를 변경해주었습니다.- 투표 버튼을 눌렀을때
onClick
메서드를 활용하여vote
함수를 실행시키는데 인자값으로k
값을 주어index
를 가져오도록 하였습니다.vote
함수는 인자값으로 받은index
를 활용하여 후보자 리스트에서index
값이 일치하는 후보자명을 가져오고 후보자명을 인자값으로 넣어Contract
에서 작성한voteForcandidate
함수를 실행시켜 해당 후보자의value
값을1
증가 시켜 득표를 처리했습니다.- 마지막으로 투표를 했다면 다시 후보자들의 총 득표수를 가져와 상태를 변경해주었습니다.
투표전
투표후