From 022cbd680052eb87b32d5f59587957779d382c0c Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Wed, 30 Mar 2016 16:22:02 +0200 Subject: [PATCH] abi: accept input slices of all supported types --- accounts/abi/abi.go | 7 +-- accounts/abi/abi_test.go | 119 ++++++++++++++--------------------- accounts/abi/numbers.go | 2 - accounts/abi/numbers_test.go | 4 -- accounts/abi/type.go | 119 +++++++++++++++++------------------ 5 files changed, 106 insertions(+), 145 deletions(-) diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index 91f9700d99..01603b2179 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -63,9 +63,8 @@ func (abi ABI) pack(method Method, args ...interface{}) ([]byte, error) { return nil, fmt.Errorf("`%s` %v", method.Name, err) } - // check for a string or bytes input type - switch input.Type.T { - case StringTy, BytesTy: + // check for a slice type (string, bytes, slice) + if input.Type.T == StringTy || input.Type.T == BytesTy || input.Type.IsSlice { // calculate the offset offset := len(method.Inputs)*32 + len(variableInput) // set the offset @@ -73,7 +72,7 @@ func (abi ABI) pack(method Method, args ...interface{}) ([]byte, error) { // Append the packed output to the variable input. The variable input // will be appended at the end of the input. variableInput = append(variableInput, packed...) - default: + } else { // append the packed value to the input ret = append(ret, packed...) } diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index 66d2e1b39c..db33face92 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -49,7 +49,9 @@ const jsondata2 = ` { "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]" } ] } + { "type" : "function", "name" : "slice256", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint256[2]" } ] }, + { "type" : "function", "name" : "sliceAddress", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "address[]" } ] }, + { "type" : "function", "name" : "sliceMultiAddress", "const" : false, "inputs" : [ { "name" : "a", "type" : "address[]" }, { "name" : "b", "type" : "address[]" } ] } ]` func TestType(t *testing.T) { @@ -68,7 +70,7 @@ func TestType(t *testing.T) { if typ.Kind != reflect.Slice { t.Error("expected uint32[] to have type slice") } - if typ.Type != ubig_ts { + if typ.Type != ubig_t { t.Error("expcted uith32[] to have type uint64") } @@ -79,7 +81,7 @@ func TestType(t *testing.T) { if typ.Kind != reflect.Slice { t.Error("expected uint32[2] to have kind slice") } - if typ.Type != ubig_ts { + if typ.Type != ubig_t { t.Error("expcted uith32[2] to have type uint64") } if typ.Size != 2 { @@ -202,16 +204,6 @@ func TestTestSlice(t *testing.T) { t.FailNow() } - addr := make([]byte, 20) - if _, err := abi.Pack("address", addr); err != nil { - t.Error(err) - } - - addr = make([]byte, 21) - if _, err := abi.Pack("address", addr); err == nil { - t.Error("expected address of 21 width to throw") - } - slice := make([]byte, 2) if _, err := abi.Pack("uint64[2]", slice); err != nil { t.Error(err) @@ -222,19 +214,6 @@ func TestTestSlice(t *testing.T) { } } -func TestTestAddress(t *testing.T) { - abi, err := JSON(strings.NewReader(jsondata2)) - if err != nil { - t.Error(err) - t.FailNow() - } - - addr := make([]byte, 20) - if _, err := abi.Pack("address", addr); err != nil { - t.Error(err) - } -} - func TestMethodSignature(t *testing.T) { String, _ := NewType("string") String32, _ := NewType("string32") @@ -310,44 +289,69 @@ func TestPackSlice(t *testing.T) { } sig := crypto.Keccak256([]byte("slice(uint32[2])"))[:4] - sig = append(sig, make([]byte, 64)...) - sig[35] = 1 - sig[67] = 2 + sig = append(sig, common.LeftPadBytes([]byte{32}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) packed, err := abi.Pack("slice", []uint32{1, 2}) if err != nil { t.Error(err) - t.FailNow() } if !bytes.Equal(packed, sig) { t.Errorf("expected %x got %x", sig, packed) } -} -func TestPackSliceBig(t *testing.T) { - abi, err := JSON(strings.NewReader(jsondata2)) + var addrA, addrB = common.Address{1}, common.Address{2} + sig = abi.Methods["sliceAddress"].Id() + sig = append(sig, common.LeftPadBytes([]byte{32}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) + sig = append(sig, common.LeftPadBytes(addrA[:], 32)...) + sig = append(sig, common.LeftPadBytes(addrB[:], 32)...) + + packed, err = abi.Pack("sliceAddress", []common.Address{addrA, addrB}) if err != nil { - t.Error(err) - t.FailNow() + t.Fatal(err) + } + if !bytes.Equal(packed, sig) { + t.Errorf("expected %x got %x", sig, packed) } - sig := crypto.Keccak256([]byte("slice256(uint256[2])"))[:4] - sig = append(sig, make([]byte, 64)...) - sig[35] = 1 - sig[67] = 2 + var addrC, addrD = common.Address{3}, common.Address{4} + sig = abi.Methods["sliceMultiAddress"].Id() + sig = append(sig, common.LeftPadBytes([]byte{64}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{160}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) + sig = append(sig, common.LeftPadBytes(addrA[:], 32)...) + sig = append(sig, common.LeftPadBytes(addrB[:], 32)...) + sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) + sig = append(sig, common.LeftPadBytes(addrC[:], 32)...) + sig = append(sig, common.LeftPadBytes(addrD[:], 32)...) + + packed, err = abi.Pack("sliceMultiAddress", []common.Address{addrA, addrB}, []common.Address{addrC, addrD}) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(packed, sig) { + t.Errorf("expected %x got %x", sig, packed) + } - packed, err := abi.Pack("slice256", []*big.Int{big.NewInt(1), big.NewInt(2)}) + sig = crypto.Keccak256([]byte("slice256(uint256[2])"))[:4] + sig = append(sig, common.LeftPadBytes([]byte{32}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) + + packed, err = abi.Pack("slice256", []*big.Int{big.NewInt(1), big.NewInt(2)}) if err != nil { t.Error(err) - t.FailNow() } if !bytes.Equal(packed, sig) { t.Errorf("expected %x got %x", sig, packed) } } - func ExampleJSON() { const definition = `[{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"isBar","outputs":[{"name":"","type":"bool"}],"type":"function"}]` @@ -370,7 +374,7 @@ func TestInputVariableInputLength(t *testing.T) { { "type" : "function", "name" : "strOne", "const" : true, "inputs" : [ { "name" : "str", "type" : "string" } ] }, { "type" : "function", "name" : "bytesOne", "const" : true, "inputs" : [ { "name" : "str", "type" : "bytes" } ] }, { "type" : "function", "name" : "strTwo", "const" : true, "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "str1", "type" : "string" } ] } -]` + ]` abi, err := JSON(strings.NewReader(definition)) if err != nil { @@ -493,35 +497,6 @@ func TestInputVariableInputLength(t *testing.T) { } } -func TestBytes(t *testing.T) { - const definition = `[ - { "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)) - if err != nil { - t.Fatal(err) - } - ok := make([]byte, 20) - _, err = abi.Pack("balance", ok) - if err != nil { - t.Error(err) - } - - toosmall := make([]byte, 19) - _, err = abi.Pack("balance", toosmall) - if err != nil { - t.Error(err) - } - - toobig := make([]byte, 21) - _, err = abi.Pack("balance", toobig) - if err == nil { - t.Error("expected error") - } -} - func TestDefaultFunctionParsing(t *testing.T) { const definition = `[{ "name" : "balance" }]` diff --git a/accounts/abi/numbers.go b/accounts/abi/numbers.go index 02609d567a..084701de5b 100644 --- a/accounts/abi/numbers.go +++ b/accounts/abi/numbers.go @@ -117,8 +117,6 @@ func packNum(value reflect.Value, to byte) []byte { // checks whether the given reflect value is signed. This also works for slices with a number type func isSigned(v reflect.Value) bool { switch v.Type() { - case ubig_ts, big_ts, big_t, ubig_t: - return true case int_ts, int8_ts, int16_ts, int32_ts, int64_ts, int_t, int8_t, int16_t, int32_t, int64_t: return true } diff --git a/accounts/abi/numbers_test.go b/accounts/abi/numbers_test.go index 78dc57543b..6590e41a6f 100644 --- a/accounts/abi/numbers_test.go +++ b/accounts/abi/numbers_test.go @@ -81,8 +81,4 @@ func TestSigned(t *testing.T) { if !isSigned(reflect.ValueOf(int(10))) { t.Error() } - - if !isSigned(reflect.ValueOf(big.NewInt(10))) { - t.Error() - } } diff --git a/accounts/abi/type.go b/accounts/abi/type.go index c08b744f7c..18cd04672c 100644 --- a/accounts/abi/type.go +++ b/accounts/abi/type.go @@ -96,63 +96,57 @@ func NewType(t string) (typ Type, err error) { t += "256" } - if isslice { + switch vtype { + case "int": + typ.Kind = reflect.Ptr + typ.Type = big_t + typ.Size = 256 + typ.T = IntTy + case "uint": + typ.Kind = reflect.Ptr + typ.Type = ubig_t + typ.Size = 256 + typ.T = UintTy + case "bool": + typ.Kind = reflect.Bool + typ.T = BoolTy + case "real": // TODO + typ.Kind = reflect.Invalid + case "address": typ.Kind = reflect.Slice - typ.Size = size - switch vtype { - case "int": - typ.Type = big_ts - case "uint": - typ.Type = ubig_ts - default: - return Type{}, fmt.Errorf("unsupported arg slice type: %s", t) - } - } else { - switch vtype { - case "int": - typ.Kind = reflect.Ptr - typ.Type = big_t - typ.Size = 256 - typ.T = IntTy - case "uint": - typ.Kind = reflect.Ptr - typ.Type = ubig_t - typ.Size = 256 - typ.T = UintTy - case "bool": - typ.Kind = reflect.Bool - typ.T = BoolTy - case "real": // TODO - typ.Kind = reflect.Invalid - case "address": - typ.Kind = reflect.Slice - typ.Type = address_t - typ.Size = 20 - typ.T = AddressTy - case "string": - typ.Kind = reflect.String - typ.Size = -1 - typ.T = StringTy - if vsize > 0 { - typ.Size = 32 - } - case "hash": - typ.Kind = reflect.Slice + typ.Type = address_t + typ.Size = 20 + typ.T = AddressTy + case "string": + typ.Kind = reflect.String + typ.Size = -1 + typ.T = StringTy + if vsize > 0 { typ.Size = 32 - typ.Type = hash_t - typ.T = HashTy - case "bytes": - typ.Kind = reflect.Slice - typ.Type = byte_ts - typ.Size = vsize - if vsize == 0 { - typ.T = BytesTy - } else { - typ.T = FixedBytesTy - } - default: - return Type{}, fmt.Errorf("unsupported arg type: %s", t) } + case "hash": + typ.Kind = reflect.Slice + typ.Size = 32 + typ.Type = hash_t + typ.T = HashTy + case "bytes": + typ.Kind = reflect.Slice + typ.Type = byte_ts + typ.Size = vsize + if vsize == 0 { + typ.T = BytesTy + } else { + typ.T = FixedBytesTy + } + default: + return Type{}, fmt.Errorf("unsupported arg type: %s", t) + } + + // if the type is a slice we must set Kind to a reflect.Slice + // so that serialisation can be determined based on this kind. + if isslice { + typ.Kind = reflect.Slice + typ.Size = size } typ.stringKind = t @@ -203,7 +197,7 @@ func (t Type) pack(v interface{}) ([]byte, error) { return packBytesSlice([]byte(value.String()), value.Len()), nil case reflect.Slice: - // if the param is a bytes type, pack the slice up as a string + // Byte slice is a special case, it gets treated as a single value if t.T == BytesTy { return packBytesSlice(value.Bytes(), value.Len()), nil } @@ -212,21 +206,20 @@ func (t Type) pack(v interface{}) ([]byte, error) { return nil, fmt.Errorf("%v out of bound. %d for %d", value.Kind(), value.Len(), t.Size) } - // Address is a special slice. The slice acts as one rather than a list of elements. - if t.T == AddressTy { - return common.LeftPadBytes(v.([]byte), 32), nil - } - // Signed / Unsigned check - if (t.T != IntTy && isSigned(value)) || (t.T == UintTy && isSigned(value)) { + if value.Type() == big_t && (t.T != IntTy && isSigned(value)) || (t.T == UintTy && isSigned(value)) { return nil, fmt.Errorf("slice of incompatible types.") } var packed []byte for i := 0; i < value.Len(); i++ { - packed = append(packed, packNum(value.Index(i), t.T)...) + val, err := t.pack(value.Index(i).Interface()) + if err != nil { + return nil, err + } + packed = append(packed, val...) } - return packed, nil + return packBytesSlice(packed, value.Len()), nil case reflect.Bool: if value.Bool() { return common.LeftPadBytes(common.Big1.Bytes(), 32), nil