티스토리 뷰

0. 이전 게시글

 

[Solitidy+Go] geth로 스마트 컨트랙트 배포하기 - 3. 생성된 Go 코드로 스마트 컨트랙트 배포

1. 로컬 테스트넷 실행 $ anvil 2. 최상위 경로에 .env 파일 생성 PRIVATE_KEY= RPC_ENDPOINT=http://localhost:8545 이 때 PRIVATE\_KEY가 0x로 시작하면 파싱에서가 발생하므로 0x를 지워준 16진수값만 넣어줍니다. 3.

piatoss3612.tistory.com


1. 코드 작성

cmd/interact/main.go

package main

import (
	"context"
	"crypto/ecdsa"
	"fmt"
	token "go-ethereum-example/gen"
	"math/big"
	"os"

	_ "github.com/joho/godotenv/autoload"

	"github.com/ethereum/go-ethereum"
	"github.com/ethereum/go-ethereum/accounts/abi/bind"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/ethereum/go-ethereum/ethclient"
)

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	// Connect to Ethereum client with RPC endpoint
	client, err := ethclient.DialContext(ctx, os.Getenv("RPC_ENDPOINT"))
	handleError(err)

	defer client.Close()

	fmt.Println("Successfully connected to Ethereum client")

	// Change these addresses to match your contract!
	contractAddress := common.HexToAddress("0x5FbDB2315678afecb367f032d93F642f64180aa3")
	toAddress := common.HexToAddress("0x70997970C51812dc3A010C7d01b50e0d17dc79C8")

	// Create an instance of the contract, specifying its address
	tokenInstance, err := token.NewToken(contractAddress, client)
	handleError(err)

	// Parse wallet private key
	privateKey := mustParsePrivateKey()
	address := crypto.PubkeyToAddress(privateKey.PublicKey)

	// Get nonce, gas price and chain ID
	nonce, err := client.PendingNonceAt(context.Background(), address)
	handleError(err)

	gasPrice, err := client.SuggestGasPrice(context.Background())
	handleError(err)

	fmt.Printf("Suggested gas price: %s\n", gasPrice)

	chainID, err := client.NetworkID(context.Background())
	handleError(err)

	fmt.Printf("Chain ID: %d\n", chainID)

	// Create an transactor with the private key, chain ID and nonce
	signer, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID)
	handleError(err)

	signer.GasPrice = gasPrice
	signer.GasLimit = 3000000
	signer.Nonce = big.NewInt(int64(nonce))

	// Call transfer method (state-changing)
	tx, err := tokenInstance.Transfer(signer, toAddress, big.NewInt(1000000))
	handleError(err)

	fmt.Printf("Transaction hash: %s\n", tx.Hash().Hex())

	// Wait for the transaction to be mined
	receipt, err := bind.WaitMined(context.Background(), client, tx)
	handleError(err)

	fmt.Printf("Transaction receipt status %d\n", receipt.Status)

	// If the transaction was reverted by the EVM, we can see the reason
	if receipt.Status == 0 {
		msg := ethereum.CallMsg{
			From:     address,
			To:       tx.To(),
			Gas:      tx.Gas(),
			GasPrice: tx.GasPrice(),
			Value:    tx.Value(),
			Data:     tx.Data(),
		}

		_, err = client.CallContract(ctx, msg, nil)
		fmt.Printf("Transaction reverted: %v\n", err)
		return
	}

	// Extract the transfer event from the receipt
	var transferred *token.TokenTransfer

	for _, log := range receipt.Logs {
		transferred, err = tokenInstance.ParseTransfer(*log)
		if err == nil {
			break
		}
	}

	if transferred != nil {
		fmt.Printf("Transferred %d tokens from %s to %s\n", transferred.Value, transferred.From.Hex(), transferred.To.Hex())
	}

	// Call the contract method (read-only)
	toBalance, err := tokenInstance.BalanceOf(&bind.CallOpts{Context: ctx}, toAddress)
	handleError(err)

	fmt.Printf("To balance: %d\n", toBalance)
}

func mustParsePrivateKey() *ecdsa.PrivateKey {
	rawPrivateKey := os.Getenv("PRIVATE_KEY")

	// Parse the private key
	privateKey, err := crypto.HexToECDSA(rawPrivateKey)
	handleError(err)

	return privateKey
}

func handleError(err error) {
	if err != nil {
		panic(err)
	}
}

테스트넷 RPC 클라이언트 연결

// Connect to Ethereum client with RPC endpoint
client, err := ethclient.DialContext(ctx, os.Getenv("RPC_ENDPOINT"))
handleError(err)

defer client.Close()

fmt.Println("Successfully connected to Ethereum client")

배포된 스마트 컨트랙트의 인스턴스 생성

// Change these addresses to match your contract!
contractAddress := common.HexToAddress("0x5FbDB2315678afecb367f032d93F642f64180aa3")
toAddress := common.HexToAddress("0x70997970C51812dc3A010C7d01b50e0d17dc79C8")

// Create an instance of the contract, specifying its address
tokenInstance, err := token.NewToken(contractAddress, client)
handleError(err)

contractAddress: 배포된 스마트 컨트랙트의 주소

toAddress: transfer 함수의 대상 주소, 로컬 테스트넷 실행 시에 제공되는 두 번째 주소를 사용함

16진수 프라이빗 키를 ECDSA 프라이빗 키로 파싱

// Parse wallet private key
privateKey := mustParsePrivateKey()

트랜잭션 서명자 생성

address := crypto.PubkeyToAddress(privateKey.PublicKey)

fmt.Printf("Deploying contract from address %s\n", address.Hex())

// Get nonce, gas price and chain ID from the Ethereum client
nonce, err := client.PendingNonceAt(ctx, address)
handleError(err)

gasPrice, err := client.SuggestGasPrice(ctx)
handleError(err)

fmt.Printf("Suggested gas price: %s\n", gasPrice)

chainID, err := client.NetworkID(ctx)
handleError(err)

fmt.Printf("Chain ID: %d\n", chainID)

// Create an signer with the private key, chain ID and nonce
signer, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID)
handleError(err)

signer.GasPrice = gasPrice
signer.GasLimit = 3000000
signer.Nonce = big.NewInt(int64(nonce))

transfer 함수 실행 및 마이닝 된 트랜잭션 상태 확인

// Call transfer method (state-changing)
tx, err := tokenInstance.Transfer(signer, toAddress, big.NewInt(1000000))
handleError(err)

fmt.Printf("Transaction hash: %s\n", tx.Hash().Hex())

// Wait for the transaction to be mined
receipt, err := bind.WaitMined(context.Background(), client, tx)
handleError(err)

fmt.Printf("Transaction receipt status %d\n", receipt.Status)

// If the transaction was reverted by the EVM, we can see the reason
if receipt.Status == 0 {
	msg := ethereum.CallMsg{
		From:     address,
		To:       tx.To(),
		Gas:      tx.Gas(),
		GasPrice: tx.GasPrice(),
		Value:    tx.Value(),
		Data:     tx.Data(),
	}

	_, err = client.CallContract(ctx, msg, nil)
	fmt.Printf("Transaction reverted: %v\n", err)
	return
}

로그에서 Transfer 이벤트 추출

// Extract the transfer event from the receipt
var transferred *token.TokenTransfer

for _, log := range receipt.Logs {
	transferred, err = tokenInstance.ParseTransfer(*log)
	if err == nil {
		break
	}
}

if transferred != nil {
	fmt.Printf("Transferred %d tokens from %s to %s\n", transferred.Value, transferred.From.Hex(), transferred.To.Hex())
}

토큰을 전송받은 주소의 잔액 확인

// Call the contract method (read-only)
toBalance, err := tokenInstance.BalanceOf(&bind.CallOpts{Context: ctx}, toAddress)
handleError(err)

fmt.Printf("To balance: %d\n", toBalance)

2. 코드 실행

$ go run ./cmd/interact/
2023/12/12 13:38:36 Successfully connected to Ethereum client
Suggested gas price: 1879547559
Chain ID: 31337
Transaction hash: 0x8336dceaef677f437377c6c90caf4ba15c3851c8f386c060e386fab5f90df64c
Transaction receipt status 1
Transferred 1000000 tokens from 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 to 0x70997970C51812dc3A010C7d01b50e0d17dc79C8
To balance: 1000000

3. 트랜잭션이 revert되는 경우

함수 호출에 잘못된 인수 전달

tx, err := tokenInstance.Transfer(signer, toAddress, big.NewInt(-1))

코드 실행

$ go run ./cmd/interact/
Successfully connected to Ethereum client
Suggested gas price: 1879547559
Chain ID: 31337
Transaction hash: 0x428986a4b3faa46db5e354fc218ed7b31ff09706e00204fd30340fe401bcd81e
Transaction receipt status 0
Transaction reverted: execution reverted

4. 전체 코드

 

GitHub - piatoss3612/go-ethereum-practice

Contribute to piatoss3612/go-ethereum-practice development by creating an account on GitHub.

github.com

 

최근에 올라온 글
최근에 달린 댓글
«   2025/01   »
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
Total
Today
Yesterday
글 보관함