Compare commits

...

19 Commits

Author SHA1 Message Date
Jared Wasinger 0df73f3fac commit of wip-code: rework the contract deployment code to be cleaner 2024-11-24 19:35:16 +07:00
Jared Wasinger cd86fca55c dependency graph calculation working 2024-11-20 20:48:43 +07:00
Jared Wasinger 46e6dd4fcd still in a wip state 2024-11-20 18:57:03 +07:00
Jared Wasinger a2479e1aed stage point before I try to dedup contract interaction api 2024-11-06 22:28:54 +09:00
Jared Wasinger aa651aecab move v2 contract interaction methods into v2 package. add test 2024-11-04 22:50:47 +09:00
Jared Wasinger 008028e5fd isolate V2 contract interaction API into its own package 2024-11-04 19:32:26 +09:00
Jared Wasinger 332c188c9c fix last rebase regression 2024-10-31 15:31:06 +09:00
Jared Wasinger 8a65262e98 add back code that got mistakenly removed in the rebase 2024-10-31 15:04:53 +09:00
Sina Mahmoodi c6f7571c5e fix test 2024-10-30 23:16:32 +09:00
Sina Mahmoodi 60535d17a4 clean lang selection leftovers 2024-10-30 23:15:39 +09:00
Sina Mahmoodi 8f756ec576 Pass pointer to log for unpack 2024-10-30 22:47:45 +09:00
Sina Mahmoodi bb63ef3a21 Add deploy code to contract struct 2024-10-30 22:47:45 +09:00
Sina Mahmoodi cea12ea4e5 define contract instance type 2024-10-30 22:47:45 +09:00
Sina Mahmoodi dcf1318a24 skip unpack method when no return args 2024-10-30 22:47:45 +09:00
Sina Mahmoodi 724f77e3ec drop fmt 2024-10-30 22:47:45 +09:00
Sina Mahmoodi 2725ae2bcb replace fmt with errors
Co-authored-by: Marius van der Wijden <m.vanderwijden@live.de>
2024-10-30 22:47:45 +09:00
Sina Mahmoodi efc2c36291 drop unnecessary imports 2024-10-30 22:47:45 +09:00
Sina Mahmoodi ac27dad728 add copyright header to lib.go 2024-10-30 22:47:45 +09:00
Sina Mahmoodi 29d90ae528 accounts/abi: abigen v2 2024-10-30 22:47:34 +09:00
12 changed files with 978 additions and 190 deletions

View File

