accounts/abi: accounts/abi/bind: Move topics to abi package (#21057)

* accounts/abi/bind: added test cases for waitDeployed

* accounts/abi/bind: added test case for boundContract

* accounts/abi/bind: removed unnecessary resolve methods

* accounts/abi: moved topics from /bind to /abi

* accounts/abi/bind: cleaned up format... functions

* accounts/abi: improved log message

* accounts/abi: added type tests

* accounts/abi/bind: remove superfluous template methods
This commit is contained in:
Marius van der Wijden
2020-05-12 12:21:40 +02:00
committed by GitHub
parent 7b7e5921a4
commit b8ea9042e5
10 changed files with 230 additions and 142 deletions

View File

@ -266,7 +266,7 @@ func (c *BoundContract) FilterLogs(opts *FilterOpts, name string, query ...[]int
// Append the event selector to the query parameters and construct the topic set
query = append([][]interface{}{{c.abi.Events[name].ID}}, query...)
topics, err := makeTopics(query...)
topics, err := abi.MakeTopics(query...)
if err != nil {
return nil, nil, err
}
@ -315,7 +315,7 @@ func (c *BoundContract) WatchLogs(opts *WatchOpts, name string, query ...[]inter
// Append the event selector to the query parameters and construct the topic set
query = append([][]interface{}{{c.abi.Events[name].ID}}, query...)
topics, err := makeTopics(query...)
topics, err := abi.MakeTopics(query...)
if err != nil {
return nil, nil, err
}
@ -349,7 +349,7 @@ func (c *BoundContract) UnpackLog(out interface{}, event string, log types.Log)
indexed = append(indexed, arg)
}
}
return parseTopics(out, indexed, log.Topics[1:])
return abi.ParseTopics(out, indexed, log.Topics[1:])
}
// UnpackLogIntoMap unpacks a retrieved log into the provided map.
@ -365,7 +365,7 @@ func (c *BoundContract) UnpackLogIntoMap(out map[string]interface{}, event strin
indexed = append(indexed, arg)
}
}
return parseTopicsIntoMap(out, indexed, log.Topics[1:])
return abi.ParseTopicsIntoMap(out, indexed, log.Topics[1:])
}
// ensureContext is a helper method to ensure a context is not nil, even if the

View File

@ -34,8 +34,10 @@ import (
)
type mockCaller struct {
codeAtBlockNumber *big.Int
callContractBlockNumber *big.Int
codeAtBlockNumber *big.Int
callContractBlockNumber *big.Int
pendingCodeAtCalled bool
pendingCallContractCalled bool
}
func (mc *mockCaller) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) {
@ -47,6 +49,16 @@ func (mc *mockCaller) CallContract(ctx context.Context, call ethereum.CallMsg, b
mc.callContractBlockNumber = blockNumber
return nil, nil
}
func (mc *mockCaller) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) {
mc.pendingCodeAtCalled = true
return nil, nil
}
func (mc *mockCaller) PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) {
mc.pendingCallContractCalled = true
return nil, nil
}
func TestPassingBlockNumber(t *testing.T) {
mc := &mockCaller{}
@ -82,6 +94,16 @@ func TestPassingBlockNumber(t *testing.T) {
if mc.codeAtBlockNumber != nil {
t.Fatalf("CodeAt() was passed a block number when it should not have been")
}
bc.Call(&bind.CallOpts{BlockNumber: blockNumber, Pending: true}, &ret, "something")
if !mc.pendingCallContractCalled {
t.Fatalf("CallContract() was not passed the block number")
}
if !mc.pendingCodeAtCalled {
t.Fatalf("CodeAt() was not passed the block number")
}
}
const hexData = "0x000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158"

View File

@ -220,8 +220,6 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
"bindtype": bindType[lang],
"bindtopictype": bindTopicType[lang],
"namedtype": namedType[lang],
"formatmethod": formatMethod,
"formatevent": formatEvent,
"capitalise": capitalise,
"decapitalise": decapitalise,
}
@ -537,9 +535,7 @@ var methodNormalizer = map[Lang]func(string) string{
}
// capitalise makes a camel-case string which starts with an upper case character.
func capitalise(input string) string {
return abi.ToCamelCase(input)
}
var capitalise = abi.ToCamelCase
// decapitalise makes a camel-case string which starts with a lower case character.
func decapitalise(input string) string {
@ -588,74 +584,3 @@ func hasStruct(t abi.Type) bool {
return false
}
}
// resolveArgName converts a raw argument representation into a user friendly format.
func resolveArgName(arg abi.Argument, structs map[string]*tmplStruct) string {
var (
prefix string
embedded string
typ = &arg.Type
)
loop:
for {
switch typ.T {
case abi.SliceTy:
prefix += "[]"
case abi.ArrayTy:
prefix += fmt.Sprintf("[%d]", typ.Size)
default:
embedded = typ.TupleRawName + typ.String()
break loop
}
typ = typ.Elem
}
if s, exist := structs[embedded]; exist {
return prefix + s.Name
} else {
return arg.Type.String()
}
}
// formatMethod transforms raw method representation into a user friendly one.
func formatMethod(method abi.Method, structs map[string]*tmplStruct) string {
inputs := make([]string, len(method.Inputs))
for i, input := range method.Inputs {
inputs[i] = fmt.Sprintf("%v %v", resolveArgName(input, structs), input.Name)
}
outputs := make([]string, len(method.Outputs))
for i, output := range method.Outputs {
outputs[i] = resolveArgName(output, structs)
if len(output.Name) > 0 {
outputs[i] += fmt.Sprintf(" %v", output.Name)
}
}
// Extract meaningful state mutability of solidity method.
// If it's default value, never print it.
state := method.StateMutability
if state == "nonpayable" {
state = ""
}
if state != "" {
state = state + " "
}
identity := fmt.Sprintf("function %v", method.RawName)
if method.Type == abi.Fallback {
identity = "fallback"
} else if method.Type == abi.Receive {
identity = "receive"
}
return fmt.Sprintf("%s(%v) %sreturns(%v)", identity, strings.Join(inputs, ", "), state, strings.Join(outputs, ", "))
}
// formatEvent transforms raw event representation into a user friendly one.
func formatEvent(event abi.Event, structs map[string]*tmplStruct) string {
inputs := make([]string, len(event.Inputs))
for i, input := range event.Inputs {
if input.Indexed {
inputs[i] = fmt.Sprintf("%v indexed %v", resolveArgName(input, structs), input.Name)
} else {
inputs[i] = fmt.Sprintf("%v %v", resolveArgName(input, structs), input.Name)
}
}
return fmt.Sprintf("event %v(%v)", event.RawName, strings.Join(inputs, ", "))
}

View File

