npm init -y
npm install keythereum web3
keystore
란?
- 암호화된 계정 정보들이 객체로 들어있는 파일들입니다.
- 이때 사용한 암호화는 양방향 암호화이기 때문에 계정을 생성할 때 입력한 계정의
PassWord
를 이용하여 복호화하여 계정주소를 다시Private Key
로 변환할 수 있습니다.
const keythereum = require('keythereum');
const path = require('path');
const account = '0x3c9b17a493558da518ff12b81df75e673ec59ab6';
const dir = path.join(__dirname); // keystore 상위 디렉토리 경로
// keystore 디렉토리에서 account와 같은 데이터가 있는 객체를 리턴
const keyObject = keythereum.importFromFile(account, dir);
console.log(keyObject);
/*
{
address: '3c9b17a493558da518ff12b81df75e673ec59ab6',
crypto: {
cipher: 'aes-128-ctr',
ciphertext: '5189dd114bd013db5e7b05c1b6b9afd310f5af351a0265482aa2f42e24de895a',
cipherparams: { iv: 'c93d8eaf1ee6fbb80a1198d72194c594' },
kdf: 'scrypt',
kdfparams: {
dklen: 32,
n: 262144,
p: 1,
r: 8,
salt: 'bd8c5eaf7f35d3f1c638140355271252257c9f4cf587b4884b97da7c4a75af48'
},
mac: 'e0432ca39c7f17603f28e233b7ecc0582bc6f98bc13d0b59339c106c84d3655b'
},
id: '47029531-6c7d-4e51-b5d0-c92567054e70',
version: 3
}
*/
// 받아온 객체를 가지고 계정을 생성할 때 사용한 Password로 복호화를 하여 개인키를 구해옵니다.
const privateKey = keythereum.recover('1234', keyObject).toString('hex');
console.log(privateKey);
// 78cd9b04766b3902c182e8f8663f2f93e0624753a7eb7a2020af72b80576a46f
pragma solidity ^0.8.15;
contract HelloWorld {
// 상태변수 선언
string public value;
// 생성자
constructor(){
value = "Hello World!";
}
// 상태변수를 가져오는것 함수 getter
function getValue() public view returns (string memory) {
return value;
}
// 상태변수를 바꿔주는 함수 setter
function setValue(string memory _value) public {
value = _value;
}
}
public
으로 선언하였을 경우 상태 변수값을 가져오는 getter
함수를 알아서 만들어줍니다./* Complie.js */\
class Contract{
static compile(_filename) {
const constractPath = path.join(__dirname, '../contracts', _filename);
const data = JSON.stringify({
language: 'Solidity', // 어떤 언어로 컴파일링을 진행할 것인가.
sources: {
[_filename]: {
content: fs.readFileSync(constractPath, 'utf8'),
},
},
settings: {
outputSelection: {
'*': {
'*': ['*'],
},
},
},
});
const compiled = JSON.parse(solc.compile(data));
this.writeOutput(compiled);
}
static writeOutput(_compiled) {
for (const constractFileName in _compiled.contracts) {
const [contractName] = constractFileName.split('.');
const contract = _compiled.contracts[constractFileName][contractName];
const abi = contract.abi;
const bytecode = contract.evm.bytecode.object;
console.log(abi)
console.log(bytecode)
const obj = {
abi,
bytecode,
};
const buildPath = path.join(__dirname, '../build', `${contractName}.json`);
fs.outputJSONSync(buildPath, obj);
}
}
}
module.exports = { Contract };
Contract
클래스로Solidity Code
로 작성한 파일명을 인자값으로 받고 해당 파일명을 가지고
해당 파일의Path
를 ( 현재 실행중인 폴더 경로(--dirname
) +/contracts
+인자값으로 받은 파일명
) 으로 구하였습니다.- 이제
solc.compile()
메서드로 컴파일링을 할 것인데 인자값으로 받는data
객체로 안에는
컴파일링을 진행할 파일의 사용 언어 (language
), 컴파일링할 파일의 내용 (source
), 설정값 (settings
)이 들어갑니다.- 아래는
compile
한 내용을JSON.parse
로JSON
형식으로 만든값입니다. 아래와 같이compile
이 완료되면 컴파일한 내용을writeOutput
메서드의 인자값으로 넣고 해당 메서드를 실행합니다.
- 이제
writeOutput
메서드를 보면for in
문을 사용하여complied.contracts
객체의key
값('HelloWorld.sol')을 가져온후split
을 사용하여.
을 기준으로 첫번째index
값인 (HelloWorld
)을 가져왔습니다.- 위에서 가져온 'HelloWorld.sol'과
HelloWorld
를 가지고_compiled.contracts
객체 안에 있는abi
파일과bytecode
값을 가져올 것입니다._compiled.contracts
의 내용은 아래와 같습니다.
- 이제 각각 구해온
abi
값과bytecode
값을buildPath
경로의HelloWorld.json
파일로 만들것이고 그 안에는 구해온abi
와bytecode
값이 들어갈 것 입니다.
/* Client.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 };
- 블록체인 네트워크에 배포를 하기위해
url
을 인자값으로 받아web3
라이브러리를 사용하여 새로운web3
인스턴스를 생성하는Class
를 만들어주었습니다.- 위의 코드를 보면 이
Class
로 인스턴스를 생성할 경우 오직 1개의Class
만 생성되는것을 확인할 수 있는데, 생성자 메서드가 여러번 호출되어도 생성되는 객체는 최초의 하나이고 이후에는 이미 생성된 인스턴스를return
해줍니다.- 이러한 패턴을 싱글톤(
Singleton
) 패턴이라고 하는데 이런식으로 구현하는 이유는 싱글톤 패턴으로 생성한 인스턴스는 전역으로 사용되는 인스턴스이기 때문에 접근성이 용이하고, 메모리 적인 측면에서도 불필요한 인스턴스를 생성하지 않아 훨씬 효율적이라고 할 수 있습니다.
/* index.js */
const { Contract } = require('./controllers/compile.js');
const { Client } = require('./controllers/client.js');
const [abi, bytecode] = Contract.compile('HelloWorld.sol');
const client = new Client('ws://127.0.0.1:9000');
// txObj에 bytecode를 넣어줍니다.
const txObject = {
data: bytecode,
};
// abi 내용이 포함된 Contract에 대한 instance가 생성됩니다.
// bytecode들을 incoding하여 읽을수 있게 해주는 파일 = abi
const contract = new client.web3.eth.Contract(abi);
async function init() {
// Contract 배포 => 트랜잭션이 발생했다는것 => Mining을 해야한다.
const instance = await contract.deploy(txObject).send({ from: '71083ec003149f2f1bfe62ed8fd9fbe8c6902bf9' });
// miner.start(4)
const ca = instance.options.address;
const deployed = new client.web3.eth.Contract(abi, ca);
const data = await deployed.methods.value().call();
console.log('before', data);
// Contract를 실행하면서 수수료를 지불하며 Txpool에 담기고 블록에 담겨야 contract가 실행되면서 value값이 바뀐다.
await deployed.methods.setValue('Hello Contract!').send({ from: '71083ec003149f2f1bfe62ed8fd9fbe8c6902bf9' });
// miner.start(4)
const changed = await deployed.methods.value().call();
console.log('after', changed);
}
init();
- 이제 솔리디티 코드작성, 컴파일링 및 블록체인 네트워크에 접근하는
Class
들은 모두 만들었으니 실제로 컴파일링된 스마트 컨트랙트를 배포하고 실행해보도록 하겠습니다.Contract.compile('HelloWorld.sol')
메서드를 사용하여 해당 솔리디티 코드를 컴파일링한abi
와bytecode
값을return
받고abi
를new client.web3.eth.Contract(abi)
의 인자값으로 넣어abi
내용을 포함하고 있는contract
인스턴스를 생성해 주었습니다.- 이제
init
함수를 실행하여contract
의deploy
메서드의 인자값에 배포할bytecode
인txObj
를 넣고.send()
메서드를 사용하여 스마트 컨트랙트를 배포할 지갑 주소를 넣어주고 실행하면bytecode
를data
로 갖는Transaction
객체가 생성되어Transaction Pool
에 담기게 되고 아직mining
이 된 상태가 아니니 블록체인 네트워크에는 배포되지 않은 상태입니다.- 이후
mining
을 하면Transaction Pool
에 있던Transaction
이 블록에 담겨 블록체인 네트워크에 배포됩니다.- 배포된 스마트 컨트랙트를 실행하기 위해 해당 컨트랙트의
CA
값을 가져온후abi
내용과CA
값을 가진deployed
인스턴스를 생성하였습니다.deployed
객체의methods
안에 있는value()
메서드를call
하여 상태변수 값을 가져올 수 있습니다.setValue()
setter
함수로 상태변수의 값을Hello Contract!
로 바꾸려면Transaction
이 발생되니 해당Transaction
의 가스 수수료를 지불할 지갑 주소를.send
메서드에 인자값으로 적어줍니다. 이제 상태변수를 바꾸려는Transaction
이Transaction Pool
에 담겼으니 다시mining
을 해준후value().call()
getter
함수로value
값을 찍어보면 상태변수가 바뀌어 있는것을 확인할 수 있습니다.
- 솔리디티 스마트 컨트랙트 개발을 할때 많이 사용하는
프레임워크
입니다. (web3
라이브러리 기반 )- 이 외에
하드햇
이라는프레임워크
도 있습니다. (ethers
라이브러리 기반 )
- npm install -g truffle
- truffle version ( 설치 및 버전 확인 )
- npx truffle init
Truffle 프레임워크에서 사용하는 디렉토리와 파일의 역할은 다음과 같습니다.
- build : 솔리디티 코드를 컴파일한 내용을 담는 디렉토리
- contracts : 솔리디티 코드를 담는 디렉토리
- migrations : 배포하는 코드를
contract
별로 나눠서 정리한 디렉토리- test : 배포된
Contract
를 실행시켜보는 테스트 공간- truffle-config.json : 네트워크에 대한 설정값등
Truffle
의 환경설정이 들어있는 파일
HelloWorld.sol 파일 그대로 contracts 디렉토리에 작성합니다.
npx truffle compile 솔리디티 코드를 컴파일링합니다.
build 디렉토리에 HelloWorld.sol 솔리디티 코드가 컴파일된 내용들이 담깁니다.
2_depoly_HelloWorld 파일 생성합니다. migrations 파일구조는 ([번호]_내용_Contract명) 입니다.
const HelloWorld = artifacts.require('HelloWorld'); // HelloWorld.json 파일 가져오기
module.exports = function (deployer) {
deployer.deploy(HelloWorld);
};
npx truffle migration - HelloWorld.json파일 가지고 블록체인 네트워크에 Contract 배포하기 -> 여기에서 발생한 가스비는 default로 coinbase 지갑 주소에서 지불합니다.
아직 txpool에 담겨있으니 miner.start(4)을 해줍니다.
npx truffle console -> 명령줄이 바뀝니다. 이것은 geth console과 같습니다.
HelloWorld => 객체가 나옵니다. Contract에 대한 내용 , 가장 마지막에 배포된 Contract와 기존의 web3를 합친 객체가 나옵니다.
HelloWorld. 탭두번을 누르면 배포에 대한 내용이 나옵니다.
HelloWorld.address => CA값을 리턴합니다. 0xDeaAD4CDd4A013f713e81BB2393e2733a435e4A2
HelloWorld.deployed().then(instance => hello = instance ) 배포된 Contract를 hello라는 변수에 담았다. 여기는 Contract에 대한 내용만 들어있는 객체로 리턴 됩니다.
hello. 탭 두번
hello.value() => Hello World!
hello.setValue('Hello Contract!') => Mining => TxReceipt 값이 나옵니다.
hello.value() => Hello Contract!