즐코

메타마스크 통해서 스마트 컨트랙트 실행해보기 본문

BlockChain

메타마스크 통해서 스마트 컨트랙트 실행해보기

YJLEE_KR 2022. 7. 13. 16:17

Counter 라는 아주 단순한 컨트랙트를 만들어서 클라이언트가 브라우저 상에서 숫자를 증가/감소 시킬때마다 (즉, 네트워크 내 상태변수를 변경시킬때마다) 메타마스크를 통해 가나쉬 테스트 네트워크에 배포된 컨트랙트를 실행시키는 걸 해보려고 한다. 상태를 변경하는 것도 하나의 트랜잭션이므로, 이 트랜잭션 발생 시 개인키로 서명을 해줘야하는데 그 역할을 메타마스크로 대체해준 것이다. 

 

1/ truffle 이용하여 컨트랙트 배포 및 실행 테스트

 

- truffle 구조 생성

$ truffle init

- truffle 설정파일 수정

저번 포스팅과 달리 가나쉬 테스트 네트워크를 이용할 예정이므로, port#는 8545로 해준다.

(미리 가나쉬 실행해두기! ganache-cli) 

// truffle-config.js

networks: {
    development: {
      host: "127.0.0.1", // Localhost (default: none)
      port: 8545, // Standard Ethereum port (default: none)
      network_id: "*", // Any network (default: none)
    },

- Counter 컨트랙트 코드 작성 

저번 포스팅에서 상태변수 설정 시 public키워드를 붙였던것과 달리 client 쪽에서 해당 변수를 사용할 수 없게끔 private으로 접근제한을 둔다. 따라서, 상태변수인 count 값을 리턴해주는 getter함수를 따로 만들어줘야한다. 그게 바로 current() 함수임

숫자를 증가/감소 시키는 increment(), decrement() 함수도 작성해준다. 

// contracts/Counter.sol

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.15;

contract Counter{
    uint256 private _count; // 상태변수 private 으로 설정

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

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

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

- migration 코드 작성

// migrations/2_deploy_counter.js

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

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

- compile 명령어 없이 바로 migration 실행하면 자동으로 compile도 해준다!

$ truffle migration

- truffle console 열어서 컨트랙트 실행해보기

컨트랙트 정보를 담고있는 Counter를 deployed() 메소드를 사용해서 it 이라는 변수에 할당해주었다.

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

상태변수값을 가지고 오는 getter 함수 current() 를 실행시켜봄

truffle(development)> it.current()
// 또는
truffle(development)> it.current.call()

> 출력내용

BN = BigNumber

기본적으로 솔리디티는 1ETH라는 10**18 wei의 큰 숫자를 다루기때문에 BN 형태로 숫자가 출력된다.

words 속성이 해당 넘버를 가리키므로 value값을 알고싶다면 이걸 참고하면된다. 

 

- 컨트랙트 실행 : 카운터를 작동시켜서 숫자를 증가시켜본다.

저번 포스팅에선 프라이빗 네트워크를 사용했기 때문에 해당 컨트랙트 실행 내용이 트랜잭션 풀에 담기고, 내가 일일이 마이닝해줘야만 해당 컨트랙트가 실행되었던 반면, 이번엔 ganache 테스트 네트워크를 이용하기 때문에 자동으로 마이닝이 되어서 직접 마이닝 해주지 않아도 컨트랙트가 실행되어 상태변수값이 업데이트되었다!

truffle(development)> it.increment()

가나쉬 네트워크 - sendTransaction 발생

컨트랙트가 실행 완료 되었기때문에 상태변수값이 1 증가하였다.

 

컨트랙트 배포하면 build/contracts내부의 컨트랙트 json 파일상에 truffle이 CA, txHash값 등을 자동으로 업데이트해준다. 

build/contracts/Counter.json

 

테스트 코드 작성해보기

// test/counter.test.js

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

describe("Counter Test", () => {
	let counter;
    it("counter deployed", async () => {
    	counter = await Counter.deployed();
    });
    it("get current", async () => {
    	console.log(await counter.current.call());
    });
    it("increment", async () => {
    	await counter.increment();
        console.log("증가", await counter.current.call());
    });
     it("decrement", async () => {
    	await counter.decrement();
        const result = await counter.current.call();
        conosle.log(result.toNubmer()); // BN 을 일반 int로 변환
    });
});

 

2/ Front-> metamask 연결작업!

 

react 환경에서 프론트단 만들어줄거임

메타마스크-클라이언트 연결 작업을 해주자. (이전 관련 포스팅 : https://yjleekr.tistory.com/89

 

클라이언트가 메타마스크에게 요청을 보낼 수 있게끔 useWeb3 라는 커스텀 훅을 만들어준다. 

해당 훅은 account와 메타마스크와의 연결매개인 web3를 상태값으로 리턴해준다.

 

우선, 이전 포스팅에서 언급한대로 메타마스크가 브라우저 상에 깔려있으면 window.ethereum이 존재할 것이다.

메타마스크와 연결된 상태라면,  web3 상태 및 연결된 계정을 가져와서 account 상태를 업데이트 해준다.

// src/hooks/useWeb3.jsx

import { useState, useEffect } from "react";
import Web3 from "web3/dist/web3.min.js";

const useWeb3 = () => {
  const [account, setAccount] = useState(null);
  const [web3, setWeb3] = useState(null);

  useEffect(() => {
    (async () => {
      if (!window.ethereum) return;
      const [acct] = await window.ethereum.request({
        method: "eth_requestAccounts",
      });
      setAccount(acct);

      const web = new Web3(window.ethereum);
      setWeb3(web);
    })();
  }, []);
  return [account, web3];
};

export default useWeb3;

 

Counter 컴포넌트는 숫자를 증가/감소시키는 컨트랙트를 실행하여 메타마스크에게 요청을 보내주는 역할이다.

상태를 변경시킨다 = 컨트랙트를 실행시킨다 = 하나의 트랜잭션이 발생한다 / 셋 다 비슷한 말이다. 

트랜잭션이 발생한다는 건 서명이 필요하다는 거고 그걸 대신해주는 매체가 메타마스크이다. 

따라서 메타마스크에게 web3 요청을 보내기 위해선 web3 가 필요할 것이고, 컨트랙트 실행 주체도 필요하므로 account도 필요하다.

따라서, useWeb3 훅에서 받아온 account와 web3 객체를 가지고 Counter 컴포넌트에 props로 전달해준다.

import useWeb3 from "./hooks/useWeb3";
import Counter from "./components/Counter";

function App() {
  const [account, web3] = useWeb3();

  if (!account) return <>메타마스크 연결 플리즈</>;
  return (
    <div>
      <span> Account: {account}</span>
      <div>Counter 나올 영역</div>
      <Counter account={account} web3={web3} />
    </div>
  );
}

export default App;

 

Counter는 컨트랙트 실행을 위한 컴포넌트이므로 렌더되자마자 배포된 컨트랙트 정보를 가져와야한다. 

그렇기 때문에 컨트랙트 내용이 담긴 컨트랙트 인스턴스와 화면에 렌더할 count 값을 상태로 가지고 있어야한다.

 

eth.Contract(abi, 'CA값') 메서드를 통해서 해당 컨트랙트(CA)에 접근하여 컨트랙트를 실행시킬 수 있다.

이 때, abi 값은 truffle에서 컴파일한 json 파일 내에서 갖고온다. src/contracts 디렉토리를 만들어 그 안에 json 파일을 저장해두었다.

컨트랙트는 componentDidMount 시점에서 가져오면 되기 때문에 useEffect를 사용하여 deployed 상태를 업데이트 해주고, 상태변수인 count도 업데이트 해준다.

// src/components/Counter.jsx

import React, { useEffect, useState } from "react";
import CounterContract from "../contracts/Counter.json";

const Counter = ({ web3, account }) => {
    const [count, setCount] = useState(0);
    const [deployed, setDeployed] = useState();
    
    const increase = async () => {
    	const result = await deployed.methods.increment().send({
        	from: account,
        });
        if(!result) return;
        const current = await deployed.methods.current().call();
        setCount(current);
    };
    
    const decrease = async () => {
    	const result = await deployed.methods.decrement().send({
        	from: account,
        });
        if(!result) return;
        const current = await deployed.methods.current().call();
        setCount(current);
    };
    
    useEffect(() => {
    	(async () => {
            if (deployed) return;
            const deployedCont = new web3.eth.Contract(
            	CounterContract.abi,
                "0xAF152c774673D45F37adf53792BBCB00D9F2F76A"
            );
            const count = await deployedCont.methods.current().call();
            setCount(count);
            setDeployed(deployedCont);
        })();
    },[]);
    
    return(
    	<div>
            <h2>Counter: {count} </h2>
            <button onClick={increase}>증가</button>
            <button onClick={decrease}>감소</button>
        </div>
    );
};

 

증가나 감소 버튼을 누르면?

아래 스크린샷처럼 메타마스크에서 해당 거래 거부/허용 여부 묻는다

 

 

허용하면 카운트가 증가함과 동시에 아래와 같이 트랜잭션 결과물이 나온다! (코드 상에선 result값)

 

 

다만, 현재 코드는 해당 컨트랙트를 실행히킨 브라우저(클라이언트)만 상태변수가 업데이트 된 걸 인지한다.

같은 블록체인 네트워크 상 노드들은 연결되어 있으므로, 특정 클라이언트에서 해당 컨트랙트가 실행되어 상태변수가 업데이트 된다면 연결된 다른 노드들도 해당 업데이트를 인지해야한다. 

Comments