From 6fe917ecb86fddbf6132e7cedd1a0455796451f4 Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Sat, 13 Feb 2016 01:42:23 +0100 Subject: [PATCH] accounts/abi: support for typed array Added support for fixed size and arbitrary length byte arrays to be marshallable in fixed size (typed) byte slices. --- accounts/abi/abi.go | 51 ++++++++++++++----- accounts/abi/abi_test.go | 106 +++++++++++++++++++++++++++------------ 2 files changed, 114 insertions(+), 43 deletions(-) diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index 2dc8039f5d..324d3c76f1 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -165,7 +165,14 @@ func (abi ABI) Call(executer Executer, v interface{}, name string, args ...inter return abi.unmarshal(v, name, executer(callData)) } -var interSlice = reflect.TypeOf([]interface{}{}) +// these variable are used to determine certain types during type assertion for +// assignment. +var ( + r_interSlice = reflect.TypeOf([]interface{}{}) + r_hash = reflect.TypeOf(common.Hash{}) + r_bytes = reflect.TypeOf([]byte{}) + r_byte = reflect.TypeOf(byte(0)) +) // unmarshal output in v according to the abi specification func (abi ABI) unmarshal(v interface{}, name string, output []byte) error { @@ -194,17 +201,14 @@ func (abi ABI) unmarshal(v interface{}, name string, output []byte) error { field := typ.Field(j) // TODO read tags: `abi:"fieldName"` if field.Name == strings.ToUpper(method.Outputs[i].Name[:1])+method.Outputs[i].Name[1:] { - if field.Type.AssignableTo(reflectValue.Type()) { - value.Field(j).Set(reflectValue) - break - } else { - return fmt.Errorf("abi: cannot unmarshal %v in to %v", field.Type, reflectValue.Type()) + if err := set(value.Field(j), reflectValue, method.Outputs[i]); err != nil { + return err } } } } case reflect.Slice: - if !value.Type().AssignableTo(interSlice) { + if !value.Type().AssignableTo(r_interSlice) { return fmt.Errorf("abi: cannot marshal tuple in to slice %T (only []interface{} is supported)", v) } @@ -228,17 +232,40 @@ func (abi ABI) unmarshal(v interface{}, name string, output []byte) error { if err != nil { return err } - reflectValue := reflect.ValueOf(marshalledValue) - if typ.AssignableTo(reflectValue.Type()) { - value.Set(reflectValue) - } else { - return fmt.Errorf("abi: cannot unmarshal %v in to %v", reflectValue.Type(), value.Type()) + if err := set(value, reflect.ValueOf(marshalledValue), method.Outputs[0]); err != nil { + return err } } return nil } +// set attempts to assign src to dst by either setting, copying or otherwise. +// +// set is a bit more lenient when it comes to assignment and doesn't force an as +// strict ruleset as bare `reflect` does. +func set(dst, src reflect.Value, output Argument) error { + dstType := dst.Type() + srcType := src.Type() + + switch { + case dstType.AssignableTo(src.Type()): + dst.Set(src) + case dstType.Kind() == reflect.Array && srcType.Kind() == reflect.Slice: + if !dstType.Elem().AssignableTo(r_byte) { + return fmt.Errorf("abi: cannot unmarshal %v in to array of elem %v", src.Type(), dstType.Elem()) + } + + if dst.Len() < output.Type.Size { + return fmt.Errorf("abi: cannot unmarshal src (len=%d) in to dst (len=%d)", output.Type.Size, dst.Len()) + } + reflect.Copy(dst, src) + default: + return fmt.Errorf("abi: cannot unmarshal %v in to %v", src.Type(), dst.Type()) + } + return nil +} + func (abi *ABI) UnmarshalJSON(data []byte) error { var fields []struct { Type string diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index bb0143d21e..c6a8705cd3 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -394,37 +394,6 @@ func TestBytes(t *testing.T) { } } -/* -func TestReturn(t *testing.T) { - const definition = `[ - { "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 { - t.Fatal(err) - } - - r := abi.Call(func([]byte) []byte { - t := make([]byte, 32) - t[0] = 1 - return t - }, "balance") - if _, ok := r.(common.Hash); !ok { - t.Errorf("expected type common.Hash, got %T", r) - } - - r = abi.Call(func([]byte) []byte { - t := make([]byte, 32) - t[0] = 1 - return t - }, "name") - if _, ok := r.(common.Address); !ok { - t.Errorf("expected type common.Address, got %T", r) - } -} -*/ - func TestDefaultFunctionParsing(t *testing.T) { const definition = `[{ "name" : "balance" }]` @@ -550,11 +519,71 @@ func TestMultiReturnWithSlice(t *testing.T) { } } +func TestMarshalArrays(t *testing.T) { + const definition = `[ + { "name" : "bytes32", "const" : false, "outputs": [ { "type": "bytes32" } ] }, + { "name" : "bytes10", "const" : false, "outputs": [ { "type": "bytes10" } ] } + ]` + + abi, err := JSON(strings.NewReader(definition)) + if err != nil { + t.Fatal(err) + } + + output := common.LeftPadBytes([]byte{1}, 32) + + var bytes10 [10]byte + err = abi.unmarshal(&bytes10, "bytes32", output) + if err == nil || err.Error() != "abi: cannot unmarshal src (len=32) in to dst (len=10)" { + t.Error("expected error or bytes32 not be assignable to bytes10:", err) + } + + var bytes32 [32]byte + err = abi.unmarshal(&bytes32, "bytes32", output) + if err != nil { + t.Error("didn't expect error:", err) + } + if !bytes.Equal(bytes32[:], output) { + t.Error("expected bytes32[31] to be 1 got", bytes32[31]) + } + + type ( + B10 [10]byte + B32 [32]byte + ) + + var b10 B10 + err = abi.unmarshal(&b10, "bytes32", output) + if err == nil || err.Error() != "abi: cannot unmarshal src (len=32) in to dst (len=10)" { + t.Error("expected error or bytes32 not be assignable to bytes10:", err) + } + + var b32 B32 + err = abi.unmarshal(&b32, "bytes32", output) + if err != nil { + t.Error("didn't expect error:", err) + } + if !bytes.Equal(b32[:], output) { + t.Error("expected bytes32[31] to be 1 got", bytes32[31]) + } + + output[10] = 1 + var shortAssignLong [32]byte + err = abi.unmarshal(&shortAssignLong, "bytes10", output) + if err != nil { + t.Error("didn't expect error:", err) + } + if !bytes.Equal(output, shortAssignLong[:]) { + t.Errorf("expected %x to be %x", shortAssignLong, output) + } +} + func TestUnmarshal(t *testing.T) { const definition = `[ { "name" : "int", "const" : false, "outputs": [ { "type": "uint256" } ] }, { "name" : "bool", "const" : false, "outputs": [ { "type": "bool" } ] }, { "name" : "bytes", "const" : false, "outputs": [ { "type": "bytes" } ] }, + { "name" : "fixed", "const" : false, "outputs": [ { "type": "bytes32" } ] }, { "name" : "multi", "const" : false, "outputs": [ { "type": "bytes" }, { "type": "bytes" } ] }, { "name" : "mixedBytes", "const" : true, "outputs": [ { "name": "a", "type": "bytes" }, { "name": "b", "type": "bytes32" } ] }]` @@ -655,6 +684,21 @@ func TestUnmarshal(t *testing.T) { t.Errorf("expected %x got %x", bytesOut, Bytes) } + // marshal dynamic bytes length 5 + buff.Reset() + buff.Write(common.RightPadBytes([]byte("hello"), 32)) + + var hash common.Hash + err = abi.unmarshal(&hash, "fixed", buff.Bytes()) + if err != nil { + t.Error(err) + } + + helloHash := common.BytesToHash(common.RightPadBytes([]byte("hello"), 32)) + if hash != helloHash { + t.Errorf("Expected %x to equal %x", hash, helloHash) + } + // marshal error buff.Reset() buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020"))