BlockChain

(BlockChain) JS에서 Solidity 및 Smart Contract 실행

JJeongHyun 2023. 2. 28. 19:07
반응형

JavaScript 환경에서 solidity 언어로 작성한 코드로 스마트 컨트랙트를 발생시키려고 한다

 

이에 우리는 3개의 라이브러리를 require 해준다

const solc = require("solc");
const fs = require("fs");
const path = require("path");
  • solc 라이브러리 : solidity 코드를 bytecode로 반환하는 컴파일 라이브러리
  • fs 라이브러리 : FileSystem, 파일에 접근하여 데이터를 가져오거나 생성 및 수정 등 기능을 제공 JS 내장 라이브러리
  • path 라이브러리 : 경로에 대한 편의 기능을 제공하는 JS 내장 라이브러리

 

그리곤, 파일 이름을 매개변수로 받아 컴파일 해주는 메서드를 클래스 내에 static 형식으로 생성

// compiler.js 내 Compiler 클래스

static compile(_fileName) {
    const contractPath = path.join(__dirname, "contracts", _fileName);
    const data = JSON.stringify({
      language: "Solidity",
      sources: {
        [_fileName]: {
          content: fs.readFileSync(contractPath, "utf-8"),
        },
      },
      settings: {
        outputSelection: {
          "*": {
            "*": ["*"],
          },
        },
      },
    });
    const compiled = solc.compile(data);
    return Compiler.writeOutput(JSON.parse(compiled));
  }
  • const contractPath = path.join(__dirname, "contracts", _fileName)
    • 현재 문서 경로에서 contracts 폴더, _fileName까지의 경로를 합쳐서 contractPath라는 변수에 저장
  • const data = JSON.stringify({...})
    • solc를 사용하여 솔리디티 코드를 컴파일 시 사용할 설정
  • language : "Solidity"
    • 언어 설정은 Solidity
  • sources : {}
    • 파일로 생성되는 solidity 객체의 이름은 [_fileName], 파일 내용은 contractPath를 utf-8로 전환하여 파일을 읽는 내용으로 설정
  • settings : {}
    • 추가적인 설정은 가져올 정보 설정과 파일이름, 가져올 데이터의 키와 값을 모든 설정
  • const compiled = solc.compile(data)
    • data를 solidity 컴파일 후 compiled 변수에 저장
  • return Compiler.writeOutput(JSON.parse(compiled))
    • JSON 형식으로 반환된 값을 객체화 하여 writeOutput 매개변수로 넘겨준다

 

// compiler.js 내 Compiler 클래스

static writeOutput(_compiled) {
    const result = {};
    for (const contractFileName in _compiled.contracts) {
      const [contractName] = contractFileName.split(".");
      const contract = _compiled.contracts[contractFileName][contractName];

      const abi = contract.abi;
      const bytecode = contract.evm.bytecode.object;
      const tempObj = { abi, bytecode };
      const buildPath = path.join(__dirname, "build", `${contractName}.json`);
      fs.writeFileSync(buildPath, JSON.stringify(tempObj));
      result[contractName] = tempObj;
    }
    return result;
  }
  • 컴파일된 solidity 객체에서 abi값과 bytecode 값을 가져온다.
    • abi : Application Binary Interface의 약자로 스마트 컨트랙트 내의 함수와 매개변수 등을 JSON 형식으로 표기
    • bytecode : 트랜잭션에 저장되는 코드이며 Receipt 내의 CA(contractAddress)로 접근할 수 있다
    • const [contractName] = contractFileName.split(".");
      const contract = _compiled.contracts[contractFileName][contractName]
      const abi = contract.abi;
      const bytecode = contract.evm.bytecode.object;
  • 현재 주소에서 build라는 폴더에 contractName의 JSON형식 파일에 경로를 연결하고 tempObj객체를 JSON 형식화해서 JSON 파일을 생성한다
    • const tempObj = { abi, bytecode };
      const buildPath = path.join(__dirname, "build", `${contractName}. json`);
      fs.writeFileSync(buildPath, JSON.stringify(tempObj));
  • 빈 객체에 contractName으로 키의 값으로 abi, bytecode를 객체화해서 정의한다
    • const result = {};
      result[contractName]=tempObj;
// web3.js

const web3 = require("web3");

let instance;

class Client {
  constructor(_url) {
    if (instance) return instance;
    this.web3 = new web3(_url);
    instance = this;
  }
}
module.exports = Client;
// index.js

const client = new Client("http://127.0.0.1:8545");

const txObj = { data: bytecode };

const contract = new client.web3.eth.Contract(abi);

async function init() {
  const instance = await contract.deploy(txObj).send({
    from: "0xeD4e7b4e1C87D3F728dF323869b14ecCE0272840",
    gas: 1000000,
  });
  console.log(instance.options.address); // CA
}

init();
  • web3 통신으로 ganache 서버에 접속
  • 트랜잭션 객체에 bytecode로 정의
  • contract 변수에 새로운 Contract를 생성하여 abi 전달
  • contractAddress에 접근하기 위해서 init() 함수 생성 및 호출
// index.js

async function test() {
  const accounts = await client.web3.eth.getAccounts();

  const ca = init() 실행 후 나온 contractAddress 주소 삽입;
  const deployed = new client.web3.eth.Contract(abi, ca);

  let text = await deployed.methods.getText().call();

  await deployed.methods.setText("수정하고 싶은 텍스트").send({ from: accounts[1] });

  text = await deployed.methods.getText().call();

  const balance = await client.web3.eth.getBalance(accounts[1]);
  console.log("보낸 녀석의 잔액 : ", balance);
  // 보낸 녀석의 잔액 :  99999990768025909829
  // 보낸 녀석의 잔액 :  999999818450504472886
}
test();
  • ganache 서버에 web3 통신으로 계정을 가져온다
    • const accounts = await client.web3.eth.getAccounts();
  • init() 함수로 나온 contractAddress값을 ca라고 정의한다
    • const ca = "contractAddress 값"
  • 컨트랙트를 생성하고 CA에 연결한다
    • const deployed = new client.web3.eth.Contract(abi, ca);
  • 컨트랙트를 실행하고 해당 계정의 잔액을 확인하여 변동사항을 확인한다
    • let text = await deployed.methods.getText().call();
      await deployed methods.setText("바꿀내용").send({from:accounts[1]})
      const balance = await client.web3.eth.getBalance(accounts[1]);