diff options
Diffstat (limited to 'accounts/abi')
-rw-r--r-- | accounts/abi/abi.go | 8 | ||||
-rw-r--r-- | accounts/abi/abi_test.go | 41 | ||||
-rw-r--r-- | accounts/abi/event.go | 8 | ||||
-rw-r--r-- | accounts/abi/event_test.go | 23 | ||||
-rw-r--r-- | accounts/abi/method.go | 9 | ||||
-rw-r--r-- | accounts/abi/unpack.go | 11 | ||||
-rw-r--r-- | accounts/abi/unpack_test.go | 64 |
7 files changed, 125 insertions, 39 deletions
diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index 205dc300b..02b4fa472 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -74,13 +74,17 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) { // Unpack output in v according to the abi specification func (abi ABI) Unpack(v interface{}, name string, output []byte) (err error) { - if err = bytesAreProper(output); err != nil { - return err + if len(output) == 0 { + return fmt.Errorf("abi: unmarshalling empty output") } + // since there can't be naming collisions with contracts and events, // we need to decide whether we're calling a method or an event var unpack unpacker if method, ok := abi.Methods[name]; ok { + if len(output)%32 != 0 { + return fmt.Errorf("abi: improperly formatted output") + } unpack = method } else if event, ok := abi.Events[name]; ok { unpack = event diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index 2775c4b1c..644a388e3 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -18,6 +18,7 @@ package abi import ( "bytes" + "encoding/hex" "fmt" "log" "math/big" @@ -600,3 +601,43 @@ func TestBareEvents(t *testing.T) { } } } + +// TestUnpackEvent is based on this contract: +// contract T { +// event received(address sender, uint amount, bytes memo); +// function receive(bytes memo) external payable { +// received(msg.sender, msg.value, memo); +// } +// } +// When receive("X") is called with sender 0x00... and value 1, it produces this tx receipt: +// receipt{status=1 cgas=23949 bloom=00000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000040200000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 logs=[log: b6818c8064f645cd82d99b59a1a267d6d61117ef [75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed] 000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158 9ae378b6d4409eada347a5dc0c180f186cb62dc68fcc0f043425eb917335aa28 0 95d429d309bb9d753954195fe2d69bd140b4ae731b9b5b605c34323de162cf00 0]} +func TestUnpackEvent(t *testing.T) { + const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` + abi, err := JSON(strings.NewReader(abiJSON)) + if err != nil { + t.Fatal(err) + } + + const hexdata = `000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158` + data, err := hex.DecodeString(hexdata) + if err != nil { + t.Fatal(err) + } + if len(data)%32 == 0 { + t.Errorf("len(data) is %d, want a non-multiple of 32", len(data)) + } + + type ReceivedEvent struct { + Address common.Address + Amount *big.Int + Memo []byte + } + var ev ReceivedEvent + + err = abi.Unpack(&ev, "received", data) + if err != nil { + t.Error(err) + } else { + t.Logf("len(data): %d; received event: %+v", len(data), ev) + } +} diff --git a/accounts/abi/event.go b/accounts/abi/event.go index 44ed7b8df..bd1098d87 100644 --- a/accounts/abi/event.go +++ b/accounts/abi/event.go @@ -71,14 +71,16 @@ func (e Event) tupleUnpack(v interface{}, output []byte) error { if input.Indexed { // can't read, continue continue - } else if input.Type.T == ArrayTy { - // need to move this up because they read sequentially - j += input.Type.Size } marshalledValue, err := toGoType((i+j)*32, input.Type, output) if err != nil { return err } + if input.Type.T == ArrayTy { + // combined index ('i' + 'j') need to be adjusted only by size of array, thus + // we need to decrement 'j' because 'i' was incremented + j += input.Type.Size - 1 + } reflectValue := reflect.ValueOf(marshalledValue) switch value.Kind() { diff --git a/accounts/abi/event_test.go b/accounts/abi/event_test.go index 7e2f13f76..a3899b4a6 100644 --- a/accounts/abi/event_test.go +++ b/accounts/abi/event_test.go @@ -17,11 +17,14 @@ package abi import ( + "bytes" + "reflect" "strings" "testing" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/require" ) func TestEventId(t *testing.T) { @@ -54,3 +57,23 @@ func TestEventId(t *testing.T) { } } } + +// TestEventMultiValueWithArrayUnpack verifies that array fields will be counted after parsing array. +func TestEventMultiValueWithArrayUnpack(t *testing.T) { + definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": false, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"uint8"}]}]` + type testStruct struct { + Value1 [2]uint8 + Value2 uint8 + } + abi, err := JSON(strings.NewReader(definition)) + require.NoError(t, err) + var b bytes.Buffer + var i uint8 = 1 + for ; i <= 3; i++ { + b.Write(packNum(reflect.ValueOf(i))) + } + var rst testStruct + require.NoError(t, abi.Unpack(&rst, "test", b.Bytes())) + require.Equal(t, [2]uint8{1, 2}, rst.Value1) + require.Equal(t, uint8(3), rst.Value2) +} diff --git a/accounts/abi/method.go b/accounts/abi/method.go index 673695aa8..66e8751f3 100644 --- a/accounts/abi/method.go +++ b/accounts/abi/method.go @@ -106,14 +106,15 @@ func (method Method) tupleUnpack(v interface{}, output []byte) error { j := 0 for i := 0; i < len(method.Outputs); i++ { toUnpack := method.Outputs[i] - if toUnpack.Type.T == ArrayTy { - // need to move this up because they read sequentially - j += toUnpack.Type.Size - } marshalledValue, err := toGoType((i+j)*32, toUnpack.Type, output) if err != nil { return err } + if toUnpack.Type.T == ArrayTy { + // combined index ('i' + 'j') need to be adjusted only by size of array, thus + // we need to decrement 'j' because 'i' was incremented + j += toUnpack.Type.Size - 1 + } reflectValue := reflect.ValueOf(marshalledValue) switch value.Kind() { diff --git a/accounts/abi/unpack.go b/accounts/abi/unpack.go index 57732797b..051fc1916 100644 --- a/accounts/abi/unpack.go +++ b/accounts/abi/unpack.go @@ -203,14 +203,3 @@ func lengthPrefixPointsTo(index int, output []byte) (start int, length int, err //fmt.Printf("LENGTH PREFIX INFO: \nsize: %v\noffset: %v\nstart: %v\n", length, offset, start) return } - -// checks for proper formatting of byte output -func bytesAreProper(output []byte) error { - if len(output) == 0 { - return fmt.Errorf("abi: unmarshalling empty output") - } else if len(output)%32 != 0 { - return fmt.Errorf("abi: improperly formatted output") - } else { - return nil - } -} diff --git a/accounts/abi/unpack_test.go b/accounts/abi/unpack_test.go index 1e21aafc0..14393d230 100644 --- a/accounts/abi/unpack_test.go +++ b/accounts/abi/unpack_test.go @@ -22,6 +22,7 @@ import ( "fmt" "math/big" "reflect" + "strconv" "strings" "testing" @@ -261,25 +262,27 @@ var unpackTests = []unpackTest{ func TestUnpack(t *testing.T) { for i, test := range unpackTests { - def := fmt.Sprintf(`[{ "name" : "method", "outputs": %s}]`, test.def) - abi, err := JSON(strings.NewReader(def)) - if err != nil { - t.Fatalf("invalid ABI definition %s: %v", def, err) - } - encb, err := hex.DecodeString(test.enc) - if err != nil { - t.Fatalf("invalid hex: %s" + test.enc) - } - outptr := reflect.New(reflect.TypeOf(test.want)) - err = abi.Unpack(outptr.Interface(), "method", encb) - if err := test.checkError(err); err != nil { - t.Errorf("test %d (%v) failed: %v", i, test.def, err) - continue - } - out := outptr.Elem().Interface() - if !reflect.DeepEqual(test.want, out) { - t.Errorf("test %d (%v) failed: expected %v, got %v", i, test.def, test.want, out) - } + t.Run(strconv.Itoa(i), func(t *testing.T) { + def := fmt.Sprintf(`[{ "name" : "method", "outputs": %s}]`, test.def) + abi, err := JSON(strings.NewReader(def)) + if err != nil { + t.Fatalf("invalid ABI definition %s: %v", def, err) + } + encb, err := hex.DecodeString(test.enc) + if err != nil { + t.Fatalf("invalid hex: %s" + test.enc) + } + outptr := reflect.New(reflect.TypeOf(test.want)) + err = abi.Unpack(outptr.Interface(), "method", encb) + if err := test.checkError(err); err != nil { + t.Errorf("test %d (%v) failed: %v", i, test.def, err) + return + } + out := outptr.Elem().Interface() + if !reflect.DeepEqual(test.want, out) { + t.Errorf("test %d (%v) failed: expected %v, got %v", i, test.def, test.want, out) + } + }) } } @@ -336,6 +339,29 @@ func TestMultiReturnWithStruct(t *testing.T) { } } +func TestMultiReturnWithArray(t *testing.T) { + const definition = `[{"name" : "multi", "outputs": [{"type": "uint64[3]"}, {"type": "uint64"}]}]` + abi, err := JSON(strings.NewReader(definition)) + if err != nil { + t.Fatal(err) + } + buff := new(bytes.Buffer) + buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000900000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000009")) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000008")) + + ret1, ret1Exp := new([3]uint64), [3]uint64{9, 9, 9} + ret2, ret2Exp := new(uint64), uint64(8) + if err := abi.Unpack(&[]interface{}{ret1, ret2}, "multi", buff.Bytes()); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(*ret1, ret1Exp) { + t.Error("array result", *ret1, "!= Expected", ret1Exp) + } + if *ret2 != ret2Exp { + t.Error("int result", *ret2, "!= Expected", ret2Exp) + } +} + func TestUnmarshal(t *testing.T) { const definition = `[ { "name" : "int", "constant" : false, "outputs": [ { "type": "uint256" } ] }, |