@ -50,6 +50,7 @@ func JSON(reader io.Reader) (ABI, error) {
var abi ABI
if err := dec.Decode(&abi); err != nil {
fmt.Println(err)
return ABI{}, err
}
return abi, nil

View File

@ -151,15 +151,24 @@ func DeployContract(opts *TransactOpts, abi abi.ABI, bytecode []byte, backend Co
return c.address, tx, c, nil
}
func DeployContractRaw(opts *TransactOpts, bytecode []byte, backend ContractBackend, packedParams []byte) (common.Address, *types.Transaction, *BoundContract, error) {
// TODO: it's weird to instantiate a bound contract (implies existence of contract) in order to deploy a contract
// that doesn't yet exist
c := NewBoundContract(common.Address{}, abi.ABI{}, backend, backend, backend)
tx, err := c.transact(opts, nil, append(bytecode, packedParams...))
if err != nil {
return common.Address{}, nil, nil, err
}
c.address = crypto.CreateAddress(opts.From, tx.Nonce())
return c.address, tx, c, nil
}
// Call invokes the (constant) contract method with params as input values and
// sets the output to result. The result type might be a single field for simple
// returns, a slice of interfaces for anonymous returns and a struct for named
// returns.
func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method string, params ...interface{}) error {
// Don't crash on a lazy user
if opts == nil {
opts = new(CallOpts)
}
if results == nil {
results = new([]interface{})
}
@ -168,59 +177,10 @@ func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method stri
if err != nil {
return err
}
var (
msg = ethereum.CallMsg{From: opts.From, To: &c.address, Data: input}
ctx = ensureContext(opts.Context)
code []byte
output []byte
)
if opts.Pending {
pb, ok := c.caller.(PendingContractCaller)
if !ok {
return ErrNoPendingState
}
output, err = pb.PendingCallContract(ctx, msg)
if err != nil {
return err
}
if len(output) == 0 {
// Make sure we have a contract to operate on, and bail out otherwise.
if code, err = pb.PendingCodeAt(ctx, c.address); err != nil {
return err
} else if len(code) == 0 {
return ErrNoCode
}
}
} else if opts.BlockHash != (common.Hash{}) {
bh, ok := c.caller.(BlockHashContractCaller)
if !ok {
return ErrNoBlockHashState
}
output, err = bh.CallContractAtHash(ctx, msg, opts.BlockHash)
if err != nil {
return err
}
if len(output) == 0 {
// Make sure we have a contract to operate on, and bail out otherwise.
if code, err = bh.CodeAtHash(ctx, c.address, opts.BlockHash); err != nil {
return err
} else if len(code) == 0 {
return ErrNoCode
}
}
} else {
output, err = c.caller.CallContract(ctx, msg, opts.BlockNumber)
if err != nil {
return err
}
if len(output) == 0 {
// Make sure we have a contract to operate on, and bail out otherwise.
if code, err = c.caller.CodeAt(ctx, c.address, opts.BlockNumber); err != nil {
return err
} else if len(code) == 0 {
return ErrNoCode
}
}
output, err := c.call(opts, input)
if err != nil {
return err
}
if len(*results) == 0 {
@ -232,6 +192,73 @@ func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method stri
return c.abi.UnpackIntoInterface(res[0], method, output)
}
func (c *BoundContract) CallRaw(opts *CallOpts, input []byte) ([]byte, error) {
return c.call(opts, input)
}
func (c *BoundContract) call(opts *CallOpts, input []byte) ([]byte, error) {
// Don't crash on a lazy user
if opts == nil {
opts = new(CallOpts)
}
var (
msg = ethereum.CallMsg{From: opts.From, To: &c.address, Data: input}
ctx = ensureContext(opts.Context)
code []byte
output []byte
err error
)
if opts.Pending {
pb, ok := c.caller.(PendingContractCaller)
if !ok {
return nil, ErrNoPendingState
}
output, err = pb.PendingCallContract(ctx, msg)
if err != nil {
return nil, err
}
if len(output) == 0 {
// Make sure we have a contract to operate on, and bail out otherwise.
if code, err = pb.PendingCodeAt(ctx, c.address); err != nil {
return nil, err
} else if len(code) == 0 {
return nil, ErrNoCode
}
}
} else if opts.BlockHash != (common.Hash{}) {
bh, ok := c.caller.(BlockHashContractCaller)
if !ok {
return nil, ErrNoBlockHashState
}
output, err = bh.CallContractAtHash(ctx, msg, opts.BlockHash)
if err != nil {
return nil, err
}
if len(output) == 0 {
// Make sure we have a contract to operate on, and bail out otherwise.
if code, err = bh.CodeAtHash(ctx, c.address, opts.BlockHash); err != nil {
return nil, err
} else if len(code) == 0 {
return nil, ErrNoCode
}
}
} else {
output, err = c.caller.CallContract(ctx, msg, opts.BlockNumber)
if err != nil {
return nil, err
}
if len(output) == 0 {
// Make sure we have a contract to operate on, and bail out otherwise.
if code, err = c.caller.CodeAt(ctx, c.address, opts.BlockNumber); err != nil {
return nil, err
} else if len(code) == 0 {
return nil, ErrNoCode
}
}
}
return output, nil
}
// Transact invokes the (paid) contract method with params as input values.
func (c *BoundContract) Transact(opts *TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
// Otherwise pack up the parameters and invoke the contract
@ -434,13 +461,16 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i
// FilterLogs filters contract logs for past blocks, returning the necessary
// channels to construct a strongly typed bound iterator on top of them.
func (c *BoundContract) FilterLogs(opts *FilterOpts, name string, query ...[]interface{}) (chan types.Log, event.Subscription, error) {
return c.filterLogs(opts, c.abi.Events[name].ID, query...)
}
func (c *BoundContract) filterLogs(opts *FilterOpts, eventID common.Hash, query ...[]interface{}) (chan types.Log, event.Subscription, error) {
// Don't crash on a lazy user
if opts == nil {
opts = new(FilterOpts)
}
// Append the event selector to the query parameters and construct the topic set
query = append([][]interface{}{{c.abi.Events[name].ID}}, query...)
query = append([][]interface{}{{eventID}}, query...)
topics, err := abi.MakeTopics(query...)
if err != nil {
return nil, nil, err
@ -480,12 +510,16 @@ func (c *BoundContract) FilterLogs(opts *FilterOpts, name string, query ...[]int
// WatchLogs filters subscribes to contract logs for future blocks, returning a
// subscription object that can be used to tear down the watcher.
func (c *BoundContract) WatchLogs(opts *WatchOpts, name string, query ...[]interface{}) (chan types.Log, event.Subscription, error) {
return c.watchLogs(opts, c.abi.Events[name].ID, query...)
}
func (c *BoundContract) watchLogs(opts *WatchOpts, eventID common.Hash, query ...[]interface{}) (chan types.Log, event.Subscription, error) {
// Don't crash on a lazy user
if opts == nil {
opts = new(WatchOpts)
}
// Append the event selector to the query parameters and construct the topic set
query = append([][]interface{}{{c.abi.Events[name].ID}}, query...)
query = append([][]interface{}{{eventID}}, query...)
topics, err := abi.MakeTopics(query...)
if err != nil {

View File

@ -33,13 +33,6 @@ import (
"github.com/ethereum/go-ethereum/log"
)
// Lang is a target programming language selector to generate bindings for.
type Lang int
const (
LangGo Lang = iota
)
func isKeyWord(arg string) bool {
switch arg {
case "break":
@ -81,7 +74,91 @@ func isKeyWord(arg string) bool {
// to be used as is in client code, but rather as an intermediate struct which
// enforces compile time type safety and naming convention as opposed to having to
// manually maintain hard coded strings that break on runtime.
func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string, aliases map[string]string) (string, error) {
func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, libs map[string]string, aliases map[string]string) (string, error) {
data, err := bind(types, abis, bytecodes, fsigs, pkg, libs, aliases)
if err != nil {
return "", err
}
buffer := new(bytes.Buffer)
funcs := map[string]interface{}{
"bindtype": bindType,
"bindtopictype": bindTopicType,
"capitalise": capitalise,
"decapitalise": decapitalise,
}
tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSource))
if err := tmpl.Execute(buffer, data); err != nil {
return "", err
}
// Pass the code through gofmt to clean it up
code, err := format.Source(buffer.Bytes())
if err != nil {
return "", fmt.Errorf("%v\n%s", err, buffer)
}
return string(code), nil
}
func BindV2(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, libs map[string]string, aliases map[string]string) (string, error) {
data, err := bind(types, abis, bytecodes, fsigs, pkg, libs, aliases)
if err != nil {
return "", err
}
for _, c := range data.Contracts {
// We want pack/unpack methods for all existing methods.
for name, t := range c.Transacts {
c.Calls[name] = t
}
c.Transacts = nil
// Make sure we return one argument. If multiple exist
// merge them into a struct.
for _, call := range c.Calls {
if call.Structured {
continue
}
if len(call.Normalized.Outputs) < 2 {
continue
}
// Build up dictionary of existing arg names.
keys := make(map[string]struct{})
for _, o := range call.Normalized.Outputs {
if o.Name != "" {
keys[strings.ToLower(o.Name)] = struct{}{}
}
}
// Assign names to anonymous fields.
for i, o := range call.Normalized.Outputs {
if o.Name != "" {
continue
}
o.Name = capitalise(abi.ResolveNameConflict("arg", func(name string) bool { _, ok := keys[name]; return ok }))
call.Normalized.Outputs[i] = o
keys[strings.ToLower(o.Name)] = struct{}{}
}
call.Structured = true
}
}
buffer := new(bytes.Buffer)
funcs := map[string]interface{}{
"bindtype": bindType,
"bindtopictype": bindTopicType,
"capitalise": capitalise,
"decapitalise": decapitalise,
}
tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSourceV2))
if err := tmpl.Execute(buffer, data); err != nil {
return "", err
}
// Pass the code through gofmt to clean it up
code, err := format.Source(buffer.Bytes())
if err != nil {
return "", fmt.Errorf("%v\n%s", err, buffer)
}
return string(code), nil
}
func bind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, libs map[string]string, aliases map[string]string) (*tmplData, error) {
var (
// contracts is the map of each individual contract requested binding
contracts = make(map[string]*tmplContract)
@ -96,7 +173,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
// Parse the actual ABI to generate the binding for
evmABI, err := abi.JSON(strings.NewReader(abis[i]))
if err != nil {
return "", err
return nil, err
}
// Strip any whitespace from the JSON ABI
strippedABI := strings.Map(func(r rune) rune {
@ -125,14 +202,14 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
for _, input := range evmABI.Constructor.Inputs {
if hasStruct(input.Type) {
bindStructType[lang](input.Type, structs)
bindStructType(input.Type, structs)
}
}
for _, original := range evmABI.Methods {
// Normalize the method for capital cases and non-anonymous inputs/outputs
normalized := original
normalizedName := methodNormalizer[lang](alias(aliases, original.Name))
normalizedName := methodNormalizer(alias(aliases, original.Name))
// Ensure there is no duplicated identifier
var identifiers = callIdentifiers
if !original.IsConstant() {
@ -147,7 +224,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
})
}
if identifiers[normalizedName] {
return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName)
return nil, fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName)
}
identifiers[normalizedName] = true
@ -159,7 +236,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j)
}
if hasStruct(input.Type) {
bindStructType[lang](input.Type, structs)
bindStructType(input.Type, structs)
}
}
normalized.Outputs = make([]abi.Argument, len(original.Outputs))
@ -169,7 +246,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
normalized.Outputs[j].Name = capitalise(output.Name)
}
if hasStruct(output.Type) {
bindStructType[lang](output.Type, structs)
bindStructType(output.Type, structs)
}
}
// Append the methods to the call or transact lists
@ -188,7 +265,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
normalized := original
// Ensure there is no duplicated identifier
normalizedName := methodNormalizer[lang](alias(aliases, original.Name))
normalizedName := methodNormalizer(alias(aliases, original.Name))
// Name shouldn't start with a digit. It will make the generated code invalid.
if len(normalizedName) > 0 && unicode.IsDigit(rune(normalizedName[0])) {
normalizedName = fmt.Sprintf("E%s", normalizedName)
@ -198,7 +275,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
})
}
if eventIdentifiers[normalizedName] {
return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName)
return nil, fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName)
}
eventIdentifiers[normalizedName] = true
normalized.Name = normalizedName
@ -220,7 +297,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
normalized.Inputs[j].Name = fmt.Sprintf("%s%d", normalized.Inputs[j].Name, index)
}
if hasStruct(input.Type) {
bindStructType[lang](input.Type, structs)
bindStructType(input.Type, structs)
}
}
// Append the event to the accumulator list
@ -233,18 +310,21 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
if evmABI.HasReceive() {
receive = &tmplMethod{Original: evmABI.Receive}
}
contracts[types[i]] = &tmplContract{
Type: capitalise(types[i]),
InputABI: strings.ReplaceAll(strippedABI, "\"", "\\\""),
InputBin: strings.TrimPrefix(strings.TrimSpace(bytecodes[i]), "0x"),
Constructor: evmABI.Constructor,
Calls: calls,
Transacts: transacts,
Fallback: fallback,
Receive: receive,
Events: events,
Libraries: make(map[string]string),
Type: capitalise(types[i]),
InputABI: strings.ReplaceAll(strippedABI, "\"", "\\\""),
InputBin: strings.TrimPrefix(strings.TrimSpace(bytecodes[i]), "0x"),
Constructor: evmABI.Constructor,
Calls: calls,
Transacts: transacts,
Fallback: fallback,
Receive: receive,
Events: events,
Libraries: make(map[string]string),
AllLibraries: make(map[string]string),
}
// Function 4-byte signatures are stored in the same sequence
// as types, if available.
if len(fsigs) > i {
@ -265,11 +345,57 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
}
}
}
// Check if that type has already been identified as a library
for i := 0; i < len(types); i++ {
_, ok := isLib[types[i]]
contracts[types[i]].Library = ok
}
// compute the full set of libraries that each contract depends on.
for _, contract := range contracts {
if contract.Library {
continue
}
// recursively traverse the library dependency graph
// of the contract, flattening it into a set.
//
// For abigenv2, we do not generate contract deploy
// methods (which in v1 recursively deploy their
// library dependencies). So, the entire set of
// library dependencies is required, and we will
// the order to deploy and link them at runtime.
var findDeps func(contract *tmplContract) map[string]struct{}
findDeps = func(contract *tmplContract) map[string]struct{} {
// 1) match all libraries that this contract depends on
re, err := regexp.Compile("__\\$([a-f0-9]+)\\$__")
if err != nil {
panic(err)
}
libBin := contracts[contract.Type].InputBin
matches := re.FindAllStringSubmatch(libBin, -1)
result := make(map[string]struct{})
// 2) recurse, gathering nested library dependencies
for _, match := range matches {
pattern := match[1]
result[pattern] = struct{}{}
depContract := contracts[libs[pattern]]
for subPattern, _ := range findDeps(depContract) {
result[subPattern] = struct{}{}
}
}
return result
}
// take the set of library patterns, convert it to a map of type -> pattern
deps := findDeps(contract)
contract.AllLibraries = make(map[string]string)
for contractPattern, _ := range deps {
contractType := libs[contractPattern]
contract.AllLibraries[contractType] = contractPattern
}
}
// Generate the contract template data content and render it
data := &tmplData{
Package: pkg,
@ -277,39 +403,11 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
Libraries: libs,
Structs: structs,
}
buffer := new(bytes.Buffer)
funcs := map[string]interface{}{
"bindtype": bindType[lang],
"bindtopictype": bindTopicType[lang],
"namedtype": namedType[lang],
"capitalise": capitalise,
"decapitalise": decapitalise,
}
tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSource[lang]))
if err := tmpl.Execute(buffer, data); err != nil {
return "", err
}
// For Go bindings pass the code through gofmt to clean it up
if lang == LangGo {
code, err := format.Source(buffer.Bytes())
if err != nil {
return "", fmt.Errorf("%v\n%s", err, buffer)
}
return string(code), nil
}
// For all others just return as is for now
return buffer.String(), nil
return data, nil
}
// bindType is a set of type binders that convert Solidity types to some supported
// programming language types.
var bindType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{
LangGo: bindTypeGo,
}
// bindBasicTypeGo converts basic solidity types(except array, slice and tuple) to Go ones.
func bindBasicTypeGo(kind abi.Type) string {
// bindBasicType converts basic solidity types(except array, slice and tuple) to Go ones.
func bindBasicType(kind abi.Type) string {
switch kind.T {
case abi.AddressTy:
return "common.Address"
@ -332,32 +430,26 @@ func bindBasicTypeGo(kind abi.Type) string {
}
}
// bindTypeGo converts solidity types to Go ones. Since there is no clear mapping
// bindType converts solidity types to Go ones. Since there is no clear mapping
// from all Solidity types to Go ones (e.g. uint17), those that cannot be exactly
// mapped will use an upscaled type (e.g. BigDecimal).
func bindTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
func bindType(kind abi.Type, structs map[string]*tmplStruct) string {
switch kind.T {
case abi.TupleTy:
return structs[kind.TupleRawName+kind.String()].Name
case abi.ArrayTy:
return fmt.Sprintf("[%d]", kind.Size) + bindTypeGo(*kind.Elem, structs)
return fmt.Sprintf("[%d]", kind.Size) + bindType(*kind.Elem, structs)
case abi.SliceTy:
return "[]" + bindTypeGo(*kind.Elem, structs)
return "[]" + bindType(*kind.Elem, structs)
default:
return bindBasicTypeGo(kind)
return bindBasicType(kind)
}
}
// bindTopicType is a set of type binders that convert Solidity types to some
// supported programming language topic types.
var bindTopicType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{
LangGo: bindTopicTypeGo,
}
// bindTopicTypeGo converts a Solidity topic type to a Go one. It is almost the same
// bindTopicType converts a Solidity topic type to a Go one. It is almost the same
// functionality as for simple types, but dynamic types get converted to hashes.
func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
bound := bindTypeGo(kind, structs)
func bindTopicType(kind abi.Type, structs map[string]*tmplStruct) string {
bound := bindType(kind, structs)
// todo(rjl493456442) according solidity documentation, indexed event
// parameters that are not value types i.e. arrays and structs are not
@ -371,16 +463,10 @@ func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
return bound
}
// bindStructType is a set of type binders that convert Solidity tuple types to some supported
// programming language struct definition.
var bindStructType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{
LangGo: bindStructTypeGo,
}
// bindStructTypeGo converts a Solidity tuple type to a Go one and records the mapping
// bindStructType converts a Solidity tuple type to a Go one and records the mapping
// in the given map.
// Notably, this function will resolve and record nested struct recursively.
func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
func bindStructType(kind abi.Type, structs map[string]*tmplStruct) string {
switch kind.T {
case abi.TupleTy:
// We compose a raw struct name and a canonical parameter expression
@ -401,7 +487,7 @@ func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
name := capitalise(kind.TupleRawNames[i])
name = abi.ResolveNameConflict(name, func(s string) bool { return names[s] })
names[name] = true
fields = append(fields, &tmplField{Type: bindStructTypeGo(*elem, structs), Name: name, SolKind: *elem})
fields = append(fields, &tmplField{Type: bindStructType(*elem, structs), Name: name, SolKind: *elem})
}
name := kind.TupleRawName
if name == "" {
@ -415,20 +501,14 @@ func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
}
return name
case abi.ArrayTy:
return fmt.Sprintf("[%d]", kind.Size) + bindStructTypeGo(*kind.Elem, structs)
return fmt.Sprintf("[%d]", kind.Size) + bindStructType(*kind.Elem, structs)
case abi.SliceTy:
return "[]" + bindStructTypeGo(*kind.Elem, structs)
return "[]" + bindStructType(*kind.Elem, structs)
default:
return bindBasicTypeGo(kind)
return bindBasicType(kind)
}
}
// namedType is a set of functions that transform language specific types to
// named versions that may be used inside method names.
var namedType = map[Lang]func(string, abi.Type) string{
LangGo: func(string, abi.Type) string { panic("this shouldn't be needed") },
}
// alias returns an alias of the given string based on the aliasing rules
// or returns itself if no rule is matched.
func alias(aliases map[string]string, n string) string {
@ -439,10 +519,8 @@ func alias(aliases map[string]string, n string) string {
}
// methodNormalizer is a name transformer that modifies Solidity method names to
// conform to target language naming conventions.
var methodNormalizer = map[Lang]func(string) string{
LangGo: abi.ToCamelCase,
}
// conform to Go naming conventions.
var methodNormalizer = abi.ToCamelCase
// capitalise makes a camel-case string which starts with an upper case character.
var capitalise = abi.ToCamelCase

View File

@ -2072,7 +2072,7 @@ var bindTests = []struct {
// Tests that packages generated by the binder can be successfully compiled and
// the requested tester run against it.
func TestGolangBindings(t *testing.T) {
func TestBindings(t *testing.T) {
t.Parallel()
// Skip the test if no Go command can be found
gocmd := runtime.GOROOT() + "/bin/go"
@ -2096,7 +2096,7 @@ func TestGolangBindings(t *testing.T) {
types = []string{tt.name}
}
// Generate the binding and create a Go source file in the workspace
bind, err := Bind(types, tt.abi, tt.bytecode, tt.fsigs, "bindtest", LangGo, tt.libs, tt.aliases)
bind, err := Bind(types, tt.abi, tt.bytecode, tt.fsigs, "bindtest", tt.libs, tt.aliases)
if err != nil {
t.Fatalf("test %d: failed to generate binding: %v", i, err)
}

33
accounts/abi/bind/lib.go Normal file
View File

@ -0,0 +1,33 @@
// Copyright 2023 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package bind
import (
"github.com/ethereum/go-ethereum/common"
)
// ContractInstance provides means to interact with
// a deployed contract.
type ContractInstance interface {
Address() common.Address
Backend() ContractBackend
}
type ContractInstanceV2 interface {
Address() common.Address
Backend() ContractBackend
}

View File

@ -32,18 +32,19 @@ type tmplData struct {
// tmplContract contains the data needed to generate an individual contract binding.
type tmplContract struct {
Type string // Type name of the main contract binding
InputABI string // JSON ABI used as the input to generate the binding from
InputBin string // Optional EVM bytecode used to generate deploy code from
FuncSigs map[string]string // Optional map: string signature -> 4-byte signature
Constructor abi.Method // Contract constructor for deploy parametrization
Calls map[string]*tmplMethod // Contract calls that only read state data
Transacts map[string]*tmplMethod // Contract calls that write state data
Fallback *tmplMethod // Additional special fallback function
Receive *tmplMethod // Additional special receive function
Events map[string]*tmplEvent // Contract events accessors
Libraries map[string]string // Same as tmplData, but filtered to only keep what the contract needs
Library bool // Indicator whether the contract is a library
Type string // Type name of the main contract binding
InputABI string // JSON ABI used as the input to generate the binding from
InputBin string // Optional EVM bytecode used to generate deploy code from
FuncSigs map[string]string // Optional map: string signature -> 4-byte signature
Constructor abi.Method // Contract constructor for deploy parametrization
Calls map[string]*tmplMethod // Contract calls that only read state data
Transacts map[string]*tmplMethod // Contract calls that write state data
Fallback *tmplMethod // Additional special fallback function
Receive *tmplMethod // Additional special receive function
Events map[string]*tmplEvent // Contract events accessors
Libraries map[string]string // Same as tmplData, but filtered to only keep direct deps that the contract needs
AllLibraries map[string]string // same as Libraries, but all direct/indirect library dependencies
Library bool // Indicator whether the contract is a library
}
// tmplMethod is a wrapper around an abi.Method that contains a few preprocessed
@ -76,14 +77,8 @@ type tmplStruct struct {
Fields []*tmplField // Struct fields definition depends on the binding language.
}
// tmplSource is language to template mapping containing all the supported
// programming languages the package can generate to.
var tmplSource = map[Lang]string{
LangGo: tmplSourceGo,
}
// tmplSourceGo is the Go source template that the generated Go contract binding
// tmplSource is the Go source template that the generated Go contract binding
// is based on.
//
//go:embed source.go.tpl
var tmplSourceGo string
var tmplSource string

View File

@ -0,0 +1,152 @@
package bind
// tmplSourceV2 is the Go source template that the generated
// Go contract binding V2 is based on.
const tmplSourceV2 = `
// Code generated via abigen V2 - DO NOT EDIT.
// This file is a generated binding and any manual changes will be lost.
package {{.Package}}
import (
"math/big"
"errors"
"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"
)
// Reference imports to suppress errors if they are not otherwise used.
var (
_ = errors.New
_ = big.NewInt
_ = bind.Bind
_ = common.Big1
_ = types.BloomLookup
_ = abi.ConvertType
)
{{$structs := .Structs}}
{{range $structs}}
// {{.Name}} is an auto generated low-level Go binding around an user-defined struct.
type {{.Name}} struct {
{{range $field := .Fields}}
{{$field.Name}} {{$field.Type}}{{end}}
}
{{end}}
{{range $contract := .Contracts}}
var {{$contract.Type}}LibraryDeps = map[string]*bind.MetaData{
{{range $name, $pattern := .AllLibraries -}}
"{{$pattern}}": {{$name}}MetaData,
{{ end}}
}
// {{.Type}}MetaData contains all meta data concerning the {{.Type}} contract.
var {{.Type}}MetaData = &bind.MetaData{
ABI: "{{.InputABI}}",
{{if $contract.FuncSigs -}}
Sigs: map[string]string{
{{range $strsig, $binsig := .FuncSigs}}"{{$binsig}}": "{{$strsig}}",
{{end}}
},
{{end -}}
{{if .InputBin -}}
Bin: "0x{{.InputBin}}",
{{end}}
}
// {{.Type}} is an auto generated Go binding around an Ethereum contract.
type {{.Type}} struct {
abi abi.ABI
}
// New{{.Type}} creates a new instance of {{.Type}}.
func New{{.Type}}() (*{{.Type}}, error) {
parsed, err := {{.Type}}MetaData.GetAbi()
if err != nil {
return nil, err
}
return &{{.Type}}{abi: *parsed}, nil
}
// TODO: create custom exported types where unpack would generate a struct return.
// TODO: test constructor with inputs
func (_{{$contract.Type}} *{{$contract.Type}}) PackConstructor({{range .Constructor.Inputs}} {{.Name}} {{bindtype .Type $structs}} {{end}}) ([]byte, error) {
return _{{$contract.Type}}.abi.Pack("" {{range .Constructor.Inputs}}, {{.Name}}{{end}})
}
{{range .Calls}}
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}.
//
// Solidity: {{.Original.String}}
func (_{{$contract.Type}} *{{$contract.Type}}) Pack{{.Normalized.Name}}({{range .Normalized.Inputs}} {{.Name}} {{bindtype .Type $structs}}, {{end}}) ([]byte, error) {
return _{{$contract.Type}}.abi.Pack("{{.Original.Name}}" {{range .Normalized.Inputs}}, {{.Name}}{{end}})
}
{{/* Unpack method is needed only when there are return args */}}
{{if .Normalized.Outputs }}
func (_{{$contract.Type}} *{{$contract.Type}}) Unpack{{.Normalized.Name}}(data []byte) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}};{{end}} },{{else}}{{range .Normalized.Outputs}}{{bindtype .Type $structs}},{{end}}{{end}} error) {
out, err := _{{$contract.Type}}.abi.Unpack("{{.Original.Name}}", data)
{{if .Structured}}
outstruct := new(struct{ {{range .Normalized.Outputs}} {{.Name}} {{bindtype .Type $structs}}; {{end}} })
if err != nil {
return *outstruct, err
}
{{range $i, $t := .Normalized.Outputs}}
outstruct.{{.Name}} = *abi.ConvertType(out[{{$i}}], new({{bindtype .Type $structs}})).(*{{bindtype .Type $structs}}){{end}}
return *outstruct, err
{{else}}
if err != nil {
return {{range $i, $_ := .Normalized.Outputs}}*new({{bindtype .Type $structs}}), {{end}} err
}
{{range $i, $t := .Normalized.Outputs}}
out{{$i}} := *abi.ConvertType(out[{{$i}}], new({{bindtype .Type $structs}})).(*{{bindtype .Type $structs}}){{end}}
return {{range $i, $t := .Normalized.Outputs}}out{{$i}}, {{end}} err
{{end}}
}
{{end}}
{{end}}
{{range .Events}}
// {{$contract.Type}}{{.Normalized.Name}} represents a {{.Normalized.Name}} event raised by the {{$contract.Type}} contract.
type {{$contract.Type}}{{.Normalized.Name}} struct { {{range .Normalized.Inputs}}
{{capitalise .Name}} {{if .Indexed}}{{bindtopictype .Type $structs}}{{else}}{{bindtype .Type $structs}}{{end}}; {{end}}
Raw *types.Log // Blockchain specific contextual infos
}
func {{$contract.Type}}{{.Normalized.Name}}EventID() common.Hash {
return common.HexToHash("{{.Original.ID}}")
}
func (_{{$contract.Type}} *{{$contract.Type}}) Unpack{{.Normalized.Name}}Event(log *types.Log) (*{{$contract.Type}}{{.Normalized.Name}}, error) {
event := "{{.Normalized.Name}}"
if log.Topics[0] != _{{$contract.Type}}.abi.Events[event].ID {
return nil, errors.New("event signature mismatch")
}
out := new({{$contract.Type}}{{.Normalized.Name}})
if len(log.Data) > 0 {
if err := _{{$contract.Type}}.abi.UnpackIntoInterface(out, event, log.Data); err != nil {
return nil, err
}
}
var indexed abi.Arguments
for _, arg := range _{{$contract.Type}}.abi.Events[event].Inputs {
if arg.Indexed {
indexed = append(indexed, arg)
}
}
if err := abi.ParseTopics(out, indexed, log.Topics[1:]); err != nil {
return nil, err
}
out.Raw = log
return out, nil
}
{{end}}
{{end}}
`

View File

@ -0,0 +1,44 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
library RecursiveDep {
function AddOne(uint256 val) public pure returns (uint256 ret) {
return val + 1;
}
}
// Array function to delete element at index and re-organize the array
// so that there are no gaps between the elements.
library Array {
using RecursiveDep for uint256;
function remove(uint256[] storage arr, uint256 index) public {
// Move the last element into the place to delete
require(arr.length > 0, "Can't remove from empty array");
arr[index] = arr[arr.length - 1];
arr[index] = arr[index].AddOne();
arr.pop();
}
}
contract TestArray {
using Array for uint256[];
uint256[] public arr;
function testArrayRemove(uint256 value) public {
for (uint256 i = 0; i < 3; i++) {
arr.push(i);
}
arr.remove(1);
assert(arr.length == 2);
assert(arr[0] == 0);
assert(arr[1] == 2);
}
constructor(uint256 foobar) {
}
}

View File

@ -0,0 +1,20 @@
package v2
import (
"context"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"math/big"
)
type V2Backend interface {
SuggestGasPrice(ctx context.Context) (*big.Int, error)
PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error)
PendingNonceAt(ctx context.Context, account common.Address) (uint64, error)
SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error)
HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error)
SendTransaction(ctx context.Context, tx *types.Transaction) error
SuggestGasTipCap(ctx context.Context) (*big.Int, error)
EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error)
}

247
accounts/abi/bind/v2/lib.go Normal file
View File

@ -0,0 +1,247 @@
package v2
import (
"encoding/hex"
"fmt"
"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"
"regexp"
"strings"
)
type ContractInstance struct {
Address common.Address
Backend bind.ContractBackend
}
func deployDeps(backend bind.ContractBackend, auth *bind.TransactOpts, constructorInputs map[string][]byte, contracts map[string]string) (deploymentTxs map[common.Address]*types.Transaction, deployAddrs map[common.Address]struct{}, err error) {
for pattern, contractBin := range contracts {
contractBinBytes, err := hex.DecodeString(contractBin[2:])
if err != nil {
return deploymentTxs, deployAddrs, fmt.Errorf("contract bytecode is not a hex string: %s", contractBin[2:])
}
var constructorInput []byte
if inp, ok := constructorInputs[pattern]; ok {
constructorInput = inp
} else {
constructorInput = make([]byte, 0) // TODO check if we can pass a nil byte slice here.
}
addr, tx, _, err := bind.DeployContractRaw(auth, contractBinBytes, backend, constructorInput)
if err != nil {
return deploymentTxs, deployAddrs, fmt.Errorf("failed to deploy contract: %v", err)
}
deploymentTxs[addr] = tx
deployAddrs[addr] = struct{}{}
}
return deploymentTxs, deployAddrs, nil
}
func linkDeps(deps *map[string]string, linked *map[string]common.Address) (deployableDeps map[string]string) {
reMatchSpecificPattern, err := regexp.Compile("__\\$([a-f0-9]+)\\$__")
if err != nil {
panic(err)
}
reMatchAnyPattern, err := regexp.Compile("__\\$.*\\$__")
if err != nil {
panic(err)
}
deployableDeps = make(map[string]string)
for pattern, dep := range *deps {
// attempt to replace references to every single linked dep
for _, match := range reMatchSpecificPattern.FindAllStringSubmatch(dep, -1) {
matchingPattern := match[1]
addr, ok := (*linked)[matchingPattern]
if !ok {
continue
}
(*deps)[pattern] = strings.ReplaceAll(dep, matchingPattern, addr.String())
}
// if we linked something into this dep, see if it can be deployed
if !reMatchAnyPattern.MatchString((*deps)[pattern]) {
deployableDeps[pattern] = (*deps)[pattern]
delete(*deps, pattern)
}
}
return deployableDeps
}
func LinkAndDeployContractsWithOverride(auth *bind.TransactOpts, backend bind.ContractBackend, constructorInputs map[string][]byte, contracts, libs map[string]string, overrides map[string]common.Address) (allDeployTxs map[common.Address]*types.Transaction, allDeployAddrs map[common.Address]struct{}, err error) {
var depsToDeploy map[string]string // map of pattern -> unlinked binary for deps we will deploy
// initialize the set of already-deployed contracts with given override addresses
linked := make(map[string]common.Address)
for pattern, deployAddr := range overrides {
linked[pattern] = deployAddr
if _, ok := contracts[pattern]; ok {
delete(contracts, pattern)
}
}
// link and deploy dynamic libraries
for {
deployableDeps := linkDeps(&depsToDeploy, &linked)
if len(deployableDeps) == 0 {
break
}
deployTxs, deployAddrs, err := deployDeps(backend, auth, constructorInputs, deployableDeps)
for addr, _ := range deployAddrs {
allDeployAddrs[addr] = struct{}{}
}
for addr, tx := range deployTxs {
allDeployTxs[addr] = tx
}
if err != nil {
return deployTxs, allDeployAddrs, err
}
}
// link and deploy the contracts
contractBins := make(map[string]string)
linkedContracts := linkDeps(&contractBins, &linked)
deployTxs, deployAddrs, err := deployDeps(backend, auth, constructorInputs, linkedContracts)
for addr, _ := range deployAddrs {
allDeployAddrs[addr] = struct{}{}
}
for addr, tx := range deployTxs {
allDeployTxs[addr] = tx
}
return allDeployTxs, allDeployAddrs, err
}
func FilterLogs[T any](instance *ContractInstance, opts *bind.FilterOpts, eventID common.Hash, unpack func(*types.Log) (*T, error), topics ...[]any) (*EventIterator[T], error) {
backend := instance.Backend
c := bind.NewBoundContract(instance.Address, abi.ABI{}, backend, backend, backend)
logs, sub, err := c.FilterLogs(opts, eventID.String(), topics...)
if err != nil {
return nil, err
}
return &EventIterator[T]{unpack: unpack, logs: logs, sub: sub}, nil
}
func WatchLogs[T any](instance *ContractInstance, opts *bind.WatchOpts, eventID common.Hash, unpack func(*types.Log) (*T, error), sink chan<- *T, topics ...[]any) (event.Subscription, error) {
backend := instance.Backend
c := bind.NewBoundContract(instance.Address, abi.ABI{}, backend, backend, backend)
logs, sub, err := c.WatchLogs(opts, eventID.String(), topics...)
if err != nil {
return nil, err
}
return event.NewSubscription(func(quit <-chan struct{}) error {
defer sub.Unsubscribe()
for {
select {
case log := <-logs:
// New log arrived, parse the event and forward to the user
ev, err := unpack(&log)
if err != nil {
return err
}
select {
case sink <- ev:
case err := <-sub.Err():
return err
case <-quit:
return nil
}
case err := <-sub.Err():
return err
case <-quit:
return nil
}
}
}), nil
}
// EventIterator is returned from FilterLogs and is used to iterate over the raw logs and unpacked data for events.
type EventIterator[T any] struct {
Event *T // Event containing the contract specifics and raw log
unpack func(*types.Log) (*T, error) // Unpack function for the event
logs chan types.Log // Log channel receiving the found contract events
sub ethereum.Subscription // Subscription for errors, completion and termination
done bool // Whether the subscription completed delivering logs
fail error // Occurred error to stop iteration
}
// Next advances the iterator to the subsequent event, returning whether there
// are any more events found. In case of a retrieval or parsing error, false is
// returned and Error() can be queried for the exact failure.
func (it *EventIterator[T]) Next() bool {
// If the iterator failed, stop iterating
if it.fail != nil {
return false
}
// If the iterator completed, deliver directly whatever's available
if it.done {
select {
case log := <-it.logs:
res, err := it.unpack(&log)
if err != nil {
it.fail = err
return false
}
it.Event = res
return true
default:
return false
}
}
// Iterator still in progress, wait for either a data or an error event
select {
case log := <-it.logs:
res, err := it.unpack(&log)
if err != nil {
it.fail = err
return false
}
it.Event = res
return true
case err := <-it.sub.Err():
it.done = true
it.fail = err
return it.Next()
}
}
// Error returns any retrieval or parsing error occurred during filtering.
func (it *EventIterator[T]) Error() error {
return it.fail
}
// Close terminates the iteration process, releasing any pending underlying
// resources.
func (it *EventIterator[T]) Close() error {
it.sub.Unsubscribe()
return nil
}
func Transact(instance bind.ContractInstance, opts *bind.TransactOpts, input []byte) (*types.Transaction, error) {
var (
addr = instance.Address()
backend = instance.Backend()
)
c := bind.NewBoundContract(addr, abi.ABI{}, backend, backend, backend)
return c.RawTransact(opts, input)
}
func Transfer(instance bind.ContractInstance, opts *bind.TransactOpts) (*types.Transaction, error) {
backend := instance.Backend()
c := bind.NewBoundContract(instance.Address(), abi.ABI{}, backend, backend, backend)
return c.Transfer(opts)
}
func CallRaw(instance bind.ContractInstance, opts *bind.CallOpts, input []byte) ([]byte, error) {
backend := instance.Backend()
c := bind.NewBoundContract(instance.Address(), abi.ABI{}, backend, backend, backend)
return c.CallRaw(opts, input)
}

View File

@ -0,0 +1,184 @@
package v2
import (
"context"
"encoding/json"
"fmt"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/accounts/abi/bind/testdata/v2_generated_testcase"
"github.com/ethereum/go-ethereum/accounts/abi/bind/testdata/v2_testcase_library"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"io"
"os"
"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/crypto"
"github.com/ethereum/go-ethereum/ethclient/simulated"
"github.com/ethereum/go-ethereum/params"
"math/big"
"strings"
"testing"
)
var testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
// JSON returns a parsed ABI interface and error if it failed.
func JSON(reader io.Reader) (abi.ABI, error) {
dec := json.NewDecoder(reader)
var instance abi.ABI
if err := dec.Decode(&instance); err != nil {
return abi.ABI{}, err
}
return instance, nil
}
func TestV2(t *testing.T) {
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
backend := simulated.NewBackend(
types.GenesisAlloc{
testAddr: {Balance: big.NewInt(10000000000000000)},
},
func(nodeConf *node.Config, ethConf *ethconfig.Config) {
ethConf.Genesis.Difficulty = big.NewInt(0)
},
)
defer backend.Close()
contractABI, err := JSON(strings.NewReader(v2_generated_testcase.V2GeneratedTestcaseMetaData.ABI))
if err != nil {
panic(err)
}
signer := types.LatestSigner(params.AllDevChainProtocolChanges)
opts := bind.TransactOpts{
From: testAddr,
Nonce: nil,
Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) {
signature, err := crypto.Sign(signer.Hash(tx).Bytes(), testKey)
if err != nil {
t.Fatal(err)
}
signedTx, err := tx.WithSignature(signer, signature)
if err != nil {
t.Fatal(err)
}
return signedTx, nil
},
Context: context.Background(),
}
// we should just be able to use the backend directly, instead of using
// this deprecated interface. However, the simulated backend no longer
// implements backends.SimulatedBackend...
bindBackend := backends.SimulatedBackend{
Backend: backend,
Client: backend.Client(),
}
address, tx, _, err := bind.DeployContract(&opts, contractABI, common.Hex2Bytes(v2_generated_testcase.V2GeneratedTestcaseMetaData.Bin), &bindBackend)
if err != nil {
t.Fatal(err)
}
_, err = bind.WaitDeployed(context.Background(), &bindBackend, tx)
if err != nil {
t.Fatalf("error deploying bound contract: %+v", err)
}
contract, err := v2_generated_testcase.NewV2GeneratedTestcase()
if err != nil {
t.Fatal(err) // can't happen here with the example used. consider removing this block
}
//contractInstance := v2_generated_testcase.NewV2GeneratedTestcaseInstance(contract, address, bindBackend)
contractInstance := ContractInstance{
Address: address,
Backend: bindBackend,
}
sinkCh := make(chan *v2_generated_testcase.V2GeneratedTestcaseStruct)
// q: what extra functionality is given by specifying this as a custom method, instead of catching emited methods
// from the sync channel?
unpackStruct := func(log *types.Log) (*v2_generated_testcase.V2GeneratedTestcaseStruct, error) {
res, err := contract.UnpackStructEvent(log)
return res, err
}
watchOpts := bind.WatchOpts{
Start: nil,
Context: context.Background(),
}
// TODO: test using various topics
// q: does nil topics mean to accept any?
sub, err := WatchLogs[v2_generated_testcase.V2GeneratedTestcaseStruct](&contractInstance, &watchOpts, v2_generated_testcase.V2GeneratedTestcaseStructEventID(), unpackStruct, sinkCh, nil)
if err != nil {
t.Fatal(err)
}
defer sub.Unsubscribe()
// send a balance to our contract (contract must accept ether by default)
}
func TestDeployment(t *testing.T) {
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
backend := simulated.NewBackend(
types.GenesisAlloc{
testAddr: {Balance: big.NewInt(10000000000000000)},
},
func(nodeConf *node.Config, ethConf *ethconfig.Config) {
ethConf.Genesis.Difficulty = big.NewInt(0)
},
)
defer backend.Close()
_, err := JSON(strings.NewReader(v2_generated_testcase.V2GeneratedTestcaseMetaData.ABI))
if err != nil {
panic(err)
}
signer := types.LatestSigner(params.AllDevChainProtocolChanges)
opts := bind.TransactOpts{
From: testAddr,
Nonce: nil,
Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) {
signature, err := crypto.Sign(signer.Hash(tx).Bytes(), testKey)
if err != nil {
t.Fatal(err)
}
signedTx, err := tx.WithSignature(signer, signature)
if err != nil {
t.Fatal(err)
}
return signedTx, nil
},
Context: context.Background(),
}
// we should just be able to use the backend directly, instead of using
// this deprecated interface. However, the simulated backend no longer
// implements backends.SimulatedBackend...
bindBackend := backends.SimulatedBackend{
Backend: backend,
Client: backend.Client(),
}
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stdout, log.LevelDebug, true)))
///LinkAndDeployContractsWithOverride(&opts, bindBackend, v2_test)
deployTxs, err := DeployContracts(&opts, bindBackend, []byte{}, v2_testcase_library.TestArrayLibraryDeps)
if err != nil {
t.Fatalf("err: %+v\n", err)
}
for _, tx := range deployTxs {
fmt.Println("waiting for deployment")
_, err = bind.WaitDeployed(context.Background(), &bindBackend, tx)
if err != nil {
t.Fatalf("error deploying bound contract: %+v", err)
}
}
}
/* test-cases that should be extracted from v1 tests
* EventChecker
*/

