일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 아이디 중복체크기능
- 라우터 분리
- buffer.from
- Uncaught Error: could not find react-redux context value; please ensure the component is wrapped in a <Provider>
- 라우터와 미들웨어
- css기초
- cookie-parser 만들어보기
- FormData()
- 블록 만들기
- JWT 만들어보기
- nodejs파일업로드
- useContext
- JWT 로그인 기능 구현
- ws 라이브러리
- javascript기초
- 라우트 매개변수
- 시퀄라이즈 기본설정
- express.static
- 라우터미들웨어 분리
- OAuth 카카오
- express session
- JWT 하드코딩
- 비동기파일업로드
- express router
- mysql wsl
- 세션으로 로그인 구현
- express실행
- next 매개변수
- node.js path
- useEffect clean up
- Today
- Total
즐코
JS로 스마트 컨트랙트 컴파일, 배포 및 실행 본문
truffle 을 배우기 전 자바스크립트로 스마트 컨트랙트를 컴파일해보고 이를 배포, 실행하는 과정을 정리해보았다.
1/ keythereum 라이브러리 사용해서 개인키 알아내기
스마트컨트랙트 배포를 위해 트랜잭션을 만드려면 서명이 필요한데 서명에는 개인키가 필요하다는 걸 인지해야한다.
개인키를 뽑아내기 위해서는 계정 생성 시 만들어진 keystore 디렉토리 내의 계정 정보가 담긴 UTC 파일을 사용해야한다.
이 때, keythereum 라이브러리를 통해서 UTC 파일 내용을 복호화해서 개인키를 알아낼 수 있다.
$ npm i keythereum web3
우선, 실습을 위해 이전에 생성한 계정 정보 UTC가 담긴 keystore 디렉토리를 복사해서 갖고온다.
keythereum.importFromFile("개인키 가져올 계정", "keystore 폴더가 있는 디렉토리, 즉 keystore 상위폴더")
: UTC 파일을 가져오는 메소드
keythereum.recover("계정 설정 시 넣었던 비번", 위에서 만든 키오브젝트).toString("hex");
: 개인키 뽑아오는 메소드
const keythereum = require("keythereum");
const path = require("path");
const address = "0x3d948d956f982a451ebb3363f1ffc340960de9db"; // 개인키를 가져올 계정
const dir = path.join(__dirname);
// /Users/yjlee/Desktop/workspace/blockchain/solidity/220712_truffle
const keyObject = keythereum.importFromFile(address, dir);
const privateKey = keythereum.recover("1234", keyObject).toString("hex");
console.log(privateKey);
// 4933dae8dd43f35b7104ad71816511984cff19e982b6697a281b173c6932d681
2/ JS로 솔리디티 코드 컴파일
1. 솔리디티 코드 작성
우선, 저번 포스팅에서 작성한 solidity 코드를 가져온다.
솔리디티에선 상태변수 앞에 public 키워드 (접근제한자)를 붙이면 getter 함수가 자동으로 생성된다.
따라서, 저번 포스팅과 달리 getValue 라는 상태변수를 반환하는 함수를 따로 넣어주지 않았다.
contracts/HelloWorld.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
contract HelloWorld{
string public value;
constructor(){
value = "Hello World";
}
function setValue(string memory _v) public {
value = _v;
}
}
2. 컴파일 실행용 파일 만들어주기
저번 포스팅에선 $ npx solc --bin --abi ./Contracts/HelloWorld.sol 명령어로 컴파일 해줬으나, 이번 포스팅에선 js 코드로 솔리디티 코드를 컴파일해주려고 한다.
재사용성을 위해 컨트랙트 내용을 컴파일 해주는 클래스를 만들었다. Contract
내부에는 두 개의 정적 메소드를 만들어주었다. compile(), createCompiledFile()
1- 컴파일 값 뽑아내는 정적 메소드 만들기 compile()
solc.complie(data) : 컴파일 해주는 메소드
이때, 인자로는 아래의 속성을 가진 객체를 string으로 바꿔준 값을 넣어줘야한다. 속성은 기본적으로 아래 3가지를 넣어주었다.
- language: 어떤 언어를 컴파일 할거냐
- sources: { 컴파일됐을때의 파일명: content: 컴파일할 파일 내용}
- settings: 컴파일 설정...? (뭔지 잘 모르겠음)
// controllers/compile.js
const solc = require("solc");
const fs = require("fs-extra");
const path = require("path");
class Contract {
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 = JSON.parse(solc.compile(data));
return Contract.createCompiledFile(compiled); // [abi, bytecode]
}
}
스마트컨트랙트 코드가 compile된 값을 JSON화한 값을 출력해보면 아래와 같다.
contracts 내용에는 해당 솔리디티 파일을 컴파일한 abi, evm, metadata 등이 담긴 객체가 있다.
sources에는 id값이 생겨있는데, 이건 뭔지 우선 모르겠다..
아무튼 중요한건 contracts 안에 담긴 내용이니 이 속성을 활용하여 abi와 bytecode를 가진 json 파일을 생성하려고 한다.
2개의 컨트랙트 코드를 작성해주면 아래와 같이, 파일당 하나씩 속성값으로 추가된다. 따라서 아래 abi, bytecode를 포함한 json파일을 생성하는 메소드 내에서 for문을 돌리는 방식으로 파일을 생성해주는 것이다.
2- abi, bytecode 담아줄 json 파일 생성해주는 정적 메소드 만들기 : createCompiledFile()
abi와 bytecode 둘 다 compiled 객체의 contracts 속성 내 객체 안의 꽤 깊은 위치에 존재하고 있다.
- abi : _compiled -> contracts -> 'HelloWorld.sol' -> HelloWorld -> abi
- bytecode : _complied -> contracts -> 'HelloWorld.sol' -> HelloWorld -> evm -> bytecode -> object
fs-extra 모듈 사용하여 JSON 파일 생성 후 저장
fs.outputJSONSync('빌드한 json파일 저장 경로', json형태로 저장할 내용)
// controllers/compile.js의 class Contract 내부 정적 메소드
static createCompiledFile(_compiled){
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 obj = { abi, bytecode };
const buildPath = path.join(__dirname, "../build", `${contractName}.json`);
fs.outputJSONSync(buildPath, obj); // 컴파일된 파일 생성
return [abi, bytecode] = Contract.compile("HelloWorld.sol");
}
}
module.exports = { Contract };
3. 이더리움 네트워크와 연결될 클라이언트 생성하는 클래스 만들어주기
아래 클래스는 싱글톤 패턴의 클래스이다. 보통 클래스는 여러개의 인스턴스를 생성할 수 있지만, 이 싱글톤 패턴의 경우 한 개의 객체만을 생성하고 그 이후에 만들어진 인스턴스들은 최초로 생성된 첫번째 인스턴스를 참조한다.
모든 인스턴스 객체가 다른 참조값을 가리키고 있는게 아니라, 하나의 참조값을 가리킨다.
// controllers/client.js
const Web3 = require("web3");
let instance;
class Client {
constructor(_url) {
if (instance) return instance;
this.web3 = new Web3(new Web3.providers.WebsocketProvider(_url));
instance = this;
}
}
module.exports = { Client };
4. 스마트 컨트랙트 배포 및 실행해보기
배포
1. 위에서 만든 Contract, Client 클래스를 가져와서, HelloWorld.sol 코드를 컴파일한 abi, bytecode값을 받아오고, web3 를 속성값으로 가진 geth와의 통신을 위한 client 인스턴스도 생성해준다.
2. 스마트 컨트랙트 배포 시 트랜잭션 내 데이터 상에는 우선 솔리디티 코드를 컴파일한 bytecode만 필요하기 때문에, 트랜잭션 객체 상에는 우선 bytecode 만 넣어준다.
3. 그리고 geth와 연결된 client의 web3 인스턴스를 사용하여 abi 내용을 contract 에 포함하여 contract 인스턴스를 생성해준다.
4. 배포를 위해 contract.deploy() 메소드 상에 bytecode를 데이터로 가진 txObject를 넣어 배포한다.
send() 메소드의 인자로는 컨트랙트를 배포할 계정을 넣어주면 된다. 해당 배포 코드는 프로미스 객체를 반환하기 때문에 async-await 처리를 위해 init 함수안에 넣었고 init()을 실행시키면 배포할 준비 완료
마이닝을 실행해준다!
5. instance.options.address 값이 CA 값으로 출력된다. CA가 출력됐다는 건 배포되었다는 뜻이다.
// index.js
const { Contract } = require("./controllers/compile");
const { Client } = require("./controllers/client");
const [abi, bytecode] = Contract.compile("HelloWorld.sol");
const client = new Client("ws://127.0.0.1:7979");
// 우선 txObject 데이터 상에는 우선 bytecode만 필요하다.
const txObject = {
data: bytecode,
}
const contract = new client.web3.eth.Contract(abi);
async function init(){
const instance = await contract.deploy(txObject).send({
from: "0x3d948d956f982a451ebb3363f1ffc340960de9db",
gas: 3000000,
})
console.log("CA 나온다", instance.options.address);
// CA 나온다 0x5994D99ce2A754CD5Cbe00bDB8F23684CF8f7980
}
init();
배포된 컨트랙트 실행!
1. 기존의 contract 는 주석처리해두고, 위에서 트랜잭션 배포로 인해 받은 CA를 기존의 contract 상에 추가해준다.
해당 코드에서의 contract는 abi 내용뿐만 아니라 CA 값을 넣어주었기 때문에, 이 특정 컨트랙트에 접근할 수 있는 하나의 인스턴스가 되었다.
2. contract.methods.value().call()
: 컨트랙트 내부의 상태변수값을 가져온다. (getter 함수)
따라서 기존의 상태 변수 값인 Hello World가 출력된다.
3. contract.methods.setValue("바꿀 데이터").send({from: "트랜잭션 실행 계정' })
: 컨트랙트를 실행,변경해본다. (setter 함수)
해당 컨트랙트 내에서 작성한 setValue 를 실행하므로 트랜잭션을 실행하는 계정을 send() 메소드의 인자로 넣어줘야 한다.
반환값을 확인해보면 새로운 트랜잭션이 생성되었고, txpool에도 쌓이게 된다.
4. 마이닝을 한 뒤 다시 contract.methods.value().call() 을 해보면, 기존의 상태변수값이 위에서 setValue의 인자로 넣었던 데이터로 바뀐 걸 확인할 수 있다.
// index.js
const { Contract } = require("./controllers/compile");
const { Client } = require("./controllers/client");
const [abi, bytecode] = Contract.compile("HelloWorld.sol");
const client = new Client("ws://127.0.0.1:7979");
// 우선 txObject 데이터 상에는 우선 bytecode만 필요하다.
const txObject = {
data: bytecode,
}
const ca = "0x5994D99ce2A754CD5Cbe00bDB8F23684CF8f7980";
// const contract = new client.web3.eth.Contract(abi);
const contract = new client.web3.eth.Contract(abi, ca);
async function init(){
const instance = await contract.deploy(txObject).send({
from: "0x3d948d956f982a451ebb3363f1ffc340960de9db",
gas: 3000000,
})
console.log("CA 나온다", instance.options.address);
// CA 나온다 0x5994D99ce2A754CD5Cbe00bDB8F23684CF8f7980
}
// init();
contract.methods
.value()
.call()
.then((data) => console.log(data)); // Hello World;
contract.methods
.setValue("hello yj 👀")
.send({
from: "0x3d948d956f982a451ebb3363f1ffc340960de9db",
})
.then((data) => console.log(data))
/*
blockHash: '0x87dc3e6ad343961ebfe6212f0307e4ca9421359164d5e7f1108623709ed7111b',
blockNumber: 469,
contractAddress: null,
cumulativeGasUsed: 475273,
effectiveGasPrice: 1000000000,
from: '0x3d948d956f982a451ebb3363f1ffc340960de9db',
gasUsed: 28932,
logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
status: true,
to: '0x5994d99ce2a754cd5cbe00bdb8f23684cf8f7980',
transactionHash: '0x9381d4e06478f7dd601cfdcd12148bc365790e61f47f4ffa789067b2f4ed2e16',
transactionIndex: 1,
type: '0x0',
events: {}
*/
'BlockChain' 카테고리의 다른 글
메타마스크 통해서 스마트 컨트랙트 실행해보기 (0) | 2022.07.13 |
---|---|
truffle - 스마트 컨트랙트 개발 프레임워크 (0) | 2022.07.12 |
solidity 시작 - 스마트 컨트랙트 배포와 실행 (1) | 2022.07.11 |
RPC 로 geth와 통신 / puppeth / 메타마스크-private 네트워크 연동 (0) | 2022.07.02 |
geth로 네트워크 돌려보기 / private 네트워크 구축해보기 (0) | 2022.07.01 |