solidity>투표앱 만들기(기본 원리)

YU YU·2021년 10월 12일
0

경일_BlockChain

목록 보기
18/24

https://medium.com/haechi-audit-kr/smart-contract-a-to-z-79ebc04d6c86

1. 주의사항


코드는 짧으니까 무엇을 만들고 어떻게 돌아가는지 알아야한다.

2. 순서

3. solidity 코드 작성

Voting.sol이라는 파일을 만든다.

  • Voting.sol
pragma solidity ^0.8.0;

contract Voting{
    //후보자들 초기화
    string [] public candidateList;
    constructor(string[] memory _candidateNames) public {
        candidateList = _candidateNames;
    }
    //투표기능 만들기
    mapping(string => uint) public voteReceived;
    //uint의 기본값은 0;

    function vodeForCandiodate(string memory _candidate) public{
        voteReceived[_candidate] += 1;
    }

    //각 후보자들의 투표 갯수 알아보기
    //후보자명을 넣어주면 결과값으로 투표갯수를 리턴해주기
    function totalVotesFor(string memory _name) view public returns(uint){
        //여기서 filesystem을 사용할 수 있어서 storage를 사용하면 파일에 저장이 된다. 
        return voteReceived[_name];
    }

    //예외처리 (string 비교-kaccak암호화를 통해서 )
    function validCandidate(string memory _name) view public returns(bool){
        //완전탐색O(n);
        /*javascript code
            voteReceived.forEach(v=>{
                if(keccak256(v.key)===keccak256(_name)){
                    return true;
                }
            })
        return false;
        */
        //keccak256() string 안들어가고 byte로 들어간다. 
        //그래서 string 을 byte값으로 변환한다.
        //keccake256() 메소드 안에 byte값을 넣는다. 
        for(uint i = 0; i<candidateList.length; i++){
            if(keccak256(bytes(candidateList[i]))== keccak256(bytes(_name))) return true;
        }

        return false;
    }
}

4. 코드 컴파일

solcjs --abi --bin .\파일명
결과물 abi, bin


5. 스마트 컨트랙트 배포하기

  • deploy.js를 만든다.
  • web3 라이브러리 가져온다.
  • 내가 현재 사용하고 있는 블록체인 서버연결 = Ganache연결
  • 배포 작업 위해 Contract 매소드를 사용해서 block을 생성
  • 결과 값을 배포하기(deploy)
  • deploy.js
const Web3 = require('web3');

const web3= new Web3('http://localhost:8545');

const deployContract = new Web3.eth.Contract();

deployContract.deploy();

위의 내용이 전체적인 코드 내용이다. 이제 세부적인 항목을 작성해보도록 하겠다.

const Web3 = require('web3');
const fs = require('fs');

const ABI = JSON.parse(fs.readFileSync('./Voting_sol_Voting.abi').toString());
const BYTECODE=fs.readFileSync('./Voting_sol_Voting.bin').toString();

const web3= new Web3('http://localhost:8546');

const deployContract = new Web3.eth.Contract(ABI);

deployContract.deploy({
    //배포를 할 때 컴파일한 byte값을 넣음
    data:BYTECODE,
    arguments:[['ingoo1','ingoo2','ingoo3'].map(name=>web3.utils.asciiToHex(name))]
    //원래 string값을 못씀 16진수로 바꾸어야 함.
    //{}가 없으면 return 값을 생략해줄 수 있다!

})//배포를 할 때 생성자를 넣어주는 것임. sol안에 생성자를 넣어주느 것이 아니다. 
.send({//어느 주소에서 transaction을 발생시킬겨냐=>ganache의 하나의 주소값을 사용한다. 
    from:'0x7fB9085d9D321106fb9De9Dd95ef0A8776599beA',
    gas:6721975,
})//결과값이 프로미스로 떨어져서 then으로 받았다.
.then(newContract=>{
    console.log(newContract.options.address);
})
//원래 이런 작업들이 truffle 프레임워크를 이용해서 할 수 있다.
// 
/*
['ingoo1','ingoo2','ingoo3'].map(name=>{
    return web3.utils.asciiToHex(name);
})
*/

const deployContract = new Web3.eth.Contract(ABI); 블록을 생성할 때 컴파일한 abi의 내용을 컴파일에 넣는다.
deploy안에 있는 값은 byte값을 넣어준다.
그래서 arguments

> eth_getBlockByNumber
eth_gasPrice
eth_sendTransaction

  Transaction: 0x6b6d60b2a431fe6ac3a189f809569d5493331467228ad8b6f853ced778a71442
  Contract created: 0xf70931b2415b7cfce985aee2e422a5e167ef4a5b
  Gas usage: 636573
  Block Number: 1
  Block Time: Tue Oct 12 2021 11:40:29 GMT+0900 (대한민국 표준시)

eth_getTransactionReceipt
eth_getCode


0xf70931B2415B7cFce985AEE2e422A5E167Ef4A5B

node 에서 나오는 값을 넣어주면 된다.
ganache에 나오는 contract crated값을 넣어줘도 된다.

const Web3 = require('web3');
const fs = require('fs');

const ABI = JSON.parse(fs.readFileSync('./Voting_sol_Voting.abi').toString());
const BYTECODE=fs.readFileSync('./Voting_sol_Voting.bin').toString();

const web3= new Web3('http://127.0.0.1:8545');

const deployContract = new web3.eth.Contract(ABI);
//블록을 생성할 때 컴파일한 abi를 컴파일에 넣었다.

// deployContract.deploy({
//     //배포를 할 때 컴파일한 byte값을 넣음
//     data:BYTECODE,
//     arguments:[['ingoo1','ingoo2','ingoo3'].map(name=>web3.utils.asciiToHex(name))]
//     //원래 string값을 못씀 16진수로 바꾸어야 함.
//     //{}가 없으면 return 값을 생략해줄 수 있다!

// })//배포를 할 때 생성자를 넣어주는 것임. sol안에 생성자를 넣어주느 것이 아니다. 
// .send({//어느 주소에서 transaction을 발생시킬겨냐=>ganache의 하나의 주소값을 사용한다. 
//     from:'0x7fB9085d9D321106fb9De9Dd95ef0A8776599beA',
//     gas:'6721975',
// })//결과값이 프로미스로 떨어져서 then으로 받았다.
// .then(newContract=>{
//     console.log(newContract.options.address);
// })
//원래 이런 작업들이 truffle 프레임워크를 이용해서 할 수 있다.
// 
/*
['ingoo1','ingoo2','ingoo3'].map(name=>{
    return web3.utils.asciiToHex(name);
})
*/

//해당 블록주소에 접속해야 한다. 내 contract는 그 주소에 있으니까
const contract=new web3.eth.Contract(ABI, '0xf70931b2415b7cfce985aee2e422a5e167ef4a5b');

contract.methods.totalVotesFor('ingoo1').call().then(data=>{
    console.log(data);
})//call을 써야지만 promise객체로 실행한다. 

contract.methods.vodeForCandiodate('ingoo1').send({from:'0x7fB9085d9D321106fb9De9Dd95ef0A8776599beA'});\
//투표하는 메소드에 넣는  from 값은 처음 생성한 계정의 주소값을 넣어주면 된다. 
//투표하는 사람이다. 10개의 계정중에 아무거나 넣어도 상관이 없다.


실행할 때마다 하나씩 늘어남을 알 수 있다.

가나쉬에서도 계속 가스를 소모하고 있다.

데이터가 생성될 때마다 블럭생성이 된다고 보면 된다.

6. html페이지 만들기

https://github.com/ChainSafe/web3.js
에서 다음 값을 복사한다.
<script src="https://cdn.jsdelivr.net/npm/web3@latest/dist/web3.min.js"></script>