View File

@ -63,15 +63,14 @@ var (
Name: "out",
Usage: "Output file for the generated binding (default = stdout)",
}
langFlag = &cli.StringFlag{
Name: "lang",
Usage: "Destination language for the bindings (go)",
Value: "go",
}
aliasFlag = &cli.StringFlag{
Name: "alias",
Usage: "Comma separated aliases for function and event renaming, e.g. original1=alias1, original2=alias2",
}
v2Flag = &cli.BoolFlag{
Name: "v2",
Usage: "Generates v2 bindings",
}
)
var app = flags.NewApp("Ethereum ABI wrapper code generator")
@ -86,8 +85,8 @@ func init() {
excFlag,
pkgFlag,
outFlag,
langFlag,
aliasFlag,
v2Flag,
}
app.Action = abigen
}
@ -98,13 +97,6 @@ func abigen(c *cli.Context) error {
if c.String(pkgFlag.Name) == "" {
utils.Fatalf("No destination package specified (--pkg)")
}
var lang bind.Lang
switch c.String(langFlag.Name) {
case "go":
lang = bind.LangGo
default:
utils.Fatalf("Unsupported destination language \"%s\" (--lang)", c.String(langFlag.Name))
}
// If the entire solidity code was specified, build and bind based on that
var (
abis []string
@ -216,7 +208,15 @@ func abigen(c *cli.Context) error {
}
}
// Generate the contract binding
code, err := bind.Bind(types, abis, bins, sigs, c.String(pkgFlag.Name), lang, libs, aliases)
var (
code string
err error
)
if c.IsSet(v2Flag.Name) {
code, err = bind.BindV2(types, abis, bins, sigs, c.String(pkgFlag.Name), libs, aliases)
} else {
code, err = bind.Bind(types, abis, bins, sigs, c.String(pkgFlag.Name), libs, aliases)
}
if err != nil {
utils.Fatalf("Failed to generate ABI binding: %v", err)
}