2019-03-27 09:32:29 +01:00
|
|
|
---
|
2019-11-05 13:46:00 +01:00
|
|
|
title: Go Contract Bindings
|
2022-11-04 15:26:26 +00:00
|
|
|
sort_key: C
|
2019-03-27 09:32:29 +01:00
|
|
|
---
|
2019-11-05 13:46:00 +01:00
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
This page introduces the concept of server-side native dapps. Geth provides the tools required
|
|
|
|
to generate [Go][go-link] language bindings to any Ethereum contract that is compile-time type safe,
|
|
|
|
highly performant and can be generated completely automatically from a compiled contract.
|
|
|
|
|
|
|
|
Interacting with a contract on the Ethereum blockchain from Go is already possible via the
|
|
|
|
RPC interfaces exposed by Ethereum clients. However, writing the boilerplate code that
|
|
|
|
translates Go language constructs into RPC calls and back is time consuming and brittle -
|
|
|
|
implementation bugs can only be detected during runtime and it's almost impossible to evolve
|
|
|
|
a contract as even a tiny change in Solidity is awkward to port over to Go. Therefore,
|
|
|
|
Geth provides tools for easily converting contract code into Go code that can be used directly
|
|
|
|
in Go applications.
|
|
|
|
|
|
|
|
This page provides an introduction to generating Go contract bindings and using them in a simple
|
|
|
|
Go application.
|
|
|
|
|
|
|
|
{:toc}
|
|
|
|
|
|
|
|
- this will be removed by the toc
|
|
|
|
|
|
|
|
## Prerequisites
|
|
|
|
|
|
|
|
This page is fairly beginner-friendly and designed for people starting out with
|
|
|
|
writing Go native dapps. The core concepts will be introduced gradually as a developer
|
|
|
|
would encounter them. However, some basic familiarity with [Ethereum](https://ethereum.org),
|
|
|
|
[Solidity](https://docs.soliditylang.org/en/v0.8.15/) and [Go](https://go.dev/) is
|
|
|
|
assumed.
|
|
|
|
|
|
|
|
|
|
|
|
## What is an ABI?
|
|
|
|
|
|
|
|
Ethereum smart contracts have a schema that defines its functions and return types in the form
|
|
|
|
of a JSON file. This JSON file is known as an *Application Binary Interface*, or ABI. The ABI
|
|
|
|
acts as a specification for precisely how to encode data sent to a contract and how to
|
|
|
|
decode the data the contract sends back. The ABI is the only essential piece of information required to
|
|
|
|
generate Go bindings. Go developers can then use the bindings to interact with the contract
|
|
|
|
from their Go application without having to deal directly with data encoding and decoding.
|
|
|
|
An ABI is generated when a contract is compiled.
|
|
|
|
|
|
|
|
## Abigen: Go binding generator
|
|
|
|
|
|
|
|
Geth includes a source code generator called `abigen` that can convert Ethereum ABI definitions
|
|
|
|
into easy to use, type-safe Go packages. With a valid Go development environment
|
|
|
|
set up and the go-ethereum repository checked out correctly, `abigen` can be built as follows:
|
2019-03-27 09:32:29 +01:00
|
|
|
|
|
|
|
```
|
|
|
|
$ cd $GOPATH/src/github.com/ethereum/go-ethereum
|
2021-10-20 06:11:17 -07:00
|
|
|
$ go build ./cmd/abigen
|
2019-03-27 09:32:29 +01:00
|
|
|
```
|
|
|
|
|
|
|
|
### Generating the bindings
|
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
To demonstrate the binding generator a contract is required. The contract `Storage.sol` implements two
|
|
|
|
very simple functions: `store` updates a user-defined `uint256` to the contract's storage, and `retrieve`
|
|
|
|
displays the value stored in the contract to the user. The Solidity code is as follows:
|
|
|
|
|
|
|
|
```solidity
|
|
|
|
// SPDX-License-Identifier: GPL-3.0
|
|
|
|
|
|
|
|
pragma solidity >0.7.0 < 0.9.0;
|
|
|
|
/**
|
|
|
|
* @title Storage
|
|
|
|
* @dev store or retrieve variable value
|
|
|
|
*/
|
|
|
|
|
|
|
|
contract Storage {
|
|
|
|
|
|
|
|
uint256 value;
|
|
|
|
|
|
|
|
function store(uint256 number) public{
|
|
|
|
value = number;
|
|
|
|
}
|
|
|
|
|
|
|
|
function retrieve() public view returns (uint256){
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
This contract can be pasted into a text file and saved as `Storage.sol`.
|
|
|
|
|
|
|
|
The following code snippet shows how an ABI can be generated for `Storage.sol`
|
|
|
|
using the Solidity compiler `solc`.
|
|
|
|
|
|
|
|
```shell
|
|
|
|
solc --abi Storage.sol -o build
|
|
|
|
```
|
|
|
|
|
|
|
|
The ABI can also be generated in other ways such as using the `compile` commands in development
|
|
|
|
frameworks such as [Truffle][truffle-link], [Hardhat][hardhat-link] and [Brownie][brownie-link]
|
|
|
|
or in the online IDE [Remix][remix-link]. ABIs for existing
|
|
|
|
verified contracts can be downloaded from [Etherscan](etherscan.io).
|
|
|
|
|
|
|
|
|
|
|
|
The ABI for `Storage.sol` (`Storage.abi`) looks as follows:
|
2019-03-27 09:32:29 +01:00
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
```json
|
|
|
|
[{"inputs":[],"name":"retrieve","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"number","type":"uint256"}],"name":"store","outputs":[],"stateMutability":"nonpayable","type":"function"}]
|
|
|
|
```
|
|
|
|
|
|
|
|
The contract binding can then be generated by passing the ABI to `abigen` as follows:
|
2019-03-27 09:32:29 +01:00
|
|
|
|
|
|
|
```
|
2022-07-04 13:27:56 +01:00
|
|
|
$ abigen --abi Storage.abi --pkg main --type Storage --out Storage.go
|
2019-03-27 09:32:29 +01:00
|
|
|
```
|
|
|
|
|
|
|
|
Where the flags are:
|
|
|
|
|
|
|
|
* `--abi`: Mandatory path to the contract ABI to bind to
|
2021-02-01 21:40:27 +08:00
|
|
|
* `--pkg`: Mandatory Go package name to place the Go code into
|
2019-03-27 09:32:29 +01:00
|
|
|
* `--type`: Optional Go type name to assign to the binding struct
|
|
|
|
* `--out`: Optional output path for the generated Go source file (not set = stdout)
|
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
This will generate a type-safe Go binding for the Storage contract. The generated code will
|
|
|
|
look something like the snippet below, the full version of which can be viewed
|
|
|
|
[here](https://gist.github.com/jmcook1186/a78e59d203bb54b06e1b81f2cda79d93).
|
2019-03-27 09:32:29 +01:00
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
```go
|
|
|
|
// Code generated - DO NOT EDIT.
|
|
|
|
// This file is a generated binding and any manual changes will be lost.
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"math/big"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
ethereum "github.com/ethereum/go-ethereum"
|
|
|
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
|
|
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
|
|
"github.com/ethereum/go-ethereum/event"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Reference imports to suppress errors if they are not otherwise used.
|
|
|
|
var (
|
|
|
|
_ = errors.New
|
|
|
|
_ = big.NewInt
|
|
|
|
_ = strings.NewReader
|
|
|
|
_ = ethereum.NotFound
|
|
|
|
_ = bind.Bind
|
|
|
|
_ = common.Big1
|
|
|
|
_ = types.BloomLookup
|
|
|
|
_ = event.NewSubscription
|
|
|
|
)
|
|
|
|
|
|
|
|
// StorageMetaData contains all meta data concerning the Storage contract.
|
|
|
|
var StorageMetaData = &bind.MetaData{
|
|
|
|
ABI: "[{\"inputs\":[],\"name\":\"retrieve\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"number\",\"type\":\"uint256\"}],\"name\":\"store\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]",
|
|
|
|
}
|
|
|
|
|
|
|
|
// StorageABI is the input ABI used to generate the binding from.
|
|
|
|
// Deprecated: Use StorageMetaData.ABI instead.
|
|
|
|
var StorageABI = StorageMetaData.ABI
|
|
|
|
|
|
|
|
// Storage is an auto generated Go binding around an Ethereum contract.
|
|
|
|
type Storage struct {
|
|
|
|
StorageCaller // Read-only binding to the contract
|
|
|
|
StorageTransactor // Write-only binding to the contract
|
|
|
|
StorageFilterer // Log filterer for contract events
|
|
|
|
}
|
|
|
|
...
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
`Storage.go` contains all the bindings required to interact with `Storage.sol` from a Go application.
|
|
|
|
However, this isn't very useful unless the contract is actually deployed on Ethereum or one of
|
|
|
|
Ethereum's testnets. The following sections will demonstrate how to deploy the contract to
|
|
|
|
an Ethereum testnet and interact with it using the Go bindings.
|
|
|
|
|
|
|
|
### Deploying contracts to Ethereum
|
|
|
|
|
|
|
|
In the previous section, the contract ABI was sufficient for generating the contract bindings from its ABI.
|
|
|
|
However, deploying the contract requires some additional information in the form of the compiled
|
|
|
|
bytecode.
|
|
|
|
|
|
|
|
The bytecode is obtained by running the compiler again but this passing the `--bin` flag, e.g.
|
2019-03-27 09:32:29 +01:00
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
```shell
|
|
|
|
solc --bin Storage.sol -o Storage.bin
|
|
|
|
```
|
|
|
|
|
|
|
|
Then `abigen` can be run again, this time passing `Storage.bin`:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
$ abigen --abi Storage.abi --pkg main --type Storage --out Storage.go --bin Storage.bin
|
|
|
|
```
|
|
|
|
|
|
|
|
This will generate something similar to the bindings generated in the previous section. However,
|
|
|
|
an additional `DeployStorage` function has been injected:
|
|
|
|
|
|
|
|
```go
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
```
|
|
|
|
View the full file [here](https://gist.github.com/jmcook1186/91124cfcbc7f22dcd3bb4f148d2868a8).
|
|
|
|
|
|
|
|
The new `DeployStorage()` function can be used to deploy the contract to an Ethereum testnet from a Go application. To do this
|
|
|
|
requires incorporating the bindings into a Go application that also handles account management, authorization and Ethereum backend
|
|
|
|
to deploy the contract through. Specifically, this requires:
|
2019-03-27 09:32:29 +01:00
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
1. A running Geth node connected to an Ethereum testnet (recommended Goerli)
|
|
|
|
2. An account in the keystore prefunded with enough ETH to cover gas costs for deploying and interacting with the contract
|
2019-03-27 09:32:29 +01:00
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
Assuming these prerequisites exist, a new `ethclient` can be instantiated with the local Geth node's ipc file, providing
|
|
|
|
access to the testnet from the Go application. The key can be instantiated as a variable in the application by copying the
|
|
|
|
JSON object from the keyfile in the keystore.
|
|
|
|
|
|
|
|
Putting it all together would result in:
|
2019-03-27 09:32:29 +01:00
|
|
|
|
|
|
|
```go
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"log"
|
2022-07-04 13:27:56 +01:00
|
|
|
"math/big"
|
|
|
|
"strings"
|
|
|
|
"time"
|
2019-03-27 09:32:29 +01:00
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
2019-03-27 09:32:29 +01:00
|
|
|
"github.com/ethereum/go-ethereum/ethclient"
|
2022-07-04 13:27:56 +01:00
|
|
|
|
2019-03-27 09:32:29 +01:00
|
|
|
)
|
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
const key = `<<json object from keystore>>`
|
|
|
|
|
2019-03-27 09:32:29 +01:00
|
|
|
func main() {
|
2022-07-04 13:27:56 +01:00
|
|
|
// Create an IPC based RPC connection to a remote node and an authorized transactor
|
|
|
|
conn, err := rpc.NewIPCClient("/home/go-ethereum/goerli/geth.ipc")
|
2019-03-27 09:32:29 +01:00
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Failed to connect to the Ethereum client: %v", err)
|
|
|
|
}
|
2022-07-04 13:27:56 +01:00
|
|
|
auth, err := bind.NewTransactor(strings.NewReader(key), "<<strong_password>>")
|
2019-03-27 09:32:29 +01:00
|
|
|
if err != nil {
|
2022-07-04 13:27:56 +01:00
|
|
|
log.Fatalf("Failed to create authorized transactor: %v", err)
|
2019-03-27 09:32:29 +01:00
|
|
|
}
|
2022-07-04 13:27:56 +01:00
|
|
|
// Deploy the contract passing the newly created `auth` and `conn` vars
|
|
|
|
address, tx, instance, err := DeployStorage(auth, conn), new(big.Int), "Storage contract in Go!", 0, "Go!")
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Failed to deploy new storage contract: %v", err)
|
|
|
|
}
|
|
|
|
fmt.Printf("Contract pending deploy: 0x%x\n", address)
|
|
|
|
fmt.Printf("Transaction waiting to be mined: 0x%x\n\n", tx.Hash())
|
|
|
|
|
|
|
|
time.Sleep(250 * time.Millisecond) // Allow it to be processed by the local node :P
|
|
|
|
|
|
|
|
// function call on `instance`. Retrieves pending name
|
|
|
|
name, err := instance.Name(&bind.CallOpts{Pending: true})
|
2019-03-27 09:32:29 +01:00
|
|
|
if err != nil {
|
2022-07-04 13:27:56 +01:00
|
|
|
log.Fatalf("Failed to retrieve pending name: %v", err)
|
2019-03-27 09:32:29 +01:00
|
|
|
}
|
2022-07-04 13:27:56 +01:00
|
|
|
fmt.Println("Pending name:", name)
|
2019-03-27 09:32:29 +01:00
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
Running this code requests the creation of a brand new `Storage` contract on the Goerli blockchain.
|
|
|
|
The contract functions can be called while the contract is waiting to be mined.
|
2019-03-27 09:32:29 +01:00
|
|
|
|
|
|
|
```
|
2022-07-04 13:27:56 +01:00
|
|
|
Contract pending deploy: 0x46506d900559ad005feb4645dcbb2dbbf65e19cc
|
|
|
|
Transaction waiting to be mined: 0x6a81231874edd2461879b7280ddde1a857162a744e3658ca7ec276984802183b
|
|
|
|
|
|
|
|
Pending name: Storage contract in Go!
|
2019-03-27 09:32:29 +01:00
|
|
|
```
|
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
Once mined, the contract exists permanently at its deployment address and can now be interacted with
|
|
|
|
from other applications without ever needing to be redeployed.
|
2019-03-27 09:32:29 +01:00
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
Note that `DeployStorage` returns four variables:
|
2019-03-27 09:32:29 +01:00
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
- `address`: the deployment address of the contract
|
|
|
|
|
|
|
|
- `tx`: the transaction hash that can be queried using Geth or a service like [Etherscan](etherscan.io)
|
|
|
|
|
|
|
|
- `instance`: an instance of the deployed contract whose functions can be called in the Go application
|
|
|
|
|
|
|
|
- `err`: a variable that handles errors in case of a deployment failure
|
|
|
|
|
2019-03-27 09:32:29 +01:00
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
### Accessing an Ethereum contract
|
2019-03-27 09:32:29 +01:00
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
To interact with a contract already deployed on the blockchain, the deployment `address` is required and
|
|
|
|
a `backend` through which to access Ethereum must be defined. The binding generator provides an RPC
|
|
|
|
backend out-of-the-box that can be used to attach to an existing Ethereum node via IPC, HTTP or WebSockets.
|
|
|
|
|
|
|
|
As in the previous section, a Geth node running on an Ethereum testnet (recommend Goerli) and an account
|
|
|
|
with some test ETH to cover gas is required. The `Storage.sol` deployment address is also needed.
|
|
|
|
|
|
|
|
Again, an instance of `ethclient` can be created, passing the path to Geth's ipc file. In the example
|
|
|
|
below this backend is assigned to the variable `conn`.
|
|
|
|
|
|
|
|
```go
|
|
|
|
// Create an IPC based RPC connection to a remote node
|
|
|
|
// NOTE update the path to the ipc file!
|
|
|
|
conn, err := ethclient.Dial("/home/go-ethereum/goerli/geth.ipc")
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Failed to connect to the Ethereum client: %v", err)
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
The functions available for interacting with the `Storage` contract are defined in `Storage.go`. To create
|
|
|
|
a new instance of the contract in a Go application, the `NewStorage()` function can be used. The function
|
|
|
|
is defined in `Storage.go` as follows:
|
|
|
|
|
|
|
|
```go
|
|
|
|
// NewStorage creates a new instance of Storage, bound to a specific deployed contract.
|
|
|
|
func NewStorage(address common.Address, backend bind.ContractBackend) (*Storage, error) {
|
|
|
|
contract, err := bindStorage(address, backend, backend, backend)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &Storage{StorageCaller: StorageCaller{contract: contract}, StorageTransactor: StorageTransactor{contract: contract}, StorageFilterer: StorageFilterer{contract: contract}}, nil
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
`NewStorage()` takes two arguments: the deployment address and a backend (`conn`) and returns
|
|
|
|
an instance of the deployed contract. In the example below, the instance is assigned to `store`.
|
2019-03-27 09:32:29 +01:00
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
|
|
"github.com/ethereum/go-ethereum/ethclient"
|
|
|
|
)
|
|
|
|
|
|
|
|
func main() {
|
2022-07-04 13:27:56 +01:00
|
|
|
// Create an IPC based RPC connection to a remote node
|
|
|
|
// NOTE update the path to the ipc file!
|
|
|
|
conn, err := ethclient.Dial("/home/go-ethereum/goerli/geth.ipc")
|
2019-03-27 09:32:29 +01:00
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Failed to connect to the Ethereum client: %v", err)
|
|
|
|
}
|
2022-07-04 13:27:56 +01:00
|
|
|
// Instantiate the contract and display its name
|
|
|
|
// NOTE update the deployment address!
|
|
|
|
store, err := NewStorage(common.HexToAddress("0x21e6fc92f93c8a1bb41e2be64b4e1f88a54d3576"), conn)
|
2019-03-27 09:32:29 +01:00
|
|
|
if err != nil {
|
2022-07-04 13:27:56 +01:00
|
|
|
log.Fatalf("Failed to instantiate Storage contract: %v", err)
|
2019-03-27 09:32:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
```
|
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
The contract instance is then available to interact with in the Go application. To read a value from
|
|
|
|
the blockchain, for example the `value` stored in the contract, the contract's `Retrieve()` function
|
|
|
|
can be called. Again, the function is defined in `Storage.go` as follows:
|
2019-03-27 09:32:29 +01:00
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
```go
|
|
|
|
// Retrieve is a free data retrieval call binding the contract method 0x2e64cec1.
|
|
|
|
//
|
|
|
|
// Solidity: function retrieve() view returns(uint256)
|
|
|
|
func (_Storage *StorageCaller) Retrieve(opts *bind.CallOpts) (*big.Int, error) {
|
|
|
|
var out []interface{}
|
|
|
|
err := _Storage.contract.Call(opts, &out, "retrieve")
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return *new(*big.Int), err
|
|
|
|
}
|
2019-03-27 09:32:29 +01:00
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int)
|
2019-03-27 09:32:29 +01:00
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
return out0, err
|
2019-03-27 09:32:29 +01:00
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
}
|
|
|
|
```
|
2019-03-27 09:32:29 +01:00
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
Note that the `Retrieve()` function requires a parameter to be passed, even though the
|
|
|
|
original Solidity contract didn't require any at all none. The parameter required is
|
|
|
|
a `*bind.CallOpts` type, which can be used to fine tune the call. If no adjustments to the
|
|
|
|
call are required, pass `nil`. Adjustments to the call include:
|
2019-03-27 09:32:29 +01:00
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
* `Pending`: Whether to access pending contract state or the current stable one
|
|
|
|
* `GasLimit`: Place a limit on the computing resources the call might consume
|
2019-03-27 09:32:29 +01:00
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
So to call the `Retrieve()` function in the Go application:
|
2019-03-27 09:32:29 +01:00
|
|
|
|
|
|
|
```go
|
2022-07-04 13:27:56 +01:00
|
|
|
value, err := store.Retrieve(nil)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Failed to retrieve value: %v", err)
|
|
|
|
}
|
|
|
|
fmt.Println("Value: ", value)
|
2019-03-27 09:32:29 +01:00
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
The output will be something like:
|
2019-03-27 09:32:29 +01:00
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
```terminal
|
|
|
|
Value: 56
|
|
|
|
```
|
2019-03-27 09:32:29 +01:00
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
### Transacting with an Ethereum contract
|
2019-03-27 09:32:29 +01:00
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
Invoking a method that changes contract state (i.e. transacting) is a bit more involved,
|
|
|
|
as a live transaction needs to be authorized and broadcast into the network. **Go bindings
|
|
|
|
require local signing of transactions and do not delegate this to a remote node.** This is
|
|
|
|
to keep accounts private within dapps, and not shared (by default) between them.
|
2019-03-27 09:32:29 +01:00
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
Thus to allow transacting with a contract, your code needs to implement a method that
|
|
|
|
given an input transaction, signs it and returns an authorized output transaction. Since
|
|
|
|
most users have their keys in the [Web3 Secret Storage][web3-ss-link] format, the `bind`
|
|
|
|
package contains a small utility method (`bind.NewTransactor(keyjson, passphrase)`) that can
|
|
|
|
create an authorized transactor from a key file and associated password, without the user
|
|
|
|
needing to implement key signing themselves.
|
2019-03-27 09:32:29 +01:00
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
Changing the previous code snippet to update the value stored in the contract:
|
2019-03-27 09:32:29 +01:00
|
|
|
|
|
|
|
```go
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"math/big"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
2022-07-04 13:27:56 +01:00
|
|
|
"github.com/ethereum/go-ethereum/common"
|
2019-03-27 09:32:29 +01:00
|
|
|
"github.com/ethereum/go-ethereum/ethclient"
|
|
|
|
)
|
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
const key = `json object from keystore`
|
2019-03-27 09:32:29 +01:00
|
|
|
|
|
|
|
func main() {
|
2022-07-04 13:27:56 +01:00
|
|
|
// Create an IPC based RPC connection to a remote node and instantiate a contract binding
|
|
|
|
conn, err := ethclient.Dial("/home/go-ethereum/goerli/geth.ipc")
|
2019-03-27 09:32:29 +01:00
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Failed to connect to the Ethereum client: %v", err)
|
|
|
|
}
|
2022-07-04 13:27:56 +01:00
|
|
|
store, err := NewStorage(common.HexToAddress("0x21e6fc92f93c8a1bb41e2be64b4e1f88a54d3576"), conn)
|
2019-03-27 09:32:29 +01:00
|
|
|
if err != nil {
|
2022-07-04 13:27:56 +01:00
|
|
|
log.Fatalf("Failed to instantiate a Storage contract: %v", err)
|
2019-03-27 09:32:29 +01:00
|
|
|
}
|
2022-07-04 13:27:56 +01:00
|
|
|
// Create an authorized transactor and call the store function
|
|
|
|
auth, err := bind.NewStorageTransactor(strings.NewReader(key), "strong_password")
|
2019-03-27 09:32:29 +01:00
|
|
|
if err != nil {
|
2022-07-04 13:27:56 +01:00
|
|
|
log.Fatalf("Failed to create authorized transactor: %v", err)
|
2019-03-27 09:32:29 +01:00
|
|
|
}
|
2022-07-04 13:27:56 +01:00
|
|
|
// Call the store() function
|
|
|
|
tx, err := store.Store(auth, big.NewInt(420))
|
2019-03-27 09:32:29 +01:00
|
|
|
if err != nil {
|
2022-07-04 13:27:56 +01:00
|
|
|
log.Fatalf("Failed to update value: %v", err)
|
2019-03-27 09:32:29 +01:00
|
|
|
}
|
2022-07-04 13:27:56 +01:00
|
|
|
fmt.Printf("Update pending: 0x%x\n", tx.Hash())
|
2019-03-27 09:32:29 +01:00
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
And the output:
|
2019-03-27 09:32:29 +01:00
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
```terminal
|
|
|
|
Update pending: 0x4f4aaeb29ed48e88dd653a81f0b05d4df64a86c99d4e83b5bfeb0f0006b0e55b
|
2019-03-27 09:32:29 +01:00
|
|
|
```
|
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
Similar to the method invocations in the previous section which only read contract state,
|
|
|
|
transacting methods also require a mandatory first parameter, a `*bind.TransactOpts` type,
|
|
|
|
which authorizes the transaction and potentially fine tunes it:
|
|
|
|
|
|
|
|
* `From`: Address of the account to invoke the method with (mandatory)
|
|
|
|
* `Signer`: Method to sign a transaction locally before broadcasting it (mandatory)
|
|
|
|
* `Nonce`: Account nonce to use for the transaction ordering (optional)
|
|
|
|
* `GasLimit`: Place a limit on the computing resources the call might consume (optional)
|
|
|
|
* `GasPrice`: Explicitly set the gas price to run the transaction with (optional)
|
|
|
|
* `Value`: Any funds to transfer along with the method call (optional)
|
|
|
|
|
|
|
|
The two mandatory fields are automatically set by the `bind` package if the auth options are
|
|
|
|
constructed using `bind.NewTransactor`. The nonce and gas related fields are automatically
|
|
|
|
derived by the binding if they are not set. Unset values are assumed to be zero.
|
|
|
|
|
|
|
|
|
|
|
|
### Pre-configured contract sessions
|
|
|
|
|
|
|
|
Reading and state modifying contract-calls require a mandatory first parameter which can
|
|
|
|
authorize and fine tune some of the internal parameters. However, most of the time the
|
|
|
|
same accounts and parameters will be used to issue many transactions, so constructing
|
|
|
|
the call/transact options individually quickly becomes unwieldy.
|
|
|
|
|
|
|
|
To avoid this, the generator also creates specialized wrappers that can be pre-configured with
|
|
|
|
tuning and authorization parameters, allowing all the Solidity defined methods to be invoked
|
|
|
|
without needing an extra parameter.
|
|
|
|
|
|
|
|
These are named similarly to the original contract type name but suffixed with `Sessions`:
|
|
|
|
|
|
|
|
```go
|
|
|
|
// Wrap the Storage contract instance into a session
|
|
|
|
session := &StorageSession{
|
|
|
|
Contract: store,
|
|
|
|
CallOpts: bind.CallOpts{
|
|
|
|
Pending: true,
|
|
|
|
},
|
|
|
|
TransactOpts: bind.TransactOpts{
|
|
|
|
From: auth.From,
|
|
|
|
Signer: auth.Signer,
|
|
|
|
GasLimit: big.NewInt(3141592),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
// Call the previous methods without the option parameters
|
|
|
|
session.Store(big.NewInt(69))
|
2019-03-27 09:32:29 +01:00
|
|
|
```
|
|
|
|
|
|
|
|
## Bind Solidity directly
|
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
In the past, abigen allowed compilation and binding of a Solidity source file directly to a Go package in a single step.
|
2022-06-01 15:29:45 -05:00
|
|
|
This feature has been discontinued from [v1.10.18](https://github.com/ethereum/go-ethereum/releases/tag/v1.10.18)
|
2022-07-04 13:27:56 +01:00
|
|
|
onwards due to maintenance synchronization challenges with the compiler in Geth.
|
2022-06-02 10:09:38 -05:00
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
The compilation and binding steps can be joined together into a pipeline, for example:
|
2022-06-23 11:22:49 +08:00
|
|
|
```
|
2022-07-04 13:27:56 +01:00
|
|
|
solc Storage.sol --combined-json abi,bin | abigen --pkg main --type storage --out Storage.go --combined-json -
|
2022-06-23 11:22:49 +08:00
|
|
|
```
|
2022-06-02 10:09:38 -05:00
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
### Project integration (`go generate`)
|
2019-03-27 09:32:29 +01:00
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
The `abigen` command was made in such a way as to integrate easily into existing
|
2019-03-27 09:32:29 +01:00
|
|
|
Go toolchains: instead of having to remember the exact command needed to bind an Ethereum
|
2022-07-04 13:27:56 +01:00
|
|
|
contract into a Go project, `go generate` can handle all the fine details.
|
2019-03-27 09:32:29 +01:00
|
|
|
|
|
|
|
Place the binding generation command into a Go source file before the package definition:
|
|
|
|
|
|
|
|
```
|
2022-07-04 13:27:56 +01:00
|
|
|
//go:generate abigen --sol Storage.sol --pkg main --out Storage.go
|
2019-03-27 09:32:29 +01:00
|
|
|
```
|
|
|
|
|
|
|
|
After which whenever the Solidity contract is modified, instead of needing to remember and
|
|
|
|
run the above command, we can simply call `go generate` on the package (or even the entire
|
|
|
|
source tree via `go generate ./...`), and it will correctly generate the new bindings for us.
|
|
|
|
|
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
## Blockchain simulator
|
2019-03-27 09:32:29 +01:00
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
Being able to deploy and access deployed Ethereum contracts from native Go code is a powerful
|
|
|
|
feature. However, using public testnets as a backend does not lend itself well to
|
|
|
|
*automated unit testing*. Therefore, Geth also implements a *simulated blockchain*
|
|
|
|
that can be set as a backend to native contracts the same way as a live RPC backend, using the
|
|
|
|
command `backends.NewSimulatedBackend(genesisAccounts)`. The code snippet below shows how this
|
2022-10-02 13:38:45 -04:00
|
|
|
can be used as a backend in a Go application.
|
2019-03-27 09:32:29 +01:00
|
|
|
|
|
|
|
```go
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"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/core"
|
|
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
|
|
)
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
// Generate a new random account and a funded simulator
|
|
|
|
key, _ := crypto.GenerateKey()
|
|
|
|
auth := bind.NewKeyedTransactor(key)
|
|
|
|
|
|
|
|
sim := backends.NewSimulatedBackend(core.GenesisAccount{Address: auth.From, Balance: big.NewInt(10000000000)})
|
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
// instantiate contract
|
|
|
|
store, err := NewStorage(common.HexToAddress("0x21e6fc92f93c8a1bb41e2be64b4e1f88a54d3576"), sim)
|
2019-03-27 09:32:29 +01:00
|
|
|
if err != nil {
|
2022-07-04 13:27:56 +01:00
|
|
|
log.Fatalf("Failed to instantiate a Storage contract: %v", err)
|
2019-03-27 09:32:29 +01:00
|
|
|
}
|
2022-07-04 13:27:56 +01:00
|
|
|
// Create an authorized transactor and call the store function
|
|
|
|
auth, err := bind.NewStorageTransactor(strings.NewReader(key), "strong_password")
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Failed to create authorized transactor: %v", err)
|
|
|
|
}
|
|
|
|
// Call the store() function
|
|
|
|
tx, err := store.Store(auth, big.NewInt(420))
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Failed to update value: %v", err)
|
|
|
|
}
|
|
|
|
fmt.Printf("Update pending: 0x%x\n", tx.Hash())
|
|
|
|
}
|
|
|
|
```
|
2019-03-27 09:32:29 +01:00
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
Note, that it is not necessary to wait for a local private chain miner, or testnet miner to
|
|
|
|
integrate the currently pending transactions. To mine the next block, simply `Commit()` the simulator.
|
2019-03-27 09:32:29 +01:00
|
|
|
|
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
## Summary
|
2019-03-27 09:32:29 +01:00
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
To make interacting with Ethereum contracts easier for Go developers, Geth provides tools that generate
|
|
|
|
contract bindings automatically. This makes contract functions available in Go native applications.
|
2019-03-27 09:32:29 +01:00
|
|
|
|
|
|
|
|
2022-07-04 13:27:56 +01:00
|
|
|
[go-link]:https://github.com/golang/go/wiki#getting-started-with-go
|
|
|
|
[truffle-link]:https://trufflesuite.com/docs/truffle/
|
|
|
|
[hardhat-link]:https://hardhat.org/
|
|
|
|
[brownie-link]:https://eth-brownie.readthedocs.io/en/stable/
|
|
|
|
[remix-link]:https://remix.ethereum.org/
|
2022-10-02 13:38:45 -04:00
|
|
|
[web3-ss-link]:https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition
|