이더리움 - Ganache

choko's avatar
Jun 29, 2024
이더리움 - Ganache
 
 
# 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
 
// 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
      1. privateKey 서명
        1. privateKey, err := crypto.HexToECDSA(key)
      1. publicKey 생성
        1. publicKey := privateKey.Public()
        2. publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
      1. nonce 생성
        1. nonce, err := s.config.Client.PendingNonceAt(context.Background(), fromAddress)
      1. 보낼 수량, gas 세팅
        1. value := big.NewInt(1000000000000000000) // in wei (1 eth)
        2. gasLimit := uint64(21000)
        3. gasPrice, err := s.config.Client.SuggestGasPrice(context.Background())
      1. 수령할 계좌
        1. toAddress := common.HexToAddress(key)
      1. submit 트랜잭션 생성
        1. tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, data)
      1. chain 선택
        1. chainID, err := s.config.Client.NetworkID(context.Background())
      1. privateKey로 서명
        1. signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
      1. 전송
        1. err = s.config.Client.SendTransaction(context.Background(), signedTx)
       
    • 결과
      • // 기존 100이더에서 1이더 전송 { "wei": 98999580000000000000, "ether": "98.99958" }
      • gas비로 0.00041이더가 부과되었다
       
       
  • TransferToken → smart contract 부분 확인
      1. method 바이트 생성
        1. transferFnSignature := []byte("transfer(address,uint256)") hash := crypto.NewKeccakState() hash.Write(transferFnSignature) // a9059cbb methodID := hash.Sum(nil)[:4] fmt.Println("method: ", hexutil.Encode(methodID)) // 0xa9059cbb
          • 스마트 컨트랙트의 메서드명이 적힌 transferFnSignature를, Keccak256 해시를 사용하여 해시를 생성한다
      1. 토큰을 보낼 address, amount
        1. paddedAddress := common.LeftPadBytes(toAddress.Bytes(), 32) amount := new(big.Int) amount.SetString("1000000000000000000000", 10) // 1000 tokens paddedAmount := common.LeftPadBytes(amount.Bytes(), 32)
          • address는 32바이트에, 왼쪽이 채워져 있어야 한다.
      1. data에 넣고 guess gas, 트랜잭션 생성 및 전송
        1. 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 설치
 
  1. solidity 코드 생성
    1. 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); } }
  1. solc로 abi, bin 파일 생성
      • solc --abi Store.sol -o Store
        • Store/Store.abi에 파일 생성
      • solc --bin Store.sol -o Store
        • Store/Store.bin에 파일 생성
  1. abigen 으로, abi → go 파일로 변환
      • abigen --abi=Store/Store.abi --pkg=store --out=Store.go
  1. 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
  1. transfer 때처럼 nonce, private key, gas price, chainc id등을 불러온 뒤
  1. NewKeyedTransactorWithChainID 로 새로운 auth(key 트랜잭션 옵션) 생성
  1. auth에 표준 트랜잭션 옵션을 설정해준 후
  1. abigen으로 생성한 go 파일을 import 하여
  1. 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

  1. Deploy 할때처럼, auth 생성
  1. Abigen 으로 생성한 go 파일의, NewStore (go package가 store라고 가정)
    1. instance, err := store.NewStore(address, s.config.Client)
  1. 컨트랙트에 정의된대로, 메서드 실행
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

Tom의 TIL 정리방