Using a Simulated Client

You can use a simulated client for testing your transactions locally quickly and easily, ideal for unit tests. In order to get started we're going to need an account with some initial ETH in it. To do that first generate an account private key.

privateKey, err := crypto.GenerateKey()
if err != nil {
  log.Fatal(err)
}

Then create a NewKeyedTransactor from the accounts/abi/bind package passing the private key.

auth := bind.NewKeyedTransactor(privateKey)

The next step is to create a genesis account and assign it an initial balance. We'll be using the GenesisAccount type from the core package.

balance := new(big.Int)
balance.SetString("10000000000000000000", 10) // 10 eth in wei

address := auth.From
genesisAlloc := map[common.Address]core.GenesisAccount{
  address: {
    Balance: balance,
  },
}

Now we pass the genesis allocation struct and a configured block gas limit to the NewSimulatedBackend method from the accounts/abi/bind/backends package which will return a new simulated ethereum client.

blockGasLimit := uint64(4712388)
client := backends.NewSimulatedBackend(genesisAlloc, blockGasLimit)

You can use this client as you'd normally would. As an example, we'll construct a new transaction and broadcast it.

fromAddress := auth.From
nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
if err != nil {
  log.Fatal(err)
}

value := big.NewInt(1000000000000000000) // in wei (1 eth)
gasLimit := uint64(21000)                // in units
gasPrice, err := client.SuggestGasPrice(context.Background())
if err != nil {
  log.Fatal(err)
}

toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")
var data []byte
tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, data)
signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, privateKey)
if err != nil {
  log.Fatal(err)
}

err = client.SendTransaction(context.Background(), signedTx)
if err != nil {
  log.Fatal(err)
}

fmt.Printf("tx sent: %s\n", signedTx.Hash().Hex()) // tx sent: 0xec3ceb05642c61d33fa6c951b54080d1953ac8227be81e7b5e4e2cfed69eeb51

By now you're probably wondering when will the transaction actually get mined. Well in order to "mine" it, there's one additional important thing you must do; call Commit on the client to commit a new mined block.

client.Commit()

Now you can fetch the transaction receipt and see that it was processed.

receipt, err := client.TransactionReceipt(context.Background(), signedTx.Hash())
if err != nil {
  log.Fatal(err)
}
if receipt == nil {
  log.Fatal("receipt is nil. Forgot to commit?")
}

fmt.Printf("status: %v\n", receipt.Status) // status: 1

So remember that the simulated client allows you to manually mine blocks at your command using the simulated client's Commit method.


Full code

client_simulated.go

package main

import (
    "context"
    "fmt"
    "log"
    "math/big"

    "github.com/ethereum/go-ethereum/accounts/abi/bind"
    "github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/core"
    "github.com/ethereum/go-ethereum/core/types"
    "github.com/ethereum/go-ethereum/crypto"
)

func main() {
    privateKey, err := crypto.GenerateKey()
    if err != nil {
        log.Fatal(err)
    }

    auth := bind.NewKeyedTransactor(privateKey)

    balance := new(big.Int)
    balance.SetString("10000000000000000000", 10) // 10 eth in wei

    address := auth.From
    genesisAlloc := map[common.Address]core.GenesisAccount{
        address: {
            Balance: balance,
        },
    }

    blockGasLimit := uint64(4712388)
    client := backends.NewSimulatedBackend(genesisAlloc, blockGasLimit)

    fromAddress := auth.From
    nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
    if err != nil {
        log.Fatal(err)
    }

    value := big.NewInt(1000000000000000000) // in wei (1 eth)
    gasLimit := uint64(21000)                // in units
    gasPrice, err := client.SuggestGasPrice(context.Background())
    if err != nil {
        log.Fatal(err)
    }

    toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")
    var data []byte
    tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, data)
    signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, privateKey)
    if err != nil {
        log.Fatal(err)
    }

    err = client.SendTransaction(context.Background(), signedTx)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("tx sent: %s\n", signedTx.Hash().Hex()) // tx sent: 0xec3ceb05642c61d33fa6c951b54080d1953ac8227be81e7b5e4e2cfed69eeb51

    client.Commit()

    receipt, err := client.TransactionReceipt(context.Background(), signedTx.Hash())
    if err != nil {
        log.Fatal(err)
    }
    if receipt == nil {
        log.Fatal("receipt is nil. Forgot to commit?")
    }

    fmt.Printf("status: %v\n", receipt.Status) // status: 1
}

results matching ""

    No results matching ""