@ -297,7 +297,7 @@ var (
{{range .Calls}}
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}.
//
// Solidity: {{formatmethod .Original $structs}}
// Solidity: {{.Original.String}}
func (_{{$contract.Type}} *{{$contract.Type}}Caller) {{.Normalized.Name}}(opts *bind.CallOpts {{range .Normalized.Inputs}}, {{.Name}} {{bindtype .Type $structs}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}};{{end}} },{{else}}{{range .Normalized.Outputs}}{{bindtype .Type $structs}},{{end}}{{end}} error) {
{{if .Structured}}ret := new(struct{
{{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}}
@ -316,14 +316,14 @@ var (
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}.
//
// Solidity: {{formatmethod .Original $structs}}
// Solidity: {{.Original.String}}
func (_{{$contract.Type}} *{{$contract.Type}}Session) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}};{{end}} }, {{else}} {{range .Normalized.Outputs}}{{bindtype .Type $structs}},{{end}} {{end}} error) {
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.CallOpts {{range .Normalized.Inputs}}, {{.Name}}{{end}})
}
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}.
//
// Solidity: {{formatmethod .Original $structs}}
// Solidity: {{.Original.String}}
func (_{{$contract.Type}} *{{$contract.Type}}CallerSession) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}};{{end}} }, {{else}} {{range .Normalized.Outputs}}{{bindtype .Type $structs}},{{end}} {{end}} error) {
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.CallOpts {{range .Normalized.Inputs}}, {{.Name}}{{end}})
}
@ -332,21 +332,21 @@ var (
{{range .Transacts}}
// {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.ID}}.
//
// Solidity: {{formatmethod .Original $structs}}
// Solidity: {{.Original.String}}
func (_{{$contract.Type}} *{{$contract.Type}}Transactor) {{.Normalized.Name}}(opts *bind.TransactOpts {{range .Normalized.Inputs}}, {{.Name}} {{bindtype .Type $structs}} {{end}}) (*types.Transaction, error) {
return _{{$contract.Type}}.contract.Transact(opts, "{{.Original.Name}}" {{range .Normalized.Inputs}}, {{.Name}}{{end}})
}
// {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.ID}}.
//
// Solidity: {{formatmethod .Original $structs}}
// Solidity: {{.Original.String}}
func (_{{$contract.Type}} *{{$contract.Type}}Session) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) (*types.Transaction, error) {
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.TransactOpts {{range $i, $_ := .Normalized.Inputs}}, {{.Name}}{{end}})
}
// {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.ID}}.
//
// Solidity: {{formatmethod .Original $structs}}
// Solidity: {{.Original.String}}
func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) (*types.Transaction, error) {
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.TransactOpts {{range $i, $_ := .Normalized.Inputs}}, {{.Name}}{{end}})
}
@ -355,21 +355,21 @@ var (
{{if .Fallback}}
// Fallback is a paid mutator transaction binding the contract fallback function.
//
// Solidity: {{formatmethod .Fallback.Original $structs}}
// Solidity: {{.Fallback.Original.String}}
func (_{{$contract.Type}} *{{$contract.Type}}Transactor) Fallback(opts *bind.TransactOpts, calldata []byte) (*types.Transaction, error) {
return _{{$contract.Type}}.contract.RawTransact(opts, calldata)
}
// Fallback is a paid mutator transaction binding the contract fallback function.
//
// Solidity: {{formatmethod .Fallback.Original $structs}}
// Solidity: {{.Fallback.Original.String}}
func (_{{$contract.Type}} *{{$contract.Type}}Session) Fallback(calldata []byte) (*types.Transaction, error) {
return _{{$contract.Type}}.Contract.Fallback(&_{{$contract.Type}}.TransactOpts, calldata)
}
// Fallback is a paid mutator transaction binding the contract fallback function.
//
// Solidity: {{formatmethod .Fallback.Original $structs}}
// Solidity: {{.Fallback.Original.String}}
func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) Fallback(calldata []byte) (*types.Transaction, error) {
return _{{$contract.Type}}.Contract.Fallback(&_{{$contract.Type}}.TransactOpts, calldata)
}
@ -378,21 +378,21 @@ var (
{{if .Receive}}
// Receive is a paid mutator transaction binding the contract receive function.
//
// Solidity: {{formatmethod .Receive.Original $structs}}
// Solidity: {{.Receive.Original.String}}
func (_{{$contract.Type}} *{{$contract.Type}}Transactor) Receive(opts *bind.TransactOpts) (*types.Transaction, error) {
return _{{$contract.Type}}.contract.RawTransact(opts, nil) // calldata is disallowed for receive function
}
// Receive is a paid mutator transaction binding the contract receive function.
//
// Solidity: {{formatmethod .Receive.Original $structs}}
// Solidity: {{.Receive.Original.String}}
func (_{{$contract.Type}} *{{$contract.Type}}Session) Receive() (*types.Transaction, error) {
return _{{$contract.Type}}.Contract.Receive(&_{{$contract.Type}}.TransactOpts)
}
// Receive is a paid mutator transaction binding the contract receive function.
//
// Solidity: {{formatmethod .Receive.Original $structs}}
// Solidity: {{.Receive.Original.String}}
func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) Receive() (*types.Transaction, error) {
return _{{$contract.Type}}.Contract.Receive(&_{{$contract.Type}}.TransactOpts)
}
@ -471,7 +471,7 @@ var (
// Filter{{.Normalized.Name}} is a free log retrieval operation binding the contract event 0x{{printf "%x" .Original.ID}}.
//
// Solidity: {{formatevent .Original $structs}}
// Solidity: {{.Original.String}}
func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Filter{{.Normalized.Name}}(opts *bind.FilterOpts{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}} []{{bindtype .Type $structs}}{{end}}{{end}}) (*{{$contract.Type}}{{.Normalized.Name}}Iterator, error) {
{{range .Normalized.Inputs}}
{{if .Indexed}}var {{.Name}}Rule []interface{}
@ -488,7 +488,7 @@ var (
// Watch{{.Normalized.Name}} is a free log subscription operation binding the contract event 0x{{printf "%x" .Original.ID}}.
//
// Solidity: {{formatevent .Original $structs}}
// Solidity: {{.Original.String}}
func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Watch{{.Normalized.Name}}(opts *bind.WatchOpts, sink chan<- *{{$contract.Type}}{{.Normalized.Name}}{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}} []{{bindtype .Type $structs}}{{end}}{{end}}) (event.Subscription, error) {
{{range .Normalized.Inputs}}
{{if .Indexed}}var {{.Name}}Rule []interface{}
@ -530,7 +530,7 @@ var (
// Parse{{.Normalized.Name}} is a log parse operation binding the contract event 0x{{printf "%x" .Original.ID}}.
//
// Solidity: {{formatevent .Original $structs}}
// Solidity: {{.Original.String}}
func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Parse{{.Normalized.Name}}(log types.Log) (*{{$contract.Type}}{{.Normalized.Name}}, error) {
event := new({{$contract.Type}}{{.Normalized.Name}})
if err := _{{$contract.Type}}.contract.UnpackLog(event, "{{.Original.Name}}", log); err != nil {
@ -662,7 +662,7 @@ import java.util.*;
{{if .Fallback}}
// Fallback is a paid mutator transaction binding the contract fallback function.
//
// Solidity: {{formatmethod .Fallback.Original $structs}}
// Solidity: {{.Fallback.Original.String}}
public Transaction Fallback(TransactOpts opts, byte[] calldata) throws Exception {
return this.Contract.rawTransact(opts, calldata);
}
@ -671,7 +671,7 @@ import java.util.*;
{{if .Receive}}
// Receive is a paid mutator transaction binding the contract receive function.
//
// Solidity: {{formatmethod .Receive.Original $structs}}
// Solidity: {{.Receive.Original.String}}
public Transaction Receive(TransactOpts opts) throws Exception {
return this.Contract.rawTransact(opts, null);
}

View File

@ -1,174 +0,0 @@
// Copyright 2018 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 (
"encoding/binary"
"errors"
"fmt"
"math/big"
"reflect"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
// makeTopics converts a filter query argument list into a filter topic set.
func makeTopics(query ...[]interface{}) ([][]common.Hash, error) {
topics := make([][]common.Hash, len(query))
for i, filter := range query {
for _, rule := range filter {
var topic common.Hash
// Try to generate the topic based on simple types
switch rule := rule.(type) {
case common.Hash:
copy(topic[:], rule[:])
case common.Address:
copy(topic[common.HashLength-common.AddressLength:], rule[:])
case *big.Int:
blob := rule.Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case bool:
if rule {
topic[common.HashLength-1] = 1
}
case int8:
copy(topic[:], genIntType(int64(rule), 1))
case int16:
copy(topic[:], genIntType(int64(rule), 2))
case int32:
copy(topic[:], genIntType(int64(rule), 4))
case int64:
copy(topic[:], genIntType(rule, 8))
case uint8:
blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case uint16:
blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case uint32:
blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case uint64:
blob := new(big.Int).SetUint64(rule).Bytes()
copy(topic[common.HashLength-len(blob):], blob)
case string:
hash := crypto.Keccak256Hash([]byte(rule))
copy(topic[:], hash[:])
case []byte:
hash := crypto.Keccak256Hash(rule)
copy(topic[:], hash[:])
default:
// todo(rjl493456442) according solidity documentation, indexed event
// parameters that are not value types i.e. arrays and structs are not
// stored directly but instead a keccak256-hash of an encoding is stored.
//
// We only convert stringS and bytes to hash, still need to deal with
// array(both fixed-size and dynamic-size) and struct.
// Attempt to generate the topic from funky types
val := reflect.ValueOf(rule)
switch {
// static byte array
case val.Kind() == reflect.Array && reflect.TypeOf(rule).Elem().Kind() == reflect.Uint8:
reflect.Copy(reflect.ValueOf(topic[:val.Len()]), val)
default:
return nil, fmt.Errorf("unsupported indexed type: %T", rule)
}
}
topics[i] = append(topics[i], topic)
}
}
return topics, nil
}
func genIntType(rule int64, size uint) []byte {
var topic [common.HashLength]byte
if rule < 0 {
// if a rule is negative, we need to put it into two's complement.
// extended to common.Hashlength bytes.
topic = [common.HashLength]byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}
}
for i := uint(0); i < size; i++ {
topic[common.HashLength-i-1] = byte(rule >> (i * 8))
}
return topic[:]
}
// parseTopics converts the indexed topic fields into actual log field values.
func parseTopics(out interface{}, fields abi.Arguments, topics []common.Hash) error {
return parseTopicWithSetter(fields, topics,
func(arg abi.Argument, reconstr interface{}) {
field := reflect.ValueOf(out).Elem().FieldByName(capitalise(arg.Name))
field.Set(reflect.ValueOf(reconstr))
})
}
// parseTopicsIntoMap converts the indexed topic field-value pairs into map key-value pairs
func parseTopicsIntoMap(out map[string]interface{}, fields abi.Arguments, topics []common.Hash) error {
return parseTopicWithSetter(fields, topics,
func(arg abi.Argument, reconstr interface{}) {
out[arg.Name] = reconstr
})
}
// parseTopicWithSetter converts the indexed topic field-value pairs and stores them using the
// provided set function.
//
// Note, dynamic types cannot be reconstructed since they get mapped to Keccak256
// hashes as the topic value!
func parseTopicWithSetter(fields abi.Arguments, topics []common.Hash, setter func(abi.Argument, interface{})) error {
// Sanity check that the fields and topics match up
if len(fields) != len(topics) {
return errors.New("topic/field count mismatch")
}
// Iterate over all the fields and reconstruct them from topics
for i, arg := range fields {
if !arg.Indexed {
return errors.New("non-indexed field in topic reconstruction")
}
var reconstr interface{}
switch arg.Type.T {
case abi.TupleTy:
return errors.New("tuple type in topic reconstruction")
case abi.StringTy, abi.BytesTy, abi.SliceTy, abi.ArrayTy:
// Array types (including strings and bytes) have their keccak256 hashes stored in the topic- not a hash
// whose bytes can be decoded to the actual value- so the best we can do is retrieve that hash
reconstr = topics[i]
case abi.FunctionTy:
if garbage := binary.BigEndian.Uint64(topics[i][0:8]); garbage != 0 {
return fmt.Errorf("bind: got improperly encoded function type, got %v", topics[i].Bytes())
}
var tmp [24]byte
copy(tmp[:], topics[i][8:32])
reconstr = tmp
default:
var err error
reconstr, err = abi.ToGoType(0, arg.Type, topics[i].Bytes())
if err != nil {
return err
}
}
// Use the setter function to store the value
setter(arg, reconstr)
}
return nil
}

View File

@ -1,276 +0,0 @@
// Copyright 2019 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 (
"math/big"
"reflect"
"testing"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
func TestMakeTopics(t *testing.T) {
type args struct {
query [][]interface{}
}
tests := []struct {
name string
args args
want [][]common.Hash
wantErr bool
}{
{
"support fixed byte types, right padded to 32 bytes",
args{[][]interface{}{{[5]byte{1, 2, 3, 4, 5}}}},
[][]common.Hash{{common.Hash{1, 2, 3, 4, 5}}},
false,
},
{
"support common hash types in topics",
args{[][]interface{}{{common.Hash{1, 2, 3, 4, 5}}}},
[][]common.Hash{{common.Hash{1, 2, 3, 4, 5}}},
false,
},
{
"support address types in topics",
args{[][]interface{}{{common.Address{1, 2, 3, 4, 5}}}},
[][]common.Hash{{common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5}}},
false,
},
{
"support *big.Int types in topics",
args{[][]interface{}{{big.NewInt(1).Lsh(big.NewInt(2), 254)}}},
[][]common.Hash{{common.Hash{128}}},
false,
},
{
"support boolean types in topics",
args{[][]interface{}{
{true},
{false},
}},
[][]common.Hash{
{common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}},
{common.Hash{0}},
},
false,
},
{
"support int/uint(8/16/32/64) types in topics",
args{[][]interface{}{
{int8(-2)},
{int16(-3)},
{int32(-4)},
{int64(-5)},
{int8(1)},
{int16(256)},
{int32(65536)},
{int64(4294967296)},
{uint8(1)},
{uint16(256)},
{uint32(65536)},
{uint64(4294967296)},
}},
[][]common.Hash{
{common.Hash{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 254}},
{common.Hash{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 253}},
{common.Hash{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 252}},
{common.Hash{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 251}},
{common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}},
{common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0}},
{common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}},
{common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0}},
{common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}},
{common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0}},
{common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}},
{common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0}},
},
false,
},
{
"support string types in topics",
args{[][]interface{}{{"hello world"}}},
[][]common.Hash{{crypto.Keccak256Hash([]byte("hello world"))}},
false,
},
{
"support byte slice types in topics",
args{[][]interface{}{{[]byte{1, 2, 3}}}},
[][]common.Hash{{crypto.Keccak256Hash([]byte{1, 2, 3})}},
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := makeTopics(tt.args.query...)
if (err != nil) != tt.wantErr {
t.Errorf("makeTopics() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("makeTopics() = %v, want %v", got, tt.want)
}
})
}
}
type args struct {
createObj func() interface{}
resultObj func() interface{}
resultMap func() map[string]interface{}
fields abi.Arguments
topics []common.Hash
}
type bytesStruct struct {
StaticBytes [5]byte
}
type int8Struct struct {
Int8Value int8
}
type int256Struct struct {
Int256Value *big.Int
}
type topicTest struct {
name string
args args
wantErr bool
}
func setupTopicsTests() []topicTest {
bytesType, _ := abi.NewType("bytes5", "", nil)
int8Type, _ := abi.NewType("int8", "", nil)
int256Type, _ := abi.NewType("int256", "", nil)
tupleType, _ := abi.NewType("tuple(int256,int8)", "", nil)
tests := []topicTest{
{
name: "support fixed byte types, right padded to 32 bytes",
args: args{
createObj: func() interface{} { return &bytesStruct{} },
resultObj: func() interface{} { return &bytesStruct{StaticBytes: [5]byte{1, 2, 3, 4, 5}} },
resultMap: func() map[string]interface{} {
return map[string]interface{}{"staticBytes": [5]byte{1, 2, 3, 4, 5}}
},
fields: abi.Arguments{abi.Argument{
Name: "staticBytes",
Type: bytesType,
Indexed: true,
}},
topics: []common.Hash{
{1, 2, 3, 4, 5},
},
},
wantErr: false,
},
{
name: "int8 with negative value",
args: args{
createObj: func() interface{} { return &int8Struct{} },
resultObj: func() interface{} { return &int8Struct{Int8Value: -1} },
resultMap: func() map[string]interface{} {
return map[string]interface{}{"int8Value": int8(-1)}
},
fields: abi.Arguments{abi.Argument{
Name: "int8Value",
Type: int8Type,
Indexed: true,
}},
topics: []common.Hash{
{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
},
},
wantErr: false,
},
{
name: "int256 with negative value",
args: args{
createObj: func() interface{} { return &int256Struct{} },
resultObj: func() interface{} { return &int256Struct{Int256Value: big.NewInt(-1)} },
resultMap: func() map[string]interface{} {
return map[string]interface{}{"int256Value": big.NewInt(-1)}
},
fields: abi.Arguments{abi.Argument{
Name: "int256Value",
Type: int256Type,
Indexed: true,
}},
topics: []common.Hash{
{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
},
},
wantErr: false,
},
{
name: "tuple(int256, int8)",
args: args{
createObj: func() interface{} { return nil },
resultObj: func() interface{} { return nil },
resultMap: func() map[string]interface{} { return make(map[string]interface{}) },
fields: abi.Arguments{abi.Argument{
Name: "tupletype",
Type: tupleType,
Indexed: true,
}},
topics: []common.Hash{},
},
wantErr: true,
},
}
return tests
}
func TestParseTopics(t *testing.T) {
tests := setupTopicsTests()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
createObj := tt.args.createObj()
if err := parseTopics(createObj, tt.args.fields, tt.args.topics); (err != nil) != tt.wantErr {
t.Errorf("parseTopics() error = %v, wantErr %v", err, tt.wantErr)
}
resultObj := tt.args.resultObj()
if !reflect.DeepEqual(createObj, resultObj) {
t.Errorf("parseTopics() = %v, want %v", createObj, resultObj)
}
})
}
}
func TestParseTopicsIntoMap(t *testing.T) {
tests := setupTopicsTests()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
outMap := make(map[string]interface{})
if err := parseTopicsIntoMap(outMap, tt.args.fields, tt.args.topics); (err != nil) != tt.wantErr {
t.Errorf("parseTopicsIntoMap() error = %v, wantErr %v", err, tt.wantErr)
}
resultMap := tt.args.resultMap()
if !reflect.DeepEqual(outMap, resultMap) {
t.Errorf("parseTopicsIntoMap() = %v, want %v", outMap, resultMap)
}
})
}
}

