GanacheInstall & runSubscribing New blockselect Ganache or Sepolia networkAbigenSolidity 소스 파일로 abi → go 파일생성 및 배포DeployStorage를 통한 배포Contract에 write
# Test Account account 1 0x29fF15c2bF1a5Ac3F528A0E0920c9886b41E7245 a593415a1f59b976eac1af87998f6d3d03c3d708854dcb30b1901549b9aeda02 account 2 0x217429AE925B4D115a0b61E45e92c0aD3b73F597 858394217bb5c8f467acb8dc9e22411f9a65fe0414fec523a803561ac765a66f
Ganache
- 로컬에서 실행할 수 있는 이더리움
- 채굴, p2p 기능은 존재하지 않고, 블록, 체인, 트랜잭션 내용 존재
Install & run
> npm install -g ganache-cli > ganache-cli // result Ganache CLI v6.12.2 (ganache-core: 2.13.2) Available Accounts ================== (0) 0x42E29c2618C9b533DB40Dba4b811177F3bf1eA76 (100 ETH) (1) 0x707192a96cEC987b08AB2d7f0cDFa8eb15c60A64 (100 ETH) (2) 0xc3bB8F3a96f0dC107938C54938792c89143e8613 (100 ETH) (3) 0x47edA2b1Ca3b73ed31939AeCfd1e57D4065970cC (100 ETH) (4) 0xb502d33C9DC1266296BB4c20366DD51c4b9565f1 (100 ETH) (5) 0xdBF17a4131D33C2Be42126a491f535346AA0323b (100 ETH) (6) 0x9474f3C07bcd83eB6d9678f6bfC4B72820690a89 (100 ETH) (7) 0xD0c158893B4290E83646b555BCcc28FCF1224421 (100 ETH) (8) 0x3892b5bd56A0B5Bec0DAE6FBc2736BF34d8920A1 (100 ETH) (9) 0xA3f1b2bF2c1a749099C730d5372283f9e88bBADB (100 ETH) Private Keys ================== (0) 0xb7f731c0a5dd986cc9dad8b11247bbd038006ef893f6f9b5d6d7bb9e44f85e94 (1) 0xcf49f9b3d823033d9e75e6fdaa3f70de2f6756020b953c7b7f42c1ad1425e30d (2) 0x959a4c5db493e7fa8ab43b25362e87fcd6007df54be75275ac47f28272e638a5 (3) 0xa50244437a366e622710e8b4b9e77bb874d70b07e883994ac9edf228b7742c13 (4) 0x7603248a20165f3ee75c9be008d32ef78375c29e3fa75dac6f98c82c9017908c (5) 0x811b4295cfca265fd94f7606e0c6e16fc760395df17e8ca04dbf729bfaa1df7f (6) 0x2eea13f2cbb26e5c5078026fa1d7210f874a5aa12e857e54b2978ca9676a6b82 (7) 0x4c34937eef95c632e804fff058628c1535c5d28b7829805007817ffd229cfcb9 (8) 0x92f3e6ba527ef72b94077dce5a7fd0aa35711e3f4654f553efe604b695b19010 (9) 0x73bcb3b1850bed76c6aafdee39b1fe10530b75e4356a9be61e0ad71ba26f10a4 HD Wallet ================== Mnemonic: property sure liberty ladder abandon hungry draft salon old friend fork survey Base HD Path: m/44'/60'/0'/0/{account_index} Gas Price ================== 20000000000 Gas Limit ================== 6721975 Call Gas Limit ================== 9007199254740991 Listening on 127.0.0.1:8545
- http://127.0.0.1:8545 에 post로 request를 날리면 된다.
// account들 조회 { "jsonrpc":"2.0", "method":"eth_accounts", "params":[] } // account balance 조회 { "jsonrpc":"2.0", "method":"eth_getBalance", "params":["0x42e29c2618c9b533db40dba4b811177f3bf1ea76"] } // 스냅샷 { "jsonrpc":"2.0", "method":"evm_snapshot", "params":[] } // revert { "jsonrpc":"2.0", "method":"evm_revert", "params":["0x1"] // -> snapshopt의 result }
- Sepolia
- Reciept
{ "receipt": [ { "type": "0x2", "root": "0x", "status": "0x1", "cumulativeGasUsed": "0xb704", "logsBloom": "0x400000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000a0000000000002000000000000000000200000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000020000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000", "logs": [ { "address": "0x635d73d6c5ebdf9bae66b1fa9e9e07361305e7b9", "topics": [ "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", "0x0000000000000000000000008496a225aa0c326d1f3da1fcc0d9538d52107464", "0x000000000000000000000000e877139db8095dd59fcbbfd65a02ae08592ac8ea" ], "data": "0x00000000000000000000000000000000000000000000000000000000004c4b40", "blockNumber": "0x52294d", "transactionHash": "0x35b32a3ff7470ed49db25b192608858677ff2a98fe31231a85664ecac98ae288", "transactionIndex": "0x0", "blockHash": "0x5750530272cdb8d3a5b394963374ff66d216cf9fb94a3db96456ec2aa2e0c7e0", "logIndex": "0x0", "removed": false } ], "transactionHash": "0x35b32a3ff7470ed49db25b192608858677ff2a98fe31231a85664ecac98ae288", "contractAddress": "0x0000000000000000000000000000000000000000", "gasUsed": "0xb704", "effectiveGasPrice": "0x3b9aca019", "blockHash": "0x5750530272cdb8d3a5b394963374ff66d216cf9fb94a3db96456ec2aa2e0c7e0", "blockNumber": "0x52294d", "transactionIndex": "0x0" }, { "type": "0x2", "root": "0x", "status": "0x1", "cumulativeGasUsed": "0x1090c", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "logs": [], "transactionHash": "0x91eedf67416b28c5a2c9774ee2cdaa3efaf8ed6bb8baf8fe3b4c3760f07aba37", "contractAddress": "0x0000000000000000000000000000000000000000", "gasUsed": "0x5208", "effectiveGasPrice": "0x3b9aca019", "blockHash": "0x5750530272cdb8d3a5b394963374ff66d216cf9fb94a3db96456ec2aa2e0c7e0", "blockNumber": "0x52294d", "transactionIndex": "0x1" } ] }
- Transfer
- privateKey 서명
privateKey, err := crypto.HexToECDSA(key)
- publicKey 생성
publicKey := privateKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
- nonce 생성
nonce, err := s.config.Client.PendingNonceAt(context.Background(), fromAddress)
- 보낼 수량, gas 세팅
value := big.NewInt(1000000000000000000) // in wei (1 eth)
gasLimit := uint64(21000)
gasPrice, err := s.config.Client.SuggestGasPrice(context.Background())
- 수령할 계좌
toAddress := common.HexToAddress(key)
- submit 트랜잭션 생성
tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, data)
- chain 선택
chainID, err := s.config.Client.NetworkID(context.Background())
- privateKey로 서명
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
- 전송
err = s.config.Client.SendTransaction(context.Background(), signedTx)
- 결과
// 기존 100이더에서 1이더 전송 { "wei": 98999580000000000000, "ether": "98.99958" }
- TransferToken → smart contract 부분 확인
- method 바이트 생성
- 스마트 컨트랙트의 메서드명이 적힌 transferFnSignature를, Keccak256 해시를 사용하여 해시를 생성한다
- 토큰을 보낼 address, amount
- address는 32바이트에, 왼쪽이 채워져 있어야 한다.
- data에 넣고 guess gas, 트랜잭션 생성 및 전송
transferFnSignature := []byte("transfer(address,uint256)") hash := crypto.NewKeccakState() hash.Write(transferFnSignature) // a9059cbb methodID := hash.Sum(nil)[:4] fmt.Println("method: ", hexutil.Encode(methodID)) // 0xa9059cbb
paddedAddress := common.LeftPadBytes(toAddress.Bytes(), 32) amount := new(big.Int) amount.SetString("1000000000000000000000", 10) // 1000 tokens paddedAmount := common.LeftPadBytes(amount.Bytes(), 32)
var data []byte data = append(data, methodID...) data = append(data, paddedAddress...) data = append(data, paddedAmount...) gasLimit, err := s.config.Client.EstimateGas(context.Background(), ethereum.CallMsg{ To: &toAddress, Data: data, }) chainID, err := s.config.Client.NetworkID(context.Background()) if err != nil { log.Fatal(err) } signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey) if err != nil { log.Fatal(err) } err = s.config.Client.SendTransaction(context.Background(), signedTx) if err != nil { log.Fatal(err) }
Subscribing New block
- Subscribe 하기 위해서는 websocket으로 이더리움 네트워크와 연결하여야 한다.
SEPOLIA_URL=wss://sepolia.infura.io/ws/v3/6ece1a163c6642c89b7dae9ebec4af93
sub, err := s.config.Client.SubscribeNewHead(context.Background(), headers) if err != nil { dto.NewErrorResponse(c, http.StatusInternalServerError, err, "Failed to subscribe new head") } for { select { case err := <-sub.Err(): dto.NewErrorResponse(c, http.StatusInternalServerError, err, "Failed to subscribe new head") case header := <-headers: log.Printf(header.Hash().Hex()) // 0xbc10defa8dda384c96a17640d84de5578804945d347072e091b4e5f390ddea7f } }
select Ganache or Sepolia network
# --> select this # TEST_NET=GANACHE # TEST_NET=Sepolia # Gin config {test release debug} Gin_MODE=test # Server config SERVER_HOST=127.0.0.1 SERVER_PORT=8081 # Sepolia config SEPOLIA_URL=wss://sepolia.infura.io/ws/v3/{{API_KEY}} # Ganache config GANACHE_ADDRESS0=0xdb7C688d060Fa51B3eaE1f2A6ce4B5ca86C3C7E9 GANACHE_ADDRESS1=0x4F7baC91f6D6bb8ac54c1168Af68596Be0339D69 GANACHE_ADDRESS2=0x0fa725B06CB235D23270b2433a8dC78B1A51Ad47 GANACHE_ADDRESS3=0x78DF26eD4FE5c755818C7017dc6454D6D44876D2 GANACHE_ADDRESS4=0xa9fA6E44d08dFEDC32bf450B867D57D1acfd9ee1 GANACHE_ADDRESS5=0x2a11b196aF748b82716cB2B3a029BFc8AECb1829 GANACHE_ADDRESS6=0x3034767AA90Ac6DD2FcF99ae375825Ce5DA583CB GANACHE_ADDRESS7=0xd19120090Ba3d84E69D27a559Fbe802faF3813C9 GANACHE_ADDRESS8=0xE91dad6d47168EC042E39D0659Cf13bb5345883F GANACHE_ADDRESS9=0x3b90CD2eF2F65F569B59B32e1a4939D1c91DF55e
Abigen
- go를 이용하여 이더리움과 쉽게 상호작용 할 수 있는 ABI 바인딩 생성기
- abi를 통해 go 패키지를 만든다.
go install github.com/ethereum/go-ethereum/cmd/abigen@latest
Solidity 소스 파일로 abi → go 파일생성 및 배포
require
- solc :
sudo npm install -g solc
- go abigen :
go get -u github.com/ethereum/go-ethereum
cd ~/go/pkg/mod/github.com/ethereum/go-ethereum@v1.13.14
- go-ethereum의 패키지 경로
make devtools
→ make 파일로 abigen 설치
- solidity 코드 생성
pragma solidity ^0.8.24; contract Store { event ItemSet(bytes32 key, bytes32 value); string public version; mapping (bytes32 => bytes32) public items; constructor(string memory _version) public { version = _version; } function setItem(bytes32 key, bytes32 value) external { items[key] = value; emit ItemSet(key, value); } }
- solc로 abi, bin 파일 생성
solc --abi Store.sol -o Store
- Store/Store.abi에 파일 생성
solc --bin Store.sol -o Store
- Store/Store.bin에 파일 생성
abigen
으로, abi → go 파일로 변환abigen --abi=Store/Store.abi --pkg=store --out=
Store.go
- abi로 스마트 컨트랙트 배포
- 컨트랙트를 배포하려면, 컴파일된 바이트코드 형식의 몇가지 추가 정보가 필요하다.
abigen --bin=Store/Store.bin --abi=Store/Store.abi --pkg=store -out=
Store.go
3번으로 얻은
Store.go
파일과, 4번으로 얻은 Store.go
파일은 DeployStorage
기능의 유무 차이가 있다.// DeployStorage deploys a new Ethereum contract, binding an instance of Storage to it. func DeployStorage(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *Storage, error) { parsed, err := StorageMetaData.GetAbi() if err != nil { return common.Address{}, nil, nil, err } if parsed == nil { return common.Address{}, nil, nil, errors.New("GetABI returned nil") } address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(StorageBin), backend) if err != nil { return common.Address{}, nil, nil, err } return address, tx, &Storage{StorageCaller: StorageCaller{contract: contract}, StorageTransactor: StorageTransactor{contract: contract}, StorageFilterer: StorageFilterer{contract: contract}}, nil }
DeployStorage()
함수를 사용하여 go 애플리케이션에서 이더리움 테스트넷에 컨트랙트를 배포할 수 있다.
DeployStorage를 통한 배포
- 컨트랙트를 배포하려면, 다음이 필요하다.
- 이더리움 테스트넷에 연결된 실행중인 Geth 노드
- 컨트랙트 배포 및 상호작용에 필요한 Gas만큼 충분한 이더가 적립된 account
- transfer 때처럼 nonce, private key, gas price, chainc id등을 불러온 뒤
NewKeyedTransactorWithChainID
로 새로운 auth(key 트랜잭션 옵션) 생성
- auth에 표준 트랜잭션 옵션을 설정해준 후
- abigen으로 생성한 go 파일을 import 하여
DeployStore(auth *bind.TransactOpts, backend bind.ContractBackend, _version string)
func (s *Server) DeployContract(c *gin.Context) { req := c.MustGet("req").(dto.DeployContractRequest) nonce, privateKey, err := s.GetNonceAndPKfromKeyString(req.From_private) if err != nil { dto.NewErrorResponse(c, http.StatusBadRequest, err, "Failed to get nonce") return } gasPrice, err := s.config.Client.SuggestGasPrice(context.Background()) if err != nil { dto.NewErrorResponse(c, http.StatusInternalServerError, err, "Gas suggestion failed") return } chainID, err := s.config.Client.NetworkID(context.Background()) if err != nil { dto.NewErrorResponse(c, http.StatusBadRequest, err, "Failed to get chainID") return } auth, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID) if err != nil { dto.NewErrorResponse(c, http.StatusInternalServerError, err, "Failed to bind auth") return } auth.Nonce = big.NewInt(int64(nonce)) auth.Value = big.NewInt(0) // in wei auth.GasLimit = uint64(300000) // in units auth.GasPrice = gasPrice input := "1.0" address, tx, instance, err := store.DeployStore(auth, s.config.Client, input) if err != nil { dto.NewErrorResponse(c, http.StatusBadRequest, err, "Failed to deploy contract") return } c.JSON(http.StatusOK, dto.TransactionHashResponse{ Hash: tx.Hash(), }) fmt.Println(address.Hex()) // 0x147B8eb97fD247D06C4006D269c90C1908Fb5D54 fmt.Println(tx.Hash().Hex()) // 0xdae8ba5444eefdc99f4d45cd0c4f24056cba6a02cefbf78066ef9f4188ff7dc0 _ = instance }
Contract에 write
- Deploy 할때처럼, auth 생성
- Abigen 으로 생성한 go 파일의, NewStore (go package가 store라고 가정)
instance, err := store.NewStore(address, s.config.Client)
- 컨트랙트에 정의된대로, 메서드 실행
key := [32]byte{} value := [32]byte{} copy(key[:], []byte("foo")) copy(value[:], []byte("bar")) tx, err := instance.SetItem(auth, key, value) if err != nil { dto.NewErrorResponse(c, http.StatusInternalServerError, err, "Failed to set item") return }
Share article