오늘은 간단한 스마트 컨트랙트를 솔리디티 언어를 이용해 만들어보고,
이를 이더리움 네트워크 안에 tx를 이용해 넣은 다음, 네트워크 상에서 이를 실행해보자.
puppeth을 이용해 네트워크를 셋업한 후 진행하면 된다.
뭔가 이더리움 네트워크 안에서 코드를 실행하려면
1. 우선 솔리디티 언어를 이용해 컨트랙트를 만들고,
2. 이를 컴파일하고 (EVM이 읽을 수 있도록)
3. 컴파일한 내용을 이더리움 네트워크에 tx를 통해 올려주는 과정을 거치면 된다.
1. 솔리디티를 이용해 스마트 컨트랙트 작성
Contracts 폴더를 생성 후, 그 안에 HelloWorld.sol 파일을 만든다.
/* Contracts/HelloWorld.sol */
// SPDX-License-Identifier ^0.8.15;
contract Hello {
string text;
constructor() {
text = "Hello Solidity"
}
function getText() public view returns (string memory) {
return text;
}
}
ts에서 class 객체를 선언하는 것과 유사하다고 생각하면 된다. 솔리디티의 문법에 대한건 일단 나중에 알아보기로 하고,
지금은 Hello contract안에 getText 함수와 text라는 변수가 있다고 생각하고 넘어가면 된다.
이렇게 작성한 코드를 evm이 해석할 수 있도록 컴파일을 거치는 과정이 필요하다.
이 컴파일은 solc 라이브러리의 도움을 받을 수 있다.
npm init -y
npm i solc
npx solc --bin --abi ./Contracts/HelloWorld.sol
각 코드를 터미널에서 순차적으로 입력하면 .bin 파일과 .abi 파일이 하나씩 생성될 것이다.
- abi : contract application binary interface의 약자로, 우리가 한 번 네트워크에 컨트렉트 코드를 컴파일해 올릴 경우,
이는 16진수의 string으로 저장되는데, 우리가 이 함수를 다시 호출하고 싶다면
우리가 필요한 함수의 이름밖에 알 수 없으므로 중간에 16진수 string을 우리가(사람이) 읽을 수 있도록 해석해주는
연결고리가 필요하다. abi가 이 연결고리의 역할을 수행한다고 보면 된다.
- bytecode : 16진수 형식으로 바꾼 컨트랙트 바이트코드이다. 이를 이용해 네트워크 상에 컨트랙트를 배포한다.
2. 이더리움 네트워크 실행
우리가 geth attach에서 tx를 일으킬 때 비밀번호를 이용해 계쩡을 unlock해주지 않으면 tx가 실행되지 않았다.
이번엔 이 번거로운 과정을 좀 생략하기 위해 네트워크를 구동할 때 몇 개의 명령어를 추가해 지갑 주소 몇 개를
unlock한 상태로 네트워크를 시작한다.
우선 지갑을 2개 만든 후, 설정한 비밀번호를 적은 파일을 만든다.
/* node/password */
// ..네트워크 셋업시 생성된 폴더 node에 password 파일을 생성 후 (확장자 없음)
// 내가 처음 설정한 비밀 번호를 적는다.
geth --datadir node --http --http.addr "0.0.0.0" --http.port 9000 --http.corsdomain "*" --http.api "admin,eth,debug,miner,net,txpool,personal,web3" --syncmode full --networkid 7722 --port 30300 --ws --ws.addr "0.0.0.0" --ws.port 9005 --ws.origins "*" --ws.api "miner,eth,net,web3" --allow-insecure-unlock --unlock "0,1" --password "./node/password" --ipcpath "~/.Ethereum/geth.ip"
추가하는 명령어는 다음 3가지이다.
--allow-insecure-unlock : 계정 unlock을 허용
--unlock "0,1" : 0번째, 1번째 계정을 unlock
( getAccount를 통해 계정을 불러오면 출력되는 배열의 index 순서와 동일하다.)
--password "./node/password" : password를 적은 파일의 경로와 파일명을 적는다.
네트워크가 잘 실행되었다면 본격적으로 작성한 컨트랙트를 네트워크에 올려보자.
3. 컨트랙트 배포
.bin 파일과 .abi 파일을 각각 보면 abi는 배열, bin 파일은 string이 있다.
이 둘을 각각 attach에서 변수에 넣어준다.
bytecode = '0x + .bin 파일의 string을 복사해 변수에 대입한다.'
abi = '.abi 파일의 배열을 복사해 대입한다.'
attach는 기본적으로 JS의 문법을 사용해 네트워크와 상호작용하는 프로그램이므로 js에서 변수에 값을 대입하는 것과
동일한 방법으로 변수에 값을 할당할 수 있다.
주의할 점은 bytecode의 value가 될 .bin 파일의 내용물은 string이므로 앞에 0x를 붙인 후 따옴표로 묶어줘야하고,
.abi 파일의 내용물은 그 자체로 배열이므로 그 값에 뭘 더 추가하지말고 대입해주어야 한다는 것이다.
대입 후, 다시 한 번 bytecode, abi를 각각 입력해 값이 잘 대입되었는지 확인한 후, txObject를 만들어준다.
txObject = { from : eth.coinbase, data : bytecode }
from은 이 컨트랙트를 네트워크에 추가하려는 계정 ( 미리 채굴을 통해 코인을 좀 추가해 둘 것),
data엔 우리가 넣어준 16진수 바이트코드를 넣어주면 된다.
계정을 미리 unlock해둔 상태이므로 이대로 이 txObject를 넣고 tx를 발생시킨다.
eth.sendTransaction(txObject)
// "0x2760e073d4a38f13497e6dbbc0ffe979a7483add7cc1fa090213da4b2e2a5e3"
문제가 없다면 txpool이 해당 tx가 들어가고 tx의 hash 값을 돌려줄 것이다.
tx 내용을 조회하려면
eth.getTransaction(txHash) 를 입력해보면 된다.
채굴을 통해 해당 tx를 블록체인에 추가한 후,
eth.getTransactionReceipt(txHash)를 통해 확인할 수 있다.
> eth.getTransaction("0x2760e073d4a38f13497e6dbbc0ffe979a7483add7cc1fa090213da4b2e2a5e38")
{
blockHash: "0x6b8d1f2d5bcf2d532b85702f3b3ce5759d08a243a4785ac9f9b9cccc76ccc343",
blockNumber: 1,
from: "0xa4fad52aad9d5548db57603a21bc7d6d457ddbcc",
gas: 208158,
gasPrice: 1000000000,
hash: "0x2760e073d4a38f13497e6dbbc0ffe979a7483add7cc1fa090213da4b2e2a5e38",
input: "0x608060405234801561001057600080fd5b506040518060400160405280600b81526020017f68656c6c6f20776f726c640000000000000000000000000000000000000000008152506000908161005591906102ab565b5061037d565b600081519050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600060028204905060018216806100dc57607f821691505b6020821081036100ef576100ee610095565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b6000600883026101577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8261011a565b610161868361011a565b95508019841693508086168417925050509392505050565b6000819050919050565b6000819050919050565b60006101a86101a361019e84610179565b610183565b610179565b9050919050565b6000819050919050565b6101c28361018d565b6101d66101ce826101af565b848454610127565b825550505050565b600090565b6101eb6101de565b6101f68184846101b9565b505050565b5b8181101561021a5761020f6000826101e3565b6001810190506101fc565b5050565b601f82111561025f57610230816100f5565b6102398461010a565b81016020851015610248578190505b61025c6102548561010a565b8301826101fb565b50505b505050565b600082821c905092915050565b600061028260001984600802610264565b1980831691505092915050565b600061029b8383610271565b9150826002028217905092915050565b6102b48261005b565b67ffffffffffffffff8111156102cd576102cc610066565b5b6102d782546100c4565b6102e282828561021e565b600060209050601f8311600181146103155760008415610303578287015190505b61030d858261028f565b865550610375565b601f198416610323866100f5565b60005b8281101561034b57848901518255600182019150602085019450602081019050610326565b868310156103685784890151610364601f891682610271565b8355505b6001600288020188555050505b505050505050565b6102318061038c6000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063e00fe2eb14610030575b600080fd5b61003861004e565b6040516100459190610179565b60405180910390f35b60606000805461005d906101ca565b80601f0160208091040260200160405190810160405280929190818152602001828054610089906101ca565b80156100d65780601f106100ab576101008083540402835291602001916100d6565b820191906000526020600020905b8154815290600101906020018083116100b957829003601f168201915b5050505050905090565b600081519050919050565b600082825260208201905092915050565b60005b8381101561011a5780820151818401526020810190506100ff565b83811115610129576000848401525b50505050565b6000601f19601f8301169050919050565b600061014b826100e0565b61015581856100eb565b93506101658185602086016100fc565b61016e8161012f565b840191505092915050565b600060208201905081810360008301526101938184610140565b905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600060028204905060018216806101e257607f821691505b6020821081036101f5576101f461019b565b5b5091905056fea26469706673582212200e69f2ad0c3182f1eeffda39f200baead7254549d27e053e14fbcd98f6743bf964736f6c634300080f0033",
nonce: 0,
r: "0x868e14ce7e7849bde96a82f8139168be0fc52c05ab62ca0bf5dd9b962cc81334",
s: "0x591be1c074995b1fd94d0cc1da74cd3ed2ad5c44ca3bcdfceb278aa8ba112cdf",
to: null,
transactionIndex: 0,
type: "0x0",
v: "0x36f9",
value: 0
}
> eth.getTransaction("0x2760e073d4a38f13497e6dbbc0ffe979a7483add7cc1fa090213da4b2e2a5e38")
{
blockHash: "0x6b8d1f2d5bcf2d532b85702f3b3ce5759d08a243a4785ac9f9b9cccc76ccc343",
blockNumber: 1,
from: "0xa4fad52aad9d5548db57603a21bc7d6d457ddbcc",
gas: 208158,
gasPrice: 1000000000,
hash: "0x2760e073d4a38f13497e6dbbc0ffe979a7483add7cc1fa090213da4b2e2a5e38",
input: "0x608060405234801561001057600080fd5b506040518060400160405280600b81526020017f68656c6c6f20776f726c640000000000000000000000000000000000000000008152506000908161005591906102ab565b5061037d565b600081519050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600060028204905060018216806100dc57607f821691505b6020821081036100ef576100ee610095565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b6000600883026101577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8261011a565b610161868361011a565b95508019841693508086168417925050509392505050565b6000819050919050565b6000819050919050565b60006101a86101a361019e84610179565b610183565b610179565b9050919050565b6000819050919050565b6101c28361018d565b6101d66101ce826101af565b848454610127565b825550505050565b600090565b6101eb6101de565b6101f68184846101b9565b505050565b5b8181101561021a5761020f6000826101e3565b6001810190506101fc565b5050565b601f82111561025f57610230816100f5565b6102398461010a565b81016020851015610248578190505b61025c6102548561010a565b8301826101fb565b50505b505050565b600082821c905092915050565b600061028260001984600802610264565b1980831691505092915050565b600061029b8383610271565b9150826002028217905092915050565b6102b48261005b565b67ffffffffffffffff8111156102cd576102cc610066565b5b6102d782546100c4565b6102e282828561021e565b600060209050601f8311600181146103155760008415610303578287015190505b61030d858261028f565b865550610375565b601f198416610323866100f5565b60005b8281101561034b57848901518255600182019150602085019450602081019050610326565b868310156103685784890151610364601f891682610271565b8355505b6001600288020188555050505b505050505050565b6102318061038c6000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063e00fe2eb14610030575b600080fd5b61003861004e565b6040516100459190610179565b60405180910390f35b60606000805461005d906101ca565b80601f0160208091040260200160405190810160405280929190818152602001828054610089906101ca565b80156100d65780601f106100ab576101008083540402835291602001916100d6565b820191906000526020600020905b8154815290600101906020018083116100b957829003601f168201915b5050505050905090565b600081519050919050565b600082825260208201905092915050565b60005b8381101561011a5780820151818401526020810190506100ff565b83811115610129576000848401525b50505050565b6000601f19601f8301169050919050565b600061014b826100e0565b61015581856100eb565b93506101658185602086016100fc565b61016e8161012f565b840191505092915050565b600060208201905081810360008301526101938184610140565b905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600060028204905060018216806101e257607f821691505b6020821081036101f5576101f461019b565b5b5091905056fea26469706673582212200e69f2ad0c3182f1eeffda39f200baead7254549d27e053e14fbcd98f6743bf964736f6c634300080f0033",
nonce: 0,
r: "0x868e14ce7e7849bde96a82f8139168be0fc52c05ab62ca0bf5dd9b962cc81334",
s: "0x591be1c074995b1fd94d0cc1da74cd3ed2ad5c44ca3bcdfceb278aa8ba112cdf",
to: null,
transactionIndex: 0,
type: "0x0",
v: "0x36f9",
value: 0
}
//
> eth.getTransactionReceipt("0x2760e073d4a38f13497e6dbbc0ffe979a7483add7cc1fa090213da4b2e2a5e38")
{
blockHash: "0x6b8d1f2d5bcf2d532b85702f3b3ce5759d08a243a4785ac9f9b9cccc76ccc343",
blockNumber: 1,
contractAddress: "0x7b007e8be7c10cfbb16465310dc9e08abf56315e",
cumulativeGasUsed: 208158,
effectiveGasPrice: 1000000000,
from: "0xa4fad52aad9d5548db57603a21bc7d6d457ddbcc",
gasUsed: 208158,
logs: [],
logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
status: "0x1",
to: null,
transactionHash: "0x2760e073d4a38f13497e6dbbc0ffe979a7483add7cc1fa090213da4b2e2a5e38",
transactionIndex: 0,
type: "0x0"
}
채굴을 통해 컨트랙트가 블럭에 담겼다면 getTransactionReceipt에 contractAddress (CA)에 hash가 생성되었을 것이다.
이 CA 값을 기억해두자.
이제 abi와 eth의 함수를 이용해 컨트랙트가 네트워크 상에 잘 들어갔는지 확인할 수 있다.
contract = eth.contract(abi)
{
abi: [{
inputs: [],
stateMutability: "nonpayable",
type: "constructor"
}, {
inputs: [],
name: "getText",
outputs: [{...}],
stateMutability: "view",
type: "function"
},
],
eth: {
accounts: ["0xa4fad52aad9d5548db57603a21bc7d6d457ddbcc", "0xc810e9c4b7aa3bc60b107d22a4abfd1b8d806412"],
blockNumber: 27,
coinbase: "0xa4fad52aad9d5548db57603a21bc7d6d457ddbcc",
compile: {
lll: function(),
serpent: function(),
solidity: function()
},
// ...중략
}
객체 안 abi 배열에 getText 함수가 있는 것을 확인할 수 있다.
이 객체 안에서 우리가 원하는 컨트랙트를 특정지어 가져오려면 사용하는게 CA이다.
다음과 같이 instance라는 변수에 CA 값을 이용해 이와 관련된 컨트랙트만을 가져온다.
instance = contract.at(CA)
결과값으로 contract에 관한 정보가 출력될 것이다.
이 중 우리가 아까 작성한 getText 함수를 호출해보자.
instance.getText.call()
// "Hello Solidity"
아까 컨트랙트에서 text 변수 안에 넣어준 값이 출력되면 성공이다.
지금까지 사용한 방법들은 약간 구식인데, 이제 보다 최근에 나온 편한 방법을 사용해 컨트랙트를 수정해보자.
4. 배포된 컨트랙트 수정
한 번 네트워크에 올라간 컨트랙트를 읽는건 자유지만 수정하려면 evm을 다시 한 번 거쳐야 하기 때문에 가스비가 든다.
따라서 솔리디티는 코드를 짤 때 수정할 일이 없도록 처음에 최대한 정교하게 작성하고, 컨트랙트가 가질 정보가
최대한 용량을 적게 쓰는 것이 중요하다. 한 바이트 , 한 바이트가 다 돈이니까..
수정도 처음 컨트랙트를 네트워크에 올리는 것과 크게 다르지 않다.
솔리디티 코드를 수정하고 다시 solc를 이용해 컴파일 한 후, txObject를 만들어 tx하고,
채굴을 통해 블럭에 담은 후, 변수 안에 CA와 abi를 통해 원하는 컨트랙트를 가져와 내부의 함수를 호출해보면 된다.
다음과 같이 컨트랙트에 setText 함수를 추가해보자.
/* Contracts/HelloWorld.sol */
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
contract Hello {
string text;
constructor() {
text = "hello world";
}
function getText() public view returns (string memory) {
return text;
}
function setText(string memory value) public {
text = value;
}
}
이제 .bin, .abi 파일을 지우고, 다시 solc를 이용해 새로운 파일들을 수정해 준다.
npx solc --bin --abi ./Contracts/HelloWorld.sol
// geth attach
bytecode = '0x...'
abi = [ ... ]
txObject = { from : eth.coinbase, data : bytecode }
// txObject 생성
eth.sendTransaction(txObject)
// txHash 를 결과값으로 반환받는다
eth.getTransaction("txHash")
// tx가 잘 생성되었는지 확인한다.
txpool
// txpool에 방금 만든 tx가 pending 상태로 존재한다.
miner.start(8)
// 채굴을 통해 블록을 만들어 tx를 블록에 담는다
miner.stop()
eth.getTransactionReceipt("txHash")
contract = eth.contract(abi)
instance = contract.new(txObject)
// CA값 없이 아까 만든 txObject를 통해 instance 변수에 데이터를 담는다.
instance.getText.call()
// 아까와 동일한 값이 나오는지 확인
instance.setText( 'Bye Solidity', { from : eth.coinbase } )
// text값을 바꿀 문자열과 바꾸려 하는 사람의 지갑 주소를 넣어 함수를 실행한다.
// 이 함수의 실행 자체도 tx이기 때문에 txHash값을 리턴해줄 것이다.
instance.setText 함수를 실행할 때, 바꾸려 하는 사람의 지갑 주소를 넣는다.
이러면 text의 값을 바꾸는 주체가 그 지갑 주소라는 것으로 인식해 이 지갑에서 가스비가 빠져나가게 된다.
이 과정에 별도의 인증 없이 가능한 것은 처음에 계정을 unlock하고 네트워크를 구동했기 때문이다.
다시 한 번 채굴을 통해 블럭을 생성하고 getText 함수를 실행해 text의 값이 잘 바뀌었는지 확인해보자.
instance에서 setText를 이용해 text의 값을 바꾸는 것 자체가 tx이기 때문에
이를 attach에 입력하면 tx Hash를 반환해줄 것이고,
이는 채굴이 진행되면 tx 배열에 담기게 된다.
'Blockchain' 카테고리의 다른 글
#23 ganache 네트워크, truffle을 이용해 Counter 컨트랙트 만들기 (0) | 2022.07.13 |
---|---|
#22 Truffle을 이용한 컨트랙트 배포 (0) | 2022.07.12 |
#20 개인 네트워크 만들기 (geth) (0) | 2022.07.03 |
#19 geth (0) | 2022.06.30 |
#18 metamask 활용 (0) | 2022.06.29 |