View File

@ -18,7 +18,7 @@ package bind
import (
"context"
"fmt"
"errors"
"time"
"github.com/ethereum/go-ethereum/common"
@ -56,14 +56,14 @@ func WaitMined(ctx context.Context, b DeployBackend, tx *types.Transaction) (*ty
// contract address when it is mined. It stops waiting when ctx is canceled.
func WaitDeployed(ctx context.Context, b DeployBackend, tx *types.Transaction) (common.Address, error) {
if tx.To() != nil {
return common.Address{}, fmt.Errorf("tx is not contract creation")
return common.Address{}, errors.New("tx is not contract creation")
}
receipt, err := WaitMined(ctx, b, tx)
if err != nil {
return common.Address{}, err
}
if receipt.ContractAddress == (common.Address{}) {
return common.Address{}, fmt.Errorf("zero address")
return common.Address{}, errors.New("zero address")
}
// Check that code has indeed been deployed at the address.
// This matters on pre-Homestead chains: OOG in the constructor

View File

@ -18,6 +18,7 @@ package bind_test
import (
"context"
"errors"
"math/big"
"testing"
"time"
@ -84,7 +85,7 @@ func TestWaitDeployed(t *testing.T) {
select {
case <-mined:
if err != test.wantErr {
t.Errorf("test %q: error mismatch: got %q, want %q", name, err, test.wantErr)
t.Errorf("test %q: error mismatch: want %q, got %q", name, test.wantErr, err)
}
if address != test.wantAddress {
t.Errorf("test %q: unexpected contract address %s", name, address.Hex())
@ -94,3 +95,40 @@ func TestWaitDeployed(t *testing.T) {
}
}
}
func TestWaitDeployedCornerCases(t *testing.T) {
backend := backends.NewSimulatedBackend(
core.GenesisAlloc{
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000)},
},
10000000,
)
defer backend.Close()
// Create a transaction to an account.
code := "6060604052600a8060106000396000f360606040526008565b00"
tx := types.NewTransaction(0, common.HexToAddress("0x01"), big.NewInt(0), 3000000, big.NewInt(1), common.FromHex(code))
tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
backend.SendTransaction(ctx, tx)
backend.Commit()
notContentCreation := errors.New("tx is not contract creation")
if _, err := bind.WaitDeployed(ctx, backend, tx); err.Error() != notContentCreation.Error() {
t.Errorf("error missmatch: want %q, got %q, ", notContentCreation, err)
}
// Create a transaction that is not mined.
tx = types.NewContractCreation(1, big.NewInt(0), 3000000, big.NewInt(1), common.FromHex(code))
tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey)
go func() {
contextCanceled := errors.New("context canceled")
if _, err := bind.WaitDeployed(ctx, backend, tx); err.Error() != contextCanceled.Error() {
t.Errorf("error missmatch: want %q, got %q, ", contextCanceled, err)
}
}()
backend.SendTransaction(ctx, tx)
cancel()
}