/*가나시 연결을 해야 한다. */

const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'));
//node.js가 아니고 javascript연결때 이렇게 사용한다. 
const ABI=JSON.parse(`[{"inputs":[{"internalType":"string[]","name":"_candidateNames","type":"string[]"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"candidateList","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_name","type":"string"}],"name":"totalVotesFor","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_name","type":"string"}],"name":"validCandidate","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_candidate","type":"string"}],"name":"vodeForCandiodate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"}],"name":"voteReceived","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]`);
const deployAddress=`0xf70931b2415b7cfce985aee2e422a5e167ef4a5b`;

let VotingContract = new web3.eth.Contract(ABI,deployAddress);

window.addEventListener('DOMContentLoaded',init);
async function init(){
    console.log('hello world!');
    await VotingContract.methods.vodeForCandiodate('ingoo1').send({from:'0x7fB9085d9D321106fb9De9Dd95ef0A8776599beA'});
    VotingContract.methods.totalVotesFor('ingoo1').call().then(data=>{
        console.log(data);
    })
}

Ganache를 끄고 다시 키면 투표 내용도 날아가고, 스마트 컨트랙트로 배포했던 내용들이 다 날라간다. 컴파일도 다시 해야한다. 코드도 다시 바꾸어야 한다. ganache는 파일에 저장하는것이 아니어서 다 날아간다. 배포할 주소값도 새로 생긴 주소값으로 넣어주어야 한다.
이부분!

  • server.js
/*가나시 연결을 해야 한다. */

const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'));
//node.js가 아니고 javascript연결때 이렇게 사용한다. 
const ABI=JSON.parse(`[{"inputs":[{"internalType":"string[]","name":"_candidateNames","type":"string[]"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"candidateList","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_name","type":"string"}],"name":"totalVotesFor","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_name","type":"string"}],"name":"validCandidate","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_candidate","type":"string"}],"name":"vodeForCandiodate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"}],"name":"voteReceived","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]`);
const deployAddress=`0xf70931b2415b7cfce985aee2e422a5e167ef4a5b`;

let candidates={"ingoo1":"candidate1","ingoo2":"candidate2","ingoo3":"candidate3"};
let VotingContract = new web3.eth.Contract(ABI,deployAddress);

window.addEventListener('DOMContentLoaded',init);
async function init(){
    let candidateNames = Object.keys(candidates);
    for(let i=0;i<candidateNames.length;i++){
        let name = candidateNames[i];//ingoo1
        candidates[name]//candidate1
        const nameElement = document.querySelector(`#${candidates[name]}`);
        nameElement.innerHTML=name;
        
        const countElement=document.querySelector(`#candidateCount${i+1}`);
        countElement.innerHTML=await VotingContract.methods.totalVotesFor(`ingoo${i+1}`).call();
    }
    // console.log('hello world!');
    // await VotingContract.methods.vodeForCandiodate('ingoo1').send({from:'0x7fB9085d9D321106fb9De9Dd95ef0A8776599beA'});
    // VotingContract.methods.totalVotesFor('ingoo1').call().then(data=>{
    //     console.log(data);
    // })
}

const btn = document.querySelector("#btn");
btn.addEventListener('click',btnEvent);

async function btnEvent(){
    let candidateName = document.querySelector(`#candidateName`).value;
    await VotingContract.methods. vodeForCandiodate(candidateName).send({from:'0x7fB9085d9D321106fb9De9Dd95ef0A8776599beA'})
}
<!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.0">
    <title>Document</title>
</head>
<body>
    <h1>투표 DApp</h1>
    <ul>
        <li><span id="candidate1"></span><span id="candidateCount1"></span></li>
        <li><span id="candidate2"></span><span id="candidateCount2"></span></li>
        <li><span id="candidate3"></span><span id="candidateCount3"></span></li>
    </ul>
    <input type = "text" id="candidateName"/>
    <button id="btn">투표하기</button>

    hello world!
    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/web3@latest/dist/web3.min.js"></script>
    <script type="text/javascript" src="./server.js"></script> 
</body>
</html>
/*가나시 연결을 해야 한다. */

const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'));
//node.js가 아니고 javascript연결때 이렇게 사용한다. 
const ABI=JSON.parse(`[{"inputs":[{"internalType":"string[]","name":"_candidateNames","type":"string[]"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"candidateList","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_name","type":"string"}],"name":"totalVotesFor","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_name","type":"string"}],"name":"validCandidate","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_candidate","type":"string"}],"name":"vodeForCandiodate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"}],"name":"voteReceived","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]`);
const deployAddress=`0xf70931b2415b7cfce985aee2e422a5e167ef4a5b`;

let candidates={"ingoo1":"candidate1","ingoo2":"candidate2","ingoo3":"candidate3"};
let VotingContract = new web3.eth.Contract(ABI,deployAddress);

window.addEventListener('DOMContentLoaded',init);
async function init(){
    let candidateNames = Object.keys(candidates);
    for(let i=0;i<candidateNames.length;i++){
        let name = candidateNames[i];//ingoo1
        candidates[name]//candidate1
        const nameElement = document.querySelector(`#${candidates[name]}`);
        nameElement.innerHTML=name;
        
        const countElement=document.querySelector(`#candidateCount${i+1}`);
        countElement.innerHTML=await VotingContract.methods.totalVotesFor(`ingoo${i+1}`).call();
    }
    // console.log('hello world!');
    // await VotingContract.methods.vodeForCandiodate('ingoo1').send({from:'0x7fB9085d9D321106fb9De9Dd95ef0A8776599beA'});
    // VotingContract.methods.totalVotesFor('ingoo1').call().then(data=>{
    //     console.log(data);
    // })
}

const btn = document.querySelector("#btn");
btn.addEventListener('click',btnEvent);

async function btnEvent(){
    let candidateName = document.querySelector(`#candidateName`).value;
    await VotingContract.methods. vodeForCandiodate(candidateName).send({from:'0x7fB9085d9D321106fb9De9Dd95ef0A8776599beA'})

    let candidateCount = await VotingContract.methods.totalVotesFor(candidateName).call();
    let number = candidateName.charAt(candidateName.length-1)
    let countElement = document.querySelector(`#candidateCount${number}`);
    countElement.innerHTML = candidateCount;


}


투표하기 버튼을 누를 때마다 트랜잭션이 일어남을 알 수 있다.

7. 메타마스크

7-1. 핫 월렛 vs 콜드 월렛

핫월렛: 온라인으로 활성화 된 지갑
콜드월렛: 오프라인으로 활성화된 지갑

7-2. 수탁형 지갑 vs 비수탁형 지갑

  • 수탁형 지갑
    거래소에 저장된 지갑
    즉, 제 3자에 의해서 보관되는 지갑

  • 비수탁형 지갑
    본인이 직접 관리하는 지갑

7-3. 지갑의 역할

주소를 보관하는 공간이다.

코인마다 지갑이 다르기에 여러개의 주소를 저장한다.
이러한 역할을 하는 것이 메타마스크이다. 주소를 보관하고 해당 주소 내용을 조회할 수도 있게 한다. 혹은 다른 주소로 코인을 보낼수도 있다.

profile
코딩 재밌어요!

1개의 댓글

comment-user-thumbnail
2022년 5월 30일

안녕하세요, 질문이 있어서 댓글 달게 되었습니다.
deployContract.deploy({
//배포를 할 때 컴파일한 byte값을 넣음
data:BYTECODE,

여기서 BYTECODE를 어떤 값을 넣어야 하는지 잘 모르겠습니다ㅠ
BYTECODE=fs.readFileSync('./Voting_sol_Voting.bin').toString(); 여기서 끝에 나온 0x~~~값을 넣으면 되는건가요?

답글 달기