diff --git a/accounts/abi/argument.go b/accounts/abi/argument.go index c0c87c530..5c1e391f5 100644 --- a/accounts/abi/argument.go +++ b/accounts/abi/argument.go @@ -275,7 +275,7 @@ func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error) { retval := make([]interface{}, 0, len(nonIndexedArgs)) virtualArgs := 0 for index, arg := range nonIndexedArgs { - marshalledValue, err := ToGoType((index+virtualArgs)*32, arg.Type, data) + marshalledValue, err := toGoType((index+virtualArgs)*32, arg.Type, data) if arg.Type.T == ArrayTy && !isDynamicType(arg.Type) { // If we have a static array, like [3]uint256, these are coded as // just like uint256,uint256,uint256. @@ -312,7 +312,7 @@ func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) { // Make sure arguments match up and pack them abiArgs := arguments if len(args) != len(abiArgs) { - return nil, fmt.Errorf("argument count mismatch: %d for %d", len(args), len(abiArgs)) + return nil, fmt.Errorf("argument count mismatch: got %d for %d", len(args), len(abiArgs)) } // variable input is the output appended at the end of packed // output. This is used for strings and bytes types input. diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index 1d6811d74..a602e5e02 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -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 diff --git a/accounts/abi/bind/base_test.go b/accounts/abi/bind/base_test.go index 970340431..7d287850f 100644 --- a/accounts/abi/bind/base_test.go +++ b/accounts/abi/bind/base_test.go @@ -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" diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index 4c6a9e9ce..7aaa85bdc 100644 --- a/accounts/abi/bind/bind.go +++ b/accounts/abi/bind/bind.go @@ -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, ", ")) -} diff --git a/accounts/abi/bind/template.go b/accounts/abi/bind/template.go index ec36409c2..3a2dff1dd 100644 --- a/accounts/abi/bind/template.go +++ b/accounts/abi/bind/template.go @@ -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); } diff --git a/accounts/abi/bind/util.go b/accounts/abi/bind/util.go index d129993ca..118abc59a 100644 --- a/accounts/abi/bind/util.go +++ b/accounts/abi/bind/util.go @@ -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 diff --git a/accounts/abi/bind/util_test.go b/accounts/abi/bind/util_test.go index e0141f46e..9f9b7a000 100644 --- a/accounts/abi/bind/util_test.go +++ b/accounts/abi/bind/util_test.go @@ -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() +} diff --git a/accounts/abi/bind/topics.go b/accounts/abi/topics.go similarity index 84% rename from accounts/abi/bind/topics.go rename to accounts/abi/topics.go index 7b64f0334..474b9a4a7 100644 --- a/accounts/abi/bind/topics.go +++ b/accounts/abi/topics.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package bind +package abi import ( "encoding/binary" @@ -23,13 +23,12 @@ import ( "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) { +// 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 { @@ -112,19 +111,19 @@ func genIntType(rule int64, size uint) []byte { return topic[:] } -// parseTopics converts the indexed topic fields into actual log field values. -func parseTopics(out interface{}, fields abi.Arguments, topics []common.Hash) error { +// ParseTopics converts the indexed topic fields into actual log field values. +func ParseTopics(out interface{}, fields Arguments, topics []common.Hash) error { return parseTopicWithSetter(fields, topics, - func(arg abi.Argument, reconstr interface{}) { - field := reflect.ValueOf(out).Elem().FieldByName(capitalise(arg.Name)) + func(arg Argument, reconstr interface{}) { + field := reflect.ValueOf(out).Elem().FieldByName(ToCamelCase(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 { +// ParseTopicsIntoMap converts the indexed topic field-value pairs into map key-value pairs +func ParseTopicsIntoMap(out map[string]interface{}, fields Arguments, topics []common.Hash) error { return parseTopicWithSetter(fields, topics, - func(arg abi.Argument, reconstr interface{}) { + func(arg Argument, reconstr interface{}) { out[arg.Name] = reconstr }) } @@ -134,7 +133,7 @@ func parseTopicsIntoMap(out map[string]interface{}, fields abi.Arguments, topics // // 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 { +func parseTopicWithSetter(fields Arguments, topics []common.Hash, setter func(Argument, interface{})) error { // Sanity check that the fields and topics match up if len(fields) != len(topics) { return errors.New("topic/field count mismatch") @@ -146,13 +145,13 @@ func parseTopicWithSetter(fields abi.Arguments, topics []common.Hash, setter fun } var reconstr interface{} switch arg.Type.T { - case abi.TupleTy: + case TupleTy: return errors.New("tuple type in topic reconstruction") - case abi.StringTy, abi.BytesTy, abi.SliceTy, abi.ArrayTy: + case StringTy, BytesTy, SliceTy, 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: + case 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()) } @@ -161,7 +160,7 @@ func parseTopicWithSetter(fields abi.Arguments, topics []common.Hash, setter fun reconstr = tmp default: var err error - reconstr, err = abi.ToGoType(0, arg.Type, topics[i].Bytes()) + reconstr, err = toGoType(0, arg.Type, topics[i].Bytes()) if err != nil { return err } diff --git a/accounts/abi/bind/topics_test.go b/accounts/abi/topics_test.go similarity index 67% rename from accounts/abi/bind/topics_test.go rename to accounts/abi/topics_test.go index 627e43316..4a539a711 100644 --- a/accounts/abi/bind/topics_test.go +++ b/accounts/abi/topics_test.go @@ -14,14 +14,13 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package bind +package abi import ( "math/big" "reflect" "testing" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" ) @@ -119,7 +118,7 @@ func TestMakeTopics(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := makeTopics(tt.args.query...) + got, err := MakeTopics(tt.args.query...) if (err != nil) != tt.wantErr { t.Errorf("makeTopics() error = %v, wantErr %v", err, tt.wantErr) return @@ -135,7 +134,7 @@ type args struct { createObj func() interface{} resultObj func() interface{} resultMap func() map[string]interface{} - fields abi.Arguments + fields Arguments topics []common.Hash } @@ -149,6 +148,14 @@ type int256Struct struct { Int256Value *big.Int } +type hashStruct struct { + HashValue common.Hash +} + +type funcStruct struct { + FuncValue [24]byte +} + type topicTest struct { name string args args @@ -156,10 +163,12 @@ type topicTest struct { } 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) + bytesType, _ := NewType("bytes5", "", nil) + int8Type, _ := NewType("int8", "", nil) + int256Type, _ := NewType("int256", "", nil) + tupleType, _ := NewType("tuple(int256,int8)", "", nil) + stringType, _ := NewType("string", "", nil) + funcType, _ := NewType("function", "", nil) tests := []topicTest{ { @@ -170,7 +179,7 @@ func setupTopicsTests() []topicTest { resultMap: func() map[string]interface{} { return map[string]interface{}{"staticBytes": [5]byte{1, 2, 3, 4, 5}} }, - fields: abi.Arguments{abi.Argument{ + fields: Arguments{Argument{ Name: "staticBytes", Type: bytesType, Indexed: true, @@ -189,7 +198,7 @@ func setupTopicsTests() []topicTest { resultMap: func() map[string]interface{} { return map[string]interface{}{"int8Value": int8(-1)} }, - fields: abi.Arguments{abi.Argument{ + fields: Arguments{Argument{ Name: "int8Value", Type: int8Type, Indexed: true, @@ -209,7 +218,7 @@ func setupTopicsTests() []topicTest { resultMap: func() map[string]interface{} { return map[string]interface{}{"int256Value": big.NewInt(-1)} }, - fields: abi.Arguments{abi.Argument{ + fields: Arguments{Argument{ Name: "int256Value", Type: int256Type, Indexed: true, @@ -222,12 +231,55 @@ func setupTopicsTests() []topicTest { wantErr: false, }, { - name: "tuple(int256, int8)", + name: "hash type", + args: args{ + createObj: func() interface{} { return &hashStruct{} }, + resultObj: func() interface{} { return &hashStruct{crypto.Keccak256Hash([]byte("stringtopic"))} }, + resultMap: func() map[string]interface{} { + return map[string]interface{}{"hashValue": crypto.Keccak256Hash([]byte("stringtopic"))} + }, + fields: Arguments{Argument{ + Name: "hashValue", + Type: stringType, + Indexed: true, + }}, + topics: []common.Hash{ + crypto.Keccak256Hash([]byte("stringtopic")), + }, + }, + wantErr: false, + }, + { + name: "function type", + args: args{ + createObj: func() interface{} { return &funcStruct{} }, + resultObj: func() interface{} { + return &funcStruct{[24]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}} + }, + resultMap: func() map[string]interface{} { + return map[string]interface{}{"funcValue": [24]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}} + }, + fields: Arguments{Argument{ + Name: "funcValue", + Type: funcType, + Indexed: true, + }}, + topics: []common.Hash{ + {0, 0, 0, 0, 0, 0, 0, 0, 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: "error on topic/field count mismatch", 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{ + fields: Arguments{Argument{ Name: "tupletype", Type: tupleType, Indexed: true, @@ -236,6 +288,59 @@ func setupTopicsTests() []topicTest { }, wantErr: true, }, + { + name: "error on unindexed arguments", + args: args{ + createObj: func() interface{} { return &int256Struct{} }, + resultObj: func() interface{} { return &int256Struct{} }, + resultMap: func() map[string]interface{} { return make(map[string]interface{}) }, + fields: Arguments{Argument{ + Name: "int256Value", + Type: int256Type, + Indexed: false, + }}, + 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: true, + }, + { + name: "error on tuple in topic reconstruction", + args: args{ + createObj: func() interface{} { return &tupleType }, + resultObj: func() interface{} { return &tupleType }, + resultMap: func() map[string]interface{} { return make(map[string]interface{}) }, + fields: Arguments{Argument{ + Name: "tupletype", + Type: tupleType, + Indexed: true, + }}, + topics: []common.Hash{{0}}, + }, + wantErr: true, + }, + { + name: "error on improper encoded function", + args: args{ + createObj: func() interface{} { return &funcStruct{} }, + resultObj: func() interface{} { return &funcStruct{} }, + resultMap: func() map[string]interface{} { + return make(map[string]interface{}) + }, + fields: Arguments{Argument{ + Name: "funcValue", + Type: funcType, + Indexed: true, + }}, + topics: []common.Hash{ + {0, 0, 0, 0, 0, 0, 0, 128, 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: true, + }, } return tests @@ -247,7 +352,7 @@ func TestParseTopics(t *testing.T) { 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 { + 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() @@ -264,7 +369,7 @@ func TestParseTopicsIntoMap(t *testing.T) { 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 { + 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() diff --git a/accounts/abi/unpack.go b/accounts/abi/unpack.go index 6c8fce0cb..c206c6ab2 100644 --- a/accounts/abi/unpack.go +++ b/accounts/abi/unpack.go @@ -144,7 +144,7 @@ func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) elemSize := getTypeSize(*t.Elem) for i, j := start, 0; j < size; i, j = i+elemSize, j+1 { - inter, err := ToGoType(i, *t.Elem, output) + inter, err := toGoType(i, *t.Elem, output) if err != nil { return nil, err } @@ -161,7 +161,7 @@ func forTupleUnpack(t Type, output []byte) (interface{}, error) { retval := reflect.New(t.getType()).Elem() virtualArgs := 0 for index, elem := range t.TupleElems { - marshalledValue, err := ToGoType((index+virtualArgs)*32, *elem, output) + marshalledValue, err := toGoType((index+virtualArgs)*32, *elem, output) if elem.T == ArrayTy && !isDynamicType(*elem) { // If we have a static array, like [3]uint256, these are coded as // just like uint256,uint256,uint256. @@ -187,9 +187,9 @@ func forTupleUnpack(t Type, output []byte) (interface{}, error) { return retval.Interface(), nil } -// ToGoType parses the output bytes and recursively assigns the value of these bytes +// toGoType parses the output bytes and recursively assigns the value of these bytes // into a go type with accordance with the ABI spec. -func ToGoType(index int, t Type, output []byte) (interface{}, error) { +func toGoType(index int, t Type, output []byte) (interface{}, error) { if index+32 > len(output) { return nil, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %d require %d", len(output), index+32) } @@ -218,9 +218,8 @@ func ToGoType(index int, t Type, output []byte) (interface{}, error) { return nil, err } return forTupleUnpack(t, output[begin:]) - } else { - return forTupleUnpack(t, output[index:]) } + return forTupleUnpack(t, output[index:]) case SliceTy: return forEachUnpack(t, output[begin:], 0, length) case ArrayTy: