diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index 635dc43fe..b84fd463a 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -37,6 +37,7 @@ type Executer func(datain []byte) []byte // packs data accordingly. type ABI struct { Methods map[string]Method + Events map[string]Event } // JSON returns a parsed ABI interface and error if it failed. @@ -149,14 +150,37 @@ func (abi ABI) Call(executer Executer, name string, args ...interface{}) interfa } func (abi *ABI) UnmarshalJSON(data []byte) error { - var methods []Method - if err := json.Unmarshal(data, &methods); err != nil { + var fields []struct { + Type string + Name string + Const bool + Indexed bool + Inputs []Argument + Outputs []Argument + } + + if err := json.Unmarshal(data, &fields); err != nil { return err } abi.Methods = make(map[string]Method) - for _, method := range methods { - abi.Methods[method.Name] = method + abi.Events = make(map[string]Event) + for _, field := range fields { + switch field.Type { + // empty defaults to function according to the abi spec + case "function", "": + abi.Methods[field.Name] = Method{ + Name: field.Name, + Const: field.Const, + Inputs: field.Inputs, + Outputs: field.Outputs, + } + case "event": + abi.Events[field.Name] = Event{ + Name: field.Name, + Inputs: field.Inputs, + } + } } return nil diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index d514fb8c7..000c118f8 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -31,25 +31,25 @@ import ( const jsondata = ` [ - { "name" : "balance", "const" : true }, - { "name" : "send", "const" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] } + { "type" : "function", "name" : "balance", "const" : true }, + { "type" : "function", "name" : "send", "const" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] } ]` const jsondata2 = ` [ - { "name" : "balance", "const" : true }, - { "name" : "send", "const" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] }, - { "name" : "test", "const" : false, "inputs" : [ { "name" : "number", "type" : "uint32" } ] }, - { "name" : "string", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "string" } ] }, - { "name" : "bool", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "bool" } ] }, - { "name" : "address", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "address" } ] }, - { "name" : "string32", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "string32" } ] }, - { "name" : "uint64[2]", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint64[2]" } ] }, - { "name" : "uint64[]", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint64[]" } ] }, - { "name" : "foo", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32" } ] }, - { "name" : "bar", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32" }, { "name" : "string", "type" : "uint16" } ] }, - { "name" : "slice", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32[2]" } ] }, - { "name" : "slice256", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint256[2]" } ] } + { "type" : "function", "name" : "balance", "const" : true }, + { "type" : "function", "name" : "send", "const" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] }, + { "type" : "function", "name" : "test", "const" : false, "inputs" : [ { "name" : "number", "type" : "uint32" } ] }, + { "type" : "function", "name" : "string", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "string" } ] }, + { "type" : "function", "name" : "bool", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "bool" } ] }, + { "type" : "function", "name" : "address", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "address" } ] }, + { "type" : "function", "name" : "string32", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "string32" } ] }, + { "type" : "function", "name" : "uint64[2]", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint64[2]" } ] }, + { "type" : "function", "name" : "uint64[]", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint64[]" } ] }, + { "type" : "function", "name" : "foo", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32" } ] }, + { "type" : "function", "name" : "bar", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32" }, { "name" : "string", "type" : "uint16" } ] }, + { "type" : "function", "name" : "slice", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32[2]" } ] }, + { "type" : "function", "name" : "slice256", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint256[2]" } ] } ]` func TestType(t *testing.T) { @@ -96,7 +96,7 @@ func TestReader(t *testing.T) { }, "send": Method{ "send", false, []Argument{ - Argument{"amount", Uint256}, + Argument{"amount", Uint256, false}, }, nil, }, }, @@ -238,7 +238,7 @@ func TestTestAddress(t *testing.T) { func TestMethodSignature(t *testing.T) { String, _ := NewType("string") String32, _ := NewType("string32") - m := Method{"foo", false, []Argument{Argument{"bar", String32}, Argument{"baz", String}}, nil} + m := Method{"foo", false, []Argument{Argument{"bar", String32, false}, Argument{"baz", String, false}}, nil} exp := "foo(string32,string)" if m.Sig() != exp { t.Error("signature mismatch", exp, "!=", m.Sig()) @@ -250,7 +250,7 @@ func TestMethodSignature(t *testing.T) { } uintt, _ := NewType("uint") - m = Method{"foo", false, []Argument{Argument{"bar", uintt}}, nil} + m = Method{"foo", false, []Argument{Argument{"bar", uintt, false}}, nil} exp = "foo(uint256)" if m.Sig() != exp { t.Error("signature mismatch", exp, "!=", m.Sig()) @@ -367,8 +367,8 @@ func ExampleJSON() { func TestBytes(t *testing.T) { const definition = `[ - { "name" : "balance", "const" : true, "inputs" : [ { "name" : "address", "type" : "bytes20" } ] }, - { "name" : "send", "const" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] } + { "type" : "function", "name" : "balance", "const" : true, "inputs" : [ { "name" : "address", "type" : "bytes20" } ] }, + { "type" : "function", "name" : "send", "const" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] } ]` abi, err := JSON(strings.NewReader(definition)) @@ -396,10 +396,8 @@ func TestBytes(t *testing.T) { func TestReturn(t *testing.T) { const definition = `[ - { "name" : "balance", "const" : true, "inputs" : [], "outputs" : [ { "name": "", "type": "hash" } ] }, - { "name" : "name", "const" : true, "inputs" : [], "outputs" : [ { "name": "", "type": "address" } ] } - -]` + { "type" : "function", "name" : "balance", "const" : true, "inputs" : [], "outputs" : [ { "name": "", "type": "hash" } ] }, + { "type" : "function", "name" : "name", "const" : true, "inputs" : [], "outputs" : [ { "name": "", "type": "address" } ] }]` abi, err := JSON(strings.NewReader(definition)) if err != nil { @@ -424,3 +422,39 @@ func TestReturn(t *testing.T) { t.Errorf("expected type common.Address, got %T", r) } } + +func TestDefaultFunctionParsing(t *testing.T) { + const definition = `[{ "name" : "balance" }]` + + abi, err := JSON(strings.NewReader(definition)) + if err != nil { + t.Fatal(err) + } + + if _, ok := abi.Methods["balance"]; !ok { + t.Error("expected 'balance' to be present") + } +} + +func TestBareEvents(t *testing.T) { + const definition = `[ + { "type" : "event", "name" : "balance" }, + { "type" : "event", "name" : "name" }]` + + abi, err := JSON(strings.NewReader(definition)) + if err != nil { + t.Fatal(err) + } + + if len(abi.Events) != 2 { + t.Error("expected 2 events") + } + + if _, ok := abi.Events["balance"]; !ok { + t.Error("expected 'balance' event to be present") + } + + if _, ok := abi.Events["name"]; !ok { + t.Error("expected 'name' event to be present") + } +} diff --git a/accounts/abi/argument.go b/accounts/abi/argument.go index 8907b2979..188203a5d 100644 --- a/accounts/abi/argument.go +++ b/accounts/abi/argument.go @@ -24,8 +24,9 @@ import ( // Argument holds the name of the argument and the corresponding type. // Types are used when packing and testing arguments. type Argument struct { - Name string - Type Type + Name string + Type Type + Indexed bool // indexed is only used by events } func (a *Argument) UnmarshalJSON(data []byte) error { diff --git a/accounts/abi/event.go b/accounts/abi/event.go new file mode 100644 index 000000000..7c4e092ea --- /dev/null +++ b/accounts/abi/event.go @@ -0,0 +1,44 @@ +// Copyright 2016 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 . + +package abi + +import ( + "fmt" + "strings" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +// Event is an event potentially triggered by the EVM's LOG mechanism. The Event +// holds type information (inputs) about the yielded output +type Event struct { + Name string + Inputs []Argument +} + +// Id returns the canonical representation of the event's signature used by the +// abi definition to identify event names and types. +func (e Event) Id() common.Hash { + types := make([]string, len(e.Inputs)) + i := 0 + for _, input := range e.Inputs { + types[i] = input.Type.String() + i++ + } + return common.BytesToHash(crypto.Sha3([]byte(fmt.Sprintf("%v(%v)", e.Name, strings.Join(types, ","))))) +} diff --git a/accounts/abi/event_test.go b/accounts/abi/event_test.go new file mode 100644 index 000000000..34a7a1684 --- /dev/null +++ b/accounts/abi/event_test.go @@ -0,0 +1,40 @@ +package abi + +import ( + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +func TestEventId(t *testing.T) { + var table = []struct { + definition string + expectations map[string]common.Hash + }{ + { + definition: `[ + { "type" : "event", "name" : "balance", "inputs": [{ "name" : "in", "type": "uint" }] }, + { "type" : "event", "name" : "check", "inputs": [{ "name" : "t", "type": "address" }, { "name": "b", "type": "uint256" }] } + ]`, + expectations: map[string]common.Hash{ + "balance": crypto.Sha3Hash([]byte("balance(uint256)")), + "check": crypto.Sha3Hash([]byte("check(address,uint256)")), + }, + }, + } + + for _, test := range table { + abi, err := JSON(strings.NewReader(test.definition)) + if err != nil { + t.Fatal(err) + } + + for name, event := range abi.Events { + if event.Id() != test.expectations[name] { + t.Errorf("expected id to be %x, got %x", test.expectations[name], event.Id()) + } + } + } +} diff --git a/accounts/abi/type.go b/accounts/abi/type.go index 8f0238fc9..32f761ef0 100644 --- a/accounts/abi/type.go +++ b/accounts/abi/type.go @@ -218,5 +218,5 @@ func (t Type) pack(v interface{}) ([]byte, error) { } } - return nil, fmt.Errorf("ABI: bad input given %T", value.Kind()) + return nil, fmt.Errorf("ABI: bad input given %v", value.Kind()) }