Blockchain

#23 ganache 네트워크, truffle을 이용해 Counter 컨트랙트 만들기

Sila 2022. 7. 13. 16:25

오늘은 truffle, ganache를 이용해 숫자 카운터 컨트랙트를 만들고 배포해보자.

 

그리고 이를 리액트를 이용한 웹 페이지에서 메타마스크를 통해 접근할 수 있도록 하는

 

디앱화 해보자.

 

1. 컨트랙트 생성, 배포 (#22 참조)

 

루트 디렉토리에 truffle 폴더를 만들고, 여기서 컨트랙트를 작성, 컴파일, 배포한다.

 

npx truffle init

 

생성된 contracts 폴더에 다음과 같이 counter 컨트랙트를 만든다.

 

/*  truffle/contracts/Counter.sol  */

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

contract Counter {
    uint256 private _count;

    function current() public view returns (uint256) {
        return _count;
    }

    function increment() public {
        _count += 1;
    }

    function decrement() public {
        _count -= 1;
    }
}

 

이제 ganache 네트워크를 구동한다.

 

npx ganache-cli

 

ganache 네트워크가 구동되면 이를 메타마스크에 추가하고, 0번 계정을 불러온다.

 

이제 truffle에서 컨트랙트를 컴파일 하고 배포한다. ( #22 참조 )

 

/*  truffle/migrations/2_deploy_Counter.js  */

const Counter = artifacts.require("Counter");
// 파일의 이름이 아닌 컨트랙트의 이름을 가져와야 한다.

module.exports = function (deployer) {
  deployer.deploy(Counter);
};
truffle compile
truffle migration

 

이 후, truffle console로 들어가 컨트랙트를 호출한다.

 

truffle console

Counter.deployed().then(instance => it = instance)

 

이제 컨트랙트 내부의 함수를 실행해보면 된다.

 

it.current()
// 0
it.increment()

it.current()
// 1

 

잘 배포가 되었고, 문제 없이 동작한다면 이제 이를 리액트와 메타마스크를 통해 동작시켜보자.

 

2. 리액트를 이용해 ui 만들기

루트디렉토리에서 리액트 앱을 만들고 web3 라이브러리를 설치한다.

 

npm i web3
create-react-app front

 

2.1 메타마스크와 블록체인 네트워크 연결

커스텀 훅을 이용해 account와 web3의 상태를 app.jsx (app.js의 확장자를 바꾼다)에 전달해줄 것이다.

 

/*  front/src/hooks/useWeb3.jsx  */

import { useEffect, useState } from 'react'
import Web3 from 'web3/dist/web3.min'

const useWeb3 = () => {
    const [ account, setAccount ] = useState(null)
    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)
            // 메타마스크가 설치되어 있다면 메타마스크와
            // 메타마스크 안에 등록된 계정을 기반으로 web3, account 값 변경
        })();
    }, [])
    return [ web3, account ]
}

export default useWeb3;

 

이는 네트워크 상의 컨트랙트와는 그다지 관계가 없고, 메타마스크를 통해 네트워크와 연결해주기 위함이다.

 

2.2  컨트랙트 랜더링

이제 우리가 배포한 컨트랙트를 불러올 컴포넌트를 만들어준다.

 

이 때, 블록체인 네트워크에서 원하는 컨트랙트를 가져오려면 abi와 ca가 필요한데,

 

아까 truffle에서 compile시 생성된 json 파일안에 그 데이터가 저장되어 있다.

 

이 json파일을 가져오는데, Contracts라는 폴더를 src 폴더에 만들고, 그 안에 json 파일을 복사해 넣어준다.

 

/*  front/src/Contracts/Counter.json  */

// truffle/build/contracts/Counter.json을 복사 붙여넣기 한다.

 

이제 카운터 컴포넌트를 작성한다.

 

/*  front/src/components/Counter.jsx  */

import React, { useState, useEffect } from 'react';
import CounterContract from '../Contracts/Counter.json'

const Counter ({web3, account}) => {
    const [ count, setCount ] = useState(0)
    const [ deployed, setDeployed ] = useState()
    
    useEffect(() => {
        (async () => {
            if(deployed) return
            
            const Deployed = new Web3.eth.Contract(
                CounterContract.abi,
                "0xA87bB5c6174d6CE1517fF5664b735720CD62A691"
                // CA를 써주면 된다
            )
            // abi, CA를 통해 블록체인 네트워크 상에 존재하는 컨트랙트를
            // 특정해 가져온다.
            
            const count = await Deployed.methods.current().call()
            // 컨트랙트의 현재 숫자값을 가져온다.
            
            setCount(count)
            // 리액트의 상태를 컨트랙트의 현재 숫자로 바꿔준다
            setDeployed(Deployed)
            // deployed의 상태를 컨트랙트로 바꿔준다.
        })()
    },[])
    
    return (
        <div>
            <h2> Counter : { count } </h2>
            <button> + </button>
            <button> - </button>
        </div>
    )
}

export default Counter;

 

 

다음으로 App.js 파일을 App.jsx 파일로 확장자만 바꿔준 후, 컴포넌트와 훅의 값을 가져온다.

 

/*  front/src/App.jsx  */

import React from 'react'
import useWeb3 from './hooks/useWeb3'
import Counter from './components/Counter'

const App = () => {
    const [ web3, account ] = useWeb()
    
    if(!account) return 'metamask로 로그인 해주세요'
    
    return (
        <div>
            <span> Account : {account} </span>
            <Counter web3={web3} account={account}/>
        </div>
    )
}

export default App;

 

이제 Counter 컴포넌트에서 +, - 버튼을 클릭했을 때 각각 컨트랙트의 증가, 감소 함수를 발동하게끔 이벤트를 넣어준다.

 

/*  front/src/components/Counter.jsx  */

// ...중략

const Counter = ({web3, account}) => {
    // ...중략
    
    const increment = async () => {
    // 이 increment는 컴포넌트 내에서 선언한 함수이다.
        const result = await deployed.methods.increments().send({
        // 이 increment는 컨트랙트 내에 존재하는 함수를 호출하는 것이다.
            from : account
        })
        
        if( !result ) return
        
        const current = await deployed.methods.current().call()
        // 함수 호출 후, 바뀐 count값을 다시 가져온다
        setCount(current)
        // 컴포넌트의 count 변수의 상태를 바꿔준다.
    }
    
    const decrement = async () => {
        const result = await deployed.methods.decrement().send()({
            from : account
        })
        if(!result) return
        
        const current = await deployed.methods.current().call()
        setCount(current)
    }
    useEffect(( => {
        // ...중략
    }, [])
    
    return (
        <div>
            <h2> Counter : {count} </h2>
            <button onClick = {increment}>+</button>
            <button onClick = {decrement}>-</button>
        </div>
    )
}

export default Counter;

 

이렇게 하고 +, - 버튼을 클릭할 경우, 컨트랙트 내의 함수를 발동하기 위해 메타마스크에서 서명을 요구하는 창이 뜬다.

 

이를 승인하면 계정에서 eth가 조금 빠져나가는 동시에 컨트랙트 내부ㅠ의 count 값이 바뀌는 것을 확인할 수 있다.

 

그런데 이 경우 내가 업데이트한 컨트랙트의 여러 가지 데이터들을 다른 사람들은 실시간으로 업데이트 받지 못한다는

 

문제가 있다.

 

이런 문제를 보완하기 위해서는 웹소켓 통신의 도움을 받아야 하는데, 이는 다음 글에서 알아보자.