Cute Bow Tie Hearts Blinking Pink Pointer

블록체인/스마트 컨트랙트

[ truffle, 스마트 컨트랙트 ] 솔리디티 event 사용해서 dApp 간단하게 구현해보기

청포도 에이드 2022. 7. 14. 16:48
728x90

 

 

목차

 

- 솔리디티 event

- dApp 구현

   - 디렉토리구조

   - 코드 구현

 

 

 

오늘은 솔리디티 event에 대해서 알아 보겠다.

 

event 라는 것은 블록체인 네트워크의  블록에 특정값을 기록하는 것을 말한다.

 

예를들어서, 송금하기 라는 함수가 있다고 가정하였을때, 송금하기 버튼을 누르면, 

누른 사람의 계좌와 금액이 이벤트로 출력이 되어서 블록체인 네트워크 안에 기록이 된다. 

 

이렇게 로그를 사용하여, 블록에 각인시키는것은  일반적으로 string 이나 다른 값들을 스마트컨트랙에 저장하는것보다 효율적이다.

 

 

이전 글(바로가기)에 이어서 코드를 조금 수정하는 방향으로

event를 사용해서 dApp을 구현하겠다.

 

 

디렉토리 구조

전체

 

back
front
truffle

 

이제 카운터만 되는... dApp 구현을 위한 코드를 작성해보겠다.

 

초기설정하기

 

터미널을 켜서 가나슈를 실행해주겠다.

 

npx ganache-cli

컴퓨터를 껐다 키면 가나슈가 초기화 되기 때문에, 테스트 account 들도 초기화된다.

따라서 메타마스크 계정 추가역시 새로 해주어야한다.

 

 

고유 네트워크 id가 변하기 때문에 트러플 마이그레이션도 새로 해주어야한다.(새 터미널)

truffle migration --reset

 

오늘은 .sol파일을 수정해줄 것이기 때문에, migration과 파일 수정은

코드를 다 적은 뒤 해주면 된다.

 

front에서 build/contracts 안에 있는 json 을 복사해다가 썼는데,

이것 또한 다시 파일을 교환해주어야한다...

 

이 이유들 때문에 front 서버 안에 truffle 파일을 넣는 개발자들도 있다고 한다.

 

 

맥북인 사람들은 위 과정들을 가볍게 스킵해주면 된다.(컴퓨터를 끌 일이 없으니...)

 

Truffle

 

truffle/contracts/Counter.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

contract Counter{
    uint256 private _count;
    event Count(uint256 count);

    function current() public view returns(uint256){
        return _count;
    } // private이기때문에 getter를 따로 만든거다.

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

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

 

솔리디티 event를 사용해주는 법은 위처럼 Event [이벤트명](타입 이름)

event Count(uint256 count);

 

emit을 사용해 log를 출력하는 법

 

emit 이벤트이름 (이벤트 파라메터 값)

emit Count(_count);

 

이런 식으로 사용한다.

 

Front

 

모듈 먼저 설치해주자.

 

npm install web3 axios

 

 

App.jsx
import React from 'react';
import useWeb3 from './hooks/useWeb3';
import Counter from './components/Counter';

const App = () => {
    const [web3, account] = useWeb3(); //[null,null]
    console.log('hello world App');
    if (!account) return <div>메타마스크 연결하고 와라!</div>;
    return (
        <div>
            <span> Account : {account}</span>
            <div>Counter 나올 영역</div>
            <Counter web3={web3} account={account} />
        </div>
    );
};

export default App;

여기까진 이전 글과 동일하다!

 

 

hooks/useWeb3.jsx
import React, { 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 () => {
            // console.log('Hello world useWeb3!');
            // 너 실행하는 브라우저에 메타마스크가 있니?

            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;

 

아래에서 event로 찍은 log를 가져온다.

components/Counter.jsx
import React, { useEffect, useState } from 'react';
import CounterContract from '../contracts/Counter.json';
import axios from 'axios';

// props.web3 props.account
const Counter = ({ web3, account }) => {
    const [count, setCount] = useState(0);
    const [deployed, setDeployed] = useState();

    const increment = async () => {
        // await deployed.methods.increment().send({
        //     from: account,
        // });

        const response = await axios.post('http://localhost:3005/api/increment', {
            from: account,
        });
        console.log(response.data);

        //{nonce: 43, from: '0x701bf7ee4010bd793af343f0fe470b9f5efb81af', to: '0x0A99c73C760D543202030Caaa3617EC8b5a4bD68', data: '0xd09de08a'}
        // data: "0xd09de08a" <- input값 (스마트컨트랙트할 때만 존재)
        // from: "0x701bf7ee4010bd793af343f0fe470b9f5efb81af"
        // gasPrice: "0x4a817c800"
        // nonce: "0x2b"
        // to: "0x0a99c73c760d543202030caaa3617ec8b5a4bd68"

        await web3.eth.sendTransaction(response.data);
    };

    const decrement = async () => {
        await deployed.methods.decrement().send({
            from: account, //배포된(truffle로) 메소드 중 decrement()함수를 사용하겠다(account정보를 실어서).
        });
    };

    // Truffle 기준으로는 deployed로 CA정보....같은거 받아옴
    //new web3.eth.Contract() << 이더 객체에 abi. adress(ca) 이런거 나오겠지

    useEffect(() => {
        (async () => {
            if (deployed) return;
            // networkId 가져올 수 있다.
            const networkId = await web3.eth.net.getId();
            const ca = CounterContract.networks[networkId].address;
            const abi = CounterContract.abi;

            const Deployed = new web3.eth.Contract(abi, ca);
            //얜 백에서 관리하는게 효율적
            //2가지의 인자값을 받을 수 있음 1.ABI 2. CA
            const count = await Deployed.methods.current().call();

            web3.eth.subscribe('logs', { address: ca }).on('data', (log) => {
                console.log(log.data); //가장 최신의 count숫자 log
                const params = [{ type: 'uint256', name: '_count' }]; //name은 사용자 지정
                const value = web3.eth.abi.decodeLog(params, log.data); //Object
                console.log(value);
                //u {0: '12', __length__: 1, _count: '12'}
                setCount(value._count);
            });
            console.log(count);
            setCount(count);
            setDeployed(Deployed);
        })();
    }, []);
    return (
        <div>
            <h2>Counter{count}</h2>
            <button onClick={increment}>증가</button>
            <button onClick={decrement}>감소</button>
        </div>
    );
};

export default Counter;

metworkId를 굳이 변수로 설정하고, 매번 가져오는 이유는 우리가 가나슈로 테스트넷을 사용할 때,

컴퓨터를 재부팅할 때마다 가나슈가 초기화 하기 때문에 매번 networkId를 복사 붙여넣기 하는 것에 번거로움이 있기 때문이다. (디렉토리 복붙만으로도 귀찮은 건 충분하다....)

 

Back

 

역시 모듈 설치 먼저 해주자.

 

npm install express cors web3 nodemon

 

server.js
const express = require('express');
const app = express();
const cors = require('cors');
const Web3 = require('web3');
const web3 = new Web3(new Web3.providers.HttpProvider('http://127.0.0.1:8545')); //가나슈 서버
const CounterContract = require('./contracts/Counter.json');

app.use(
    cors({
        origin: true,
        Credentials: true,
    })
);
app.use(express.json());

app.post('/api/increment', async (req, res) => {
    const { from } = req.body; // 0x701bf7EE4010bD793aF343f0fE470b9F5Efb81aF 메타마스크 계정복붙
	//가나슈에서 private 키 가장 첫번째 것이다.
    const nonce = await web3.eth.getTransactionCount(from);
    const networkId = await web3.eth.net.getId();
    const ca = CounterContract.networks[networkId].address;

    // abi
    const abi = CounterContract.abi;
    const deployed = new web3.eth.Contract(abi, ca);
    const data = await deployed.methods.increment().encodeABI();
    let txObject = {
        nonce,
        from,
        to: ca,
        data,
        //스마트 컨트랙트 함수에 대한 내용을 적는거임.
    };

    res.json(txObject);
});

app.listen(3005, () => {
    console.log('server start');
});

 

 

postman으로 data에 from:CA값을 담아 보내면,

아래에 결과가 뜬다.

필자는 increment만 백/프론트를 나누었다. decrement도 똑같은 방법으로 분리 가능하다.

728x90