docs: update Go contract bindings page (#25177)
initial commit for bindings page rework up to chain simulator finish page draft
This commit is contained in:
parent
56afb17fab
commit
b222b2e507
|
@ -3,52 +3,49 @@ title: Go Contract Bindings
|
||||||
sort_key: E
|
sort_key: E
|
||||||
---
|
---
|
||||||
|
|
||||||
**[Please note, events are not yet implemented as they need some RPC subscription
|
This page introduces the concept of server-side native dapps. Geth provides the tools required
|
||||||
features that are still under review.]**
|
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.
|
||||||
|
|
||||||
The original roadmap and/or dream of the Ethereum platform was to provide a solid, high
|
Interacting with a contract on the Ethereum blockchain from Go is already possible via the
|
||||||
performing client implementation of the consensus protocol in various languages, which
|
RPC interfaces exposed by Ethereum clients. However, writing the boilerplate code that
|
||||||
would provide an RPC interface for JavaScript DApps to communicate with, pushing towards
|
translates Go language constructs into RPC calls and back is time consuming and brittle -
|
||||||
the direction of the Mist browser, through which users can interact with the blockchain.
|
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.
|
||||||
|
|
||||||
Although this was a solid plan for mainstream adoption and does cover quite a lot of use
|
This page provides an introduction to generating Go contract bindings and using them in a simple
|
||||||
cases that people come up with (mostly where people manually interact with the blockchain),
|
Go application.
|
||||||
it excludes the server side (backend, fully automated, devops) use cases where JavaScript is
|
|
||||||
usually not the language of choice given its dynamic nature.
|
|
||||||
|
|
||||||
This page introduces the concept of server side native Dapps: Go language bindings to any
|
{:toc}
|
||||||
Ethereum contract that is compile time type safe, highly performant and best of all, can
|
|
||||||
be generated fully automatically from a contract ABI and optionally the EVM bytecode.
|
|
||||||
|
|
||||||
*This page is written in a more beginner friendly tutorial style to make it easier for
|
- this will be removed by the toc
|
||||||
people to start out with writing Go native Dapps. The used concepts will be introduced
|
|
||||||
gradually as a developer would need/encounter them. However, we do assume the reader
|
|
||||||
is familiar with Ethereum in general, has a fair understanding of Solidity and can code
|
|
||||||
Go.*
|
|
||||||
|
|
||||||
## Token contract
|
## Prerequisites
|
||||||
|
|
||||||
To avoid falling into the fallacy of useless academic examples, we're going to take the
|
This page is fairly beginner-friendly and designed for people starting out with
|
||||||
official Token contract as the base for introducing the Go
|
writing Go native dapps. The core concepts will be introduced gradually as a developer
|
||||||
native bindings. If you're unfamiliar with the contract, skimming the linked page should
|
would encounter them. However, some basic familiarity with [Ethereum](https://ethereum.org),
|
||||||
probably be enough, the details aren't relevant for now. *In short the contract implements
|
[Solidity](https://docs.soliditylang.org/en/v0.8.15/) and [Go](https://go.dev/) is
|
||||||
a custom token that can be deployed on top of Ethereum.* To make sure this tutorial doesn't
|
assumed.
|
||||||
go stale if the linked website changes, the Solidity source code of the Token contract is
|
|
||||||
also available at [`token.sol`](https://gist.github.com/karalabe/08f4b780e01c8452d989).
|
|
||||||
|
|
||||||
### Go binding generator
|
|
||||||
|
|
||||||
Interacting with a contract on the Ethereum blockchain from Go (or any other language for
|
## What is an ABI?
|
||||||
a matter of fact) is already possible via the RPC interfaces exposed by Ethereum clients.
|
|
||||||
However, writing the boilerplate code that translates decent Go language constructs into
|
|
||||||
RPC calls and back is extremely time consuming and also extremely 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 can be painful to port over to Go.
|
|
||||||
|
|
||||||
To avoid all this mess, the go-ethereum implementation introduces a source code generator
|
Ethereum smart contracts have a schema that defines its functions and return types in the form
|
||||||
that can convert Ethereum ABI definitions into easy to use, type-safe Go packages. Assuming
|
of a JSON file. This JSON file is known as an *Application Binary Interface*, or ABI. The ABI
|
||||||
you have a valid Go development environment set up and the go-ethereum
|
acts as a specification for precisely how to encode data sent to a contract and how to
|
||||||
repository checked out correctly, you can build the generator with:
|
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:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ cd $GOPATH/src/github.com/ethereum/go-ethereum
|
$ cd $GOPATH/src/github.com/ethereum/go-ethereum
|
||||||
|
@ -57,14 +54,58 @@ $ go build ./cmd/abigen
|
||||||
|
|
||||||
### Generating the bindings
|
### Generating the bindings
|
||||||
|
|
||||||
The single essential thing needed to generate a Go binding to an Ethereum contract is the
|
To demonstrate the binding generator a contract is required. The contract `Storage.sol` implements two
|
||||||
contract's ABI definition `JSON` file. For our `Token` contract tutorial you can obtain this
|
very simple functions: `store` updates a user-defined `uint256` to the contract's storage, and `retrieve`
|
||||||
either by compiling the Solidity code yourself (e.g. via @chriseth's [online Solidity compiler](https://chriseth.github.io/browser-solidity/)), or you can download our pre-compiled [`token.abi`](https://gist.github.com/karalabe/b8dfdb6d301660f56c1b).
|
displays the value stored in the contract to the user. The Solidity code is as follows:
|
||||||
|
|
||||||
To generate a binding, simply call:
|
```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:
|
||||||
|
|
||||||
|
```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:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ abigen --abi token.abi --pkg main --type Token --out token.go
|
$ abigen --abi Storage.abi --pkg main --type Storage --out Storage.go
|
||||||
```
|
```
|
||||||
|
|
||||||
Where the flags are:
|
Where the flags are:
|
||||||
|
@ -74,203 +115,117 @@ Where the flags are:
|
||||||
* `--type`: Optional Go type name to assign to the binding struct
|
* `--type`: Optional Go type name to assign to the binding struct
|
||||||
* `--out`: Optional output path for the generated Go source file (not set = stdout)
|
* `--out`: Optional output path for the generated Go source file (not set = stdout)
|
||||||
|
|
||||||
This will generate a type-safe Go binding for the Token contract. The generated code will
|
This will generate a type-safe Go binding for the Storage contract. The generated code will
|
||||||
look something like [`token.go`](https://gist.github.com/karalabe/5839509295afa4f7e2215bc4116c7a8f),
|
look something like the snippet below, the full version of which can be viewed
|
||||||
but please generate your own as this will change as more work is put into the generator.
|
[here](https://gist.github.com/jmcook1186/a78e59d203bb54b06e1b81f2cda79d93).
|
||||||
|
|
||||||
### Accessing an Ethereum contract
|
|
||||||
|
|
||||||
To interact with a contract deployed on the blockchain, you'll need to know the `address`
|
|
||||||
of the contract itself, and need to specify a `backend` through which to access Ethereum.
|
|
||||||
The binding generator provides out of the box an RPC backend through which you can attach
|
|
||||||
to an existing Ethereum node via IPC, HTTP or WebSockets.
|
|
||||||
|
|
||||||
We'll use the foundation's Unicorn token contract deployed
|
|
||||||
on the testnet to demonstrate calling contract methods. It is deployed at the address
|
|
||||||
`0x21e6fc92f93c8a1bb41e2be64b4e1f88a54d3576`.
|
|
||||||
|
|
||||||
To run the snippet below, please ensure a Geth instance is running and attached to the
|
|
||||||
Morden test network where the above mentioned contract was deployed. Also please update
|
|
||||||
the path to the IPC socket below to the one reported by your own local Geth node.
|
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
// Code generated - DO NOT EDIT.
|
||||||
|
// This file is a generated binding and any manual changes will be lost.
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"errors"
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/ethclient"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// Create an IPC based RPC connection to a remote node
|
|
||||||
conn, err := ethclient.Dial("/home/karalabe/.ethereum/testnet/geth.ipc")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to connect to the Ethereum client: %v", err)
|
|
||||||
}
|
|
||||||
// Instantiate the contract and display its name
|
|
||||||
token, err := NewToken(common.HexToAddress("0x21e6fc92f93c8a1bb41e2be64b4e1f88a54d3576"), conn)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to instantiate a Token contract: %v", err)
|
|
||||||
}
|
|
||||||
name, err := token.Name(nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to retrieve token name: %v", err)
|
|
||||||
}
|
|
||||||
fmt.Println("Token name:", name)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
And the output (yay):
|
|
||||||
|
|
||||||
```
|
|
||||||
Token name: Testnet Unicorn
|
|
||||||
```
|
|
||||||
|
|
||||||
If you look at the method invoked to read the token name `token.Name(nil)`, it required
|
|
||||||
a parameter to be passed, even though the original Solidity contract requires none. This
|
|
||||||
is a `*bind.CallOpts` type, which can be used to fine tune the call.
|
|
||||||
|
|
||||||
* `Pending`: Whether to access pending contract state or the current stable one
|
|
||||||
* `GasLimit`: Place a limit on the computing resources the call might consume
|
|
||||||
|
|
||||||
### Transacting with an Ethereum contract
|
|
||||||
|
|
||||||
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. **Opposed
|
|
||||||
to the conventional way of storing accounts and keys in the node we attach to, Go bindings
|
|
||||||
require signing transactions locally and do not delegate this to a remote node.** This is
|
|
||||||
done so to facilitate the general direction of the Ethereum community where accounts are
|
|
||||||
kept private to DApps, and not shared (by default) between them.
|
|
||||||
|
|
||||||
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](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) 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 himself.
|
|
||||||
|
|
||||||
Changing the previous code snippet to send one unicorn to the zero address:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"strings"
|
"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/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/ethclient"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/event"
|
||||||
)
|
)
|
||||||
|
|
||||||
const key = `paste the contents of your *testnet* key json here`
|
// 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
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
// StorageMetaData contains all meta data concerning the Storage contract.
|
||||||
// Create an IPC based RPC connection to a remote node and instantiate a contract binding
|
var StorageMetaData = &bind.MetaData{
|
||||||
conn, err := ethclient.Dial("/home/karalabe/.ethereum/testnet/geth.ipc")
|
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\"}]",
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to connect to the Ethereum client: %v", err)
|
|
||||||
}
|
|
||||||
token, err := NewToken(common.HexToAddress("0x21e6fc92f93c8a1bb41e2be64b4e1f88a54d3576"), conn)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to instantiate a Token contract: %v", err)
|
|
||||||
}
|
|
||||||
// Create an authorized transactor and spend 1 unicorn
|
|
||||||
auth, err := bind.NewTransactor(strings.NewReader(key), "my awesome super secret password")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to create authorized transactor: %v", err)
|
|
||||||
}
|
|
||||||
tx, err := token.Transfer(auth, common.HexToAddress("0x0000000000000000000000000000000000000000"), big.NewInt(1))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to request token transfer: %v", err)
|
|
||||||
}
|
|
||||||
fmt.Printf("Transfer pending: 0x%x\n", tx.Hash())
|
|
||||||
}
|
}
|
||||||
```
|
|
||||||
|
|
||||||
And the output (yay):
|
// 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.
|
||||||
Transfer pending: 0x4f4aaeb29ed48e88dd653a81f0b05d4df64a86c99d4e83b5bfeb0f0006b0e55b
|
type Storage struct {
|
||||||
```
|
StorageCaller // Read-only binding to the contract
|
||||||
|
StorageTransactor // Write-only binding to the contract
|
||||||
*Note, with high probability you won't have any testnet unicorns available to spend, so the
|
StorageFilterer // Log filterer for contract events
|
||||||
above program will fail with an error. Send at least 2.014 testnet(!) Ethers to the foundation
|
|
||||||
testnet tipjar `0xDf7D0030bfed998Db43288C190b63470c2d18F50` to receive a unicorn token and
|
|
||||||
you'll be able to see the above code run without an error!*
|
|
||||||
|
|
||||||
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. An unset value is assumed to be zero.
|
|
||||||
|
|
||||||
### Pre-configured contract sessions
|
|
||||||
|
|
||||||
As mentioned in the previous two sections, both reading as well as state modifying contract
|
|
||||||
calls require a mandatory first parameter which can both authorize as well as fine tune some
|
|
||||||
of the internal parameters. However, most of the time we want to use the same parameters and
|
|
||||||
issue transactions with the same account, so always constructing the call/transact options or
|
|
||||||
passing them along with the binding can become unwieldy.
|
|
||||||
|
|
||||||
To avoid these scenarios, 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 analogous to the original contract type name, just suffixed with `Sessions`:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Wrap the Token contract instance into a session
|
|
||||||
session := &TokenSession{
|
|
||||||
Contract: token,
|
|
||||||
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.Name()
|
|
||||||
session.Transfer("0x0000000000000000000000000000000000000000"), big.NewInt(1))
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`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
|
### Deploying contracts to Ethereum
|
||||||
|
|
||||||
Interacting with existing contracts is nice, but let's take it up a notch and deploy
|
In the previous section, the contract ABI was sufficient for generating the contract bindings from its ABI.
|
||||||
a brand new contract onto the Ethereum blockchain! To do so however, the contract ABI
|
However, deploying the contract requires some additional information in the form of the compiled
|
||||||
we used to generate the binding is not enough. We need the compiled bytecode too to
|
bytecode.
|
||||||
allow deploying it.
|
|
||||||
|
|
||||||
To get the bytecode, either go back to the online compiler with which you may generate it,
|
The bytecode is obtained by running the compiler again but this passing the `--bin` flag, e.g.
|
||||||
or alternatively download our [`token.bin`](https://gist.github.com/karalabe/026548f6a5f5f97b54de).
|
|
||||||
You'll need to rerun the Go generator with the bytecode included for it to create deploy
|
|
||||||
code too:
|
|
||||||
|
|
||||||
```
|
```shell
|
||||||
$ abigen --abi token.abi --pkg main --type Token --out token.go --bin token.bin
|
solc --bin Storage.sol -o Storage.bin
|
||||||
```
|
```
|
||||||
|
|
||||||
This will generate something similar to [`token.go`](https://gist.github.com/karalabe/2153b087c1f80f651fd87dd4c439fac4).
|
Then `abigen` can be run again, this time passing `Storage.bin`:
|
||||||
If you quickly skim this file, you'll find an extra `DeployToken` function that was just
|
|
||||||
injected compared to the previous code. Beside all the parameters specified by Solidity,
|
|
||||||
it also needs the usual authorization options to deploy the contract with and the Ethereum
|
```
|
||||||
backend to deploy the contract through.
|
$ 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:
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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:
|
Putting it all together would result in:
|
||||||
|
|
||||||
|
@ -286,32 +241,33 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/ethclient"
|
"github.com/ethereum/go-ethereum/ethclient"
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const key = `paste the contents of your *testnet* key json here`
|
const key = `<<json object from keystore>>`
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Create an IPC based RPC connection to a remote node and an authorized transactor
|
// Create an IPC based RPC connection to a remote node and an authorized transactor
|
||||||
conn, err := rpc.NewIPCClient("/home/karalabe/.ethereum/testnet/geth.ipc")
|
conn, err := rpc.NewIPCClient("/home/go-ethereum/goerli/geth.ipc")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to connect to the Ethereum client: %v", err)
|
log.Fatalf("Failed to connect to the Ethereum client: %v", err)
|
||||||
}
|
}
|
||||||
auth, err := bind.NewTransactor(strings.NewReader(key), "my awesome super secret password")
|
auth, err := bind.NewTransactor(strings.NewReader(key), "<<strong_password>>")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to create authorized transactor: %v", err)
|
log.Fatalf("Failed to create authorized transactor: %v", err)
|
||||||
}
|
}
|
||||||
// Deploy a new awesome contract for the binding demo
|
// Deploy the contract passing the newly created `auth` and `conn` vars
|
||||||
address, tx, token, err := DeployToken(auth, conn), new(big.Int), "Contracts in Go!!!", 0, "Go!")
|
address, tx, instance, err := DeployStorage(auth, conn), new(big.Int), "Storage contract in Go!", 0, "Go!")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to deploy new token contract: %v", err)
|
log.Fatalf("Failed to deploy new storage contract: %v", err)
|
||||||
}
|
}
|
||||||
fmt.Printf("Contract pending deploy: 0x%x\n", address)
|
fmt.Printf("Contract pending deploy: 0x%x\n", address)
|
||||||
fmt.Printf("Transaction waiting to be mined: 0x%x\n\n", tx.Hash())
|
fmt.Printf("Transaction waiting to be mined: 0x%x\n\n", tx.Hash())
|
||||||
|
|
||||||
// Don't even wait, check its presence in the local pending state
|
|
||||||
time.Sleep(250 * time.Millisecond) // Allow it to be processed by the local node :P
|
time.Sleep(250 * time.Millisecond) // Allow it to be processed by the local node :P
|
||||||
|
|
||||||
name, err := token.Name(&bind.CallOpts{Pending: true})
|
// function call on `instance`. Retrieves pending name
|
||||||
|
name, err := instance.Name(&bind.CallOpts{Pending: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to retrieve pending name: %v", err)
|
log.Fatalf("Failed to retrieve pending name: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -319,70 +275,289 @@ func main() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
And the code performs as expected: it requests the creation of a brand new Token contract
|
Running this code requests the creation of a brand new `Storage` contract on the Goerli blockchain.
|
||||||
on the Ethereum blockchain, which we can either wait for to be mined or as in the above code
|
The contract functions can be called while the contract is waiting to be mined.
|
||||||
start calling methods on it in the pending state :)
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Contract pending deploy: 0x46506d900559ad005feb4645dcbb2dbbf65e19cc
|
Contract pending deploy: 0x46506d900559ad005feb4645dcbb2dbbf65e19cc
|
||||||
Transaction waiting to be mined: 0x6a81231874edd2461879b7280ddde1a857162a744e3658ca7ec276984802183b
|
Transaction waiting to be mined: 0x6a81231874edd2461879b7280ddde1a857162a744e3658ca7ec276984802183b
|
||||||
|
|
||||||
Pending name: Contracts in Go!!!
|
Pending name: Storage contract in Go!
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Note that `DeployStorage` returns four variables:
|
||||||
|
|
||||||
|
- `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
|
||||||
|
|
||||||
|
|
||||||
|
### Accessing an Ethereum contract
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/ethclient"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
// Instantiate the contract and display its name
|
||||||
|
// NOTE update the deployment address!
|
||||||
|
store, err := NewStorage(common.HexToAddress("0x21e6fc92f93c8a1bb41e2be64b4e1f88a54d3576"), conn)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to instantiate Storage contract: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
```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
|
||||||
|
}
|
||||||
|
|
||||||
|
out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int)
|
||||||
|
|
||||||
|
return out0, err
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
* `Pending`: Whether to access pending contract state or the current stable one
|
||||||
|
* `GasLimit`: Place a limit on the computing resources the call might consume
|
||||||
|
|
||||||
|
So to call the `Retrieve()` function in the Go application:
|
||||||
|
|
||||||
|
```go
|
||||||
|
value, err := store.Retrieve(nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to retrieve value: %v", err)
|
||||||
|
}
|
||||||
|
fmt.Println("Value: ", value)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The output will be something like:
|
||||||
|
|
||||||
|
```terminal
|
||||||
|
Value: 56
|
||||||
|
```
|
||||||
|
|
||||||
|
### Transacting with an Ethereum contract
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Changing the previous code snippet to update the value stored in the contract:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math/big"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/ethclient"
|
||||||
|
)
|
||||||
|
|
||||||
|
const key = `json object from keystore`
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// 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")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to connect to the Ethereum client: %v", err)
|
||||||
|
}
|
||||||
|
store, err := NewStorage(common.HexToAddress("0x21e6fc92f93c8a1bb41e2be64b4e1f88a54d3576"), conn)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to instantiate a Storage contract: %v", err)
|
||||||
|
}
|
||||||
|
// 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())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And the output:
|
||||||
|
|
||||||
|
```terminal
|
||||||
|
Update pending: 0x4f4aaeb29ed48e88dd653a81f0b05d4df64a86c99d4e83b5bfeb0f0006b0e55b
|
||||||
|
```
|
||||||
|
|
||||||
|
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))
|
||||||
```
|
```
|
||||||
|
|
||||||
## Bind Solidity directly
|
## Bind Solidity directly
|
||||||
|
|
||||||
In the past, abigen allowed you to compile and bind a Solidity source file directly to a Go package.
|
In the past, abigen allowed compilation and binding of a Solidity source file directly to a Go package in a single step.
|
||||||
This feature has been discontinued from [v1.10.18](https://github.com/ethereum/go-ethereum/releases/tag/v1.10.18)
|
This feature has been discontinued from [v1.10.18](https://github.com/ethereum/go-ethereum/releases/tag/v1.10.18)
|
||||||
onwards due to maintenance synchronization challenges with the compiler in ```go-ethereum```.
|
onwards due to maintenance synchronization challenges with the compiler in Geth.
|
||||||
Now, to bind a Solidity source file into a go package you will have to compile it first using
|
|
||||||
any of your prefered approaches (e.g. [solc](https://docs.soliditylang.org/en/v0.8.14/installing-solidity.html)
|
The compilation and binding steps can be joined together into a pipeline, for example:
|
||||||
or [Remix](https://remix.ethereum.org/)) and bind it later. Binding the official Token contract [`token.sol`](https://gist.github.com/karalabe/08f4b780e01c8452d989) would then entail to running:
|
|
||||||
```
|
```
|
||||||
$ solc --abi --bin token.sol -o tokenDirectory
|
solc Storage.sol --combined-json abi,bin | abigen --pkg main --type storage --out Storage.go --combined-json -
|
||||||
$ abigen --abi tokenDirectory/token.abi --bin tokenDirectory/token.bin --pkg main --type token --out token.go
|
|
||||||
```
|
```
|
||||||
|
|
||||||
You can use the ```solc``` compiler to get a single ```.json``` file containing ABI and bytecode, and then use
|
### Project integration (`go generate`)
|
||||||
it as input to ```abigen``` to generate the Go package in a shorter command:
|
|
||||||
```
|
|
||||||
$ solc token.sol --combined-json abi,bin -o .
|
|
||||||
$ abigen --combined-json combined.json --pkg main --type token --out token.go
|
|
||||||
```
|
|
||||||
|
|
||||||
Even you can combine these two steps together as a pipeline
|
The `abigen` command was made in such a way as to integrate easily into existing
|
||||||
```
|
|
||||||
solc token.sol --combined-json abi,bin | abigen --pkg main --type token --out token.go --combined-json -
|
|
||||||
```
|
|
||||||
|
|
||||||
### Project integration (i.e. `go generate`)
|
|
||||||
|
|
||||||
The `abigen` command was made in such a way as to play beautifully together with existing
|
|
||||||
Go toolchains: instead of having to remember the exact command needed to bind an Ethereum
|
Go toolchains: instead of having to remember the exact command needed to bind an Ethereum
|
||||||
contract into a Go project, we can leverage `go generate` to remember all the nitty-gritty
|
contract into a Go project, `go generate` can handle all the fine details.
|
||||||
details.
|
|
||||||
|
|
||||||
Place the binding generation command into a Go source file before the package definition:
|
Place the binding generation command into a Go source file before the package definition:
|
||||||
|
|
||||||
```
|
```
|
||||||
//go:generate abigen --sol token.sol --pkg main --out token.go
|
//go:generate abigen --sol Storage.sol --pkg main --out Storage.go
|
||||||
```
|
```
|
||||||
|
|
||||||
After which whenever the Solidity contract is modified, instead of needing to remember and
|
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
|
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.
|
source tree via `go generate ./...`), and it will correctly generate the new bindings for us.
|
||||||
|
|
||||||
|
|
||||||
## Blockchain simulator
|
## Blockchain simulator
|
||||||
|
|
||||||
Being able to deploy and access already deployed Ethereum contracts from within native Go
|
Being able to deploy and access deployed Ethereum contracts from native Go code is a powerful
|
||||||
code is an extremely powerful feature, but there is one facet with developing native code
|
feature. However, using public testnets as a backend does not lend itself well to
|
||||||
that not even the testnet lends itself well to: *automatic unit testing*. Using go-ethereum
|
*automated unit testing*. Therefore, Geth also implements a *simulated blockchain*
|
||||||
internal constructs it's possible to create test chains and verify them, but it is unfeasible
|
that can be set as a backend to native contracts the same way as a live RPC backend, using the
|
||||||
to do high level contract testing with such low level mechanisms.
|
command `backends.NewSimulatedBackend(genesisAccounts)`. The code snippet below shows how this
|
||||||
|
can be used as a backend in a Go applicatioon.
|
||||||
To sort out this last issue that would make it hard to run (and test) native DApps, we've also
|
|
||||||
implemented a *simulated blockchain*, that can be set as a backend to native contracts the same
|
|
||||||
way as a live RPC backend could be: `backends.NewSimulatedBackend(genesisAccounts)`.
|
|
||||||
|
|
||||||
```go
|
```go
|
||||||
package main
|
package main
|
||||||
|
@ -405,38 +580,38 @@ func main() {
|
||||||
|
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAccount{Address: auth.From, Balance: big.NewInt(10000000000)})
|
sim := backends.NewSimulatedBackend(core.GenesisAccount{Address: auth.From, Balance: big.NewInt(10000000000)})
|
||||||
|
|
||||||
// Deploy a token contract on the simulated blockchain
|
// instantiate contract
|
||||||
_, _, token, err := DeployMyToken(auth, sim, new(big.Int), "Simulated blockchain tokens", 0, "SBT")
|
store, err := NewStorage(common.HexToAddress("0x21e6fc92f93c8a1bb41e2be64b4e1f88a54d3576"), sim)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to deploy new token contract: %v", err)
|
log.Fatalf("Failed to instantiate a Storage contract: %v", err)
|
||||||
}
|
}
|
||||||
// Print the current (non existent) and pending name of the contract
|
// Create an authorized transactor and call the store function
|
||||||
name, _ := token.Name(nil)
|
auth, err := bind.NewStorageTransactor(strings.NewReader(key), "strong_password")
|
||||||
fmt.Println("Pre-mining name:", name)
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to create authorized transactor: %v", err)
|
||||||
name, _ = token.Name(&bind.CallOpts{Pending: true})
|
}
|
||||||
fmt.Println("Pre-mining pending name:", name)
|
// Call the store() function
|
||||||
|
tx, err := store.Store(auth, big.NewInt(420))
|
||||||
// Commit all pending transactions in the simulator and print the names again
|
if err != nil {
|
||||||
sim.Commit()
|
log.Fatalf("Failed to update value: %v", err)
|
||||||
|
}
|
||||||
name, _ = token.Name(nil)
|
fmt.Printf("Update pending: 0x%x\n", tx.Hash())
|
||||||
fmt.Println("Post-mining name:", name)
|
|
||||||
|
|
||||||
name, _ = token.Name(&bind.CallOpts{Pending: true})
|
|
||||||
fmt.Println("Post-mining pending name:", name)
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
And the output (yay):
|
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.
|
||||||
|
|
||||||
```
|
|
||||||
Pre-mining name:
|
|
||||||
Pre-mining pending name: Simulated blockchain tokens
|
|
||||||
Post-mining name: Simulated blockchain tokens
|
|
||||||
Post-mining pending name: Simulated blockchain tokens
|
|
||||||
```
|
|
||||||
|
|
||||||
Note, that we don't have to wait for a local private chain miner, or testnet miner to
|
## Summary
|
||||||
integrate the currently pending transactions. When we decide to mine the next block,
|
|
||||||
we simply `Commit()` the simulator.
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
[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/
|
||||||
|
[web3-ss-link]:https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition
|
Loading…
Reference in New Issue