From 022cbd680052eb87b32d5f59587957779d382c0c Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Wed, 30 Mar 2016 16:22:02 +0200 Subject: [PATCH 1/3] abi: accept input slices of all supported types --- accounts/abi/abi.go | 7 +- accounts/abi/abi_test.go | 143 +++++++++++++++-------------------- accounts/abi/numbers.go | 2 - accounts/abi/numbers_test.go | 4 - accounts/abi/type.go | 119 ++++++++++++++--------------- 5 files changed, 118 insertions(+), 157 deletions(-) diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index 91f9700d9..01603b217 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 66d2e1b39..db33face9 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) + } + + 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.Fatal(err) + } + if !bytes.Equal(packed, sig) { + t.Errorf("expected %x got %x", sig, packed) + } + + 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) + } + + 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) } if !bytes.Equal(packed, sig) { t.Errorf("expected %x got %x", sig, packed) } } - -func TestPackSliceBig(t *testing.T) { - abi, err := JSON(strings.NewReader(jsondata2)) - if err != nil { - t.Error(err) - t.FailNow() - } - - sig := crypto.Keccak256([]byte("slice256(uint256[2])"))[:4] - sig = append(sig, make([]byte, 64)...) - sig[35] = 1 - sig[67] = 2 - - 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 02609d567..084701de5 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 78dc57543..6590e41a6 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 c08b744f7..18cd04672 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" } + 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.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 - 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.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) - } } 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 From 968d8ffe942f6ef3776b43a73fb0fe85eb955a68 Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Thu, 31 Mar 2016 11:38:31 +0200 Subject: [PATCH 2/3] abi: accept output slices of all supported types --- accounts/abi/abi.go | 72 +++++++++++++++++++++++++++++++++++++++- accounts/abi/abi_test.go | 71 ++++++++++++++++++++++++++++++++++++++- accounts/abi/type.go | 5 ++- 3 files changed, 143 insertions(+), 5 deletions(-) diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index 01603b217..b0765e7db 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -20,6 +20,7 @@ import ( "encoding/json" "fmt" "io" + "math/big" "reflect" "strings" @@ -116,11 +117,80 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) { return append(method.Id(), arguments...), nil } +// toGoSliceType prses the input and casts it to the proper slice defined by the ABI +// argument in T. +func toGoSlice(i int, t Argument, output []byte) (interface{}, error) { + index := i * 32 + // The slice must, at very least be large enough for the index+32 which is exactly the size required + // for the [offset in output, size of offset]. + if index+32 > len(output) { + return nil, fmt.Errorf("abi: cannot marshal in to go slice: insufficient size output %d require %d", len(output), index+32) + } + + // first we need to create a slice of the type + var refSlice reflect.Value + switch t.Type.T { + case IntTy, UintTy, BoolTy: // int, uint, bool can all be of type big int. + refSlice = reflect.ValueOf([]*big.Int(nil)) + case AddressTy: // address must be of slice Address + refSlice = reflect.ValueOf([]common.Address(nil)) + case HashTy: // hash must be of slice hash + refSlice = reflect.ValueOf([]common.Hash(nil)) + default: // no other types are supported + return nil, fmt.Errorf("abi: unsupported slice type %v", t.Type.T) + } + // get the offset which determines the start of this array ... + offset := int(common.BytesToBig(output[index : index+32]).Uint64()) + if offset+32 > len(output) { + return nil, fmt.Errorf("abi: cannot marshal in to go slice: offset %d would go over slice boundary (len=%d)", len(output), offset+32) + } + + slice := output[offset:] + // ... starting with the size of the array in elements ... + size := int(common.BytesToBig(slice[:32]).Uint64()) + slice = slice[32:] + // ... and make sure that we've at the very least the amount of bytes + // available in the buffer. + if size*32 > len(slice) { + return nil, fmt.Errorf("abi: cannot marshal in to go slice: insufficient size output %d require %d", len(output), offset+32+size*32) + } + + // reslice to match the required size + slice = slice[:(size * 32)] + for i := 0; i < size; i++ { + var ( + inter interface{} // interface type + returnOutput = slice[i*32 : i*32+32] // the return output + ) + + // set inter to the correct type (cast) + switch t.Type.T { + case IntTy, UintTy: + inter = common.BytesToBig(returnOutput) + case BoolTy: + inter = common.BytesToBig(returnOutput).Uint64() > 0 + case AddressTy: + inter = common.BytesToAddress(returnOutput) + case HashTy: + inter = common.BytesToHash(returnOutput) + } + // append the item to our reflect slice + refSlice = reflect.Append(refSlice, reflect.ValueOf(inter)) + } + + // return the interface + return refSlice.Interface(), nil +} + // toGoType parses the input and casts it to the proper type defined by the ABI // argument in T. func toGoType(i int, t Argument, output []byte) (interface{}, error) { - index := i * 32 + // we need to treat slices differently + if t.Type.Kind == reflect.Slice { + return toGoSlice(i, t, output) + } + index := i * 32 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) } diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index db33face9..a0f0d1034 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -688,12 +688,15 @@ func TestUnmarshal(t *testing.T) { { "name" : "bytes", "const" : false, "outputs": [ { "type": "bytes" } ] }, { "name" : "fixed", "const" : false, "outputs": [ { "type": "bytes32" } ] }, { "name" : "multi", "const" : false, "outputs": [ { "type": "bytes" }, { "type": "bytes" } ] }, + { "name" : "addressSliceSingle", "const" : false, "outputs": [ { "type": "address[]" } ] }, + { "name" : "addressSliceDouble", "const" : false, "outputs": [ { "name": "a", "type": "address[]" }, { "name": "b", "type": "address[]" } ] }, { "name" : "mixedBytes", "const" : true, "outputs": [ { "name": "a", "type": "bytes" }, { "name": "b", "type": "bytes32" } ] }]` abi, err := JSON(strings.NewReader(definition)) if err != nil { t.Fatal(err) } + buff := new(bytes.Buffer) // marshal int var Int *big.Int @@ -718,7 +721,6 @@ func TestUnmarshal(t *testing.T) { } // marshal dynamic bytes max length 32 - buff := new(bytes.Buffer) buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) bytesOut := common.RightPadBytes([]byte("hello"), 32) @@ -837,4 +839,71 @@ func TestUnmarshal(t *testing.T) { if !bytes.Equal(fixed, out[1].([]byte)) { t.Errorf("expected %x, got %x", fixed, out[1]) } + + // marshal address slice + buff.Reset() + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) // offset + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // size + buff.Write(common.Hex2Bytes("0000000000000000000000000100000000000000000000000000000000000000")) + + var outAddr []common.Address + err = abi.Unpack(&outAddr, "addressSliceSingle", buff.Bytes()) + if err != nil { + t.Fatal("didn't expect error:", err) + } + + if len(outAddr) != 1 { + t.Fatal("expected 1 item, got", len(outAddr)) + } + + if outAddr[0] != (common.Address{1}) { + t.Errorf("expected %x, got %x", common.Address{1}, outAddr[0]) + } + + // marshal multiple address slice + buff.Reset() + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040")) // offset + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000080")) // offset + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // size + buff.Write(common.Hex2Bytes("0000000000000000000000000100000000000000000000000000000000000000")) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) // size + buff.Write(common.Hex2Bytes("0000000000000000000000000200000000000000000000000000000000000000")) + buff.Write(common.Hex2Bytes("0000000000000000000000000300000000000000000000000000000000000000")) + + var outAddrStruct struct { + A []common.Address + B []common.Address + } + err = abi.Unpack(&outAddrStruct, "addressSliceDouble", buff.Bytes()) + if err != nil { + t.Fatal("didn't expect error:", err) + } + + if len(outAddrStruct.A) != 1 { + t.Fatal("expected 1 item, got", len(outAddrStruct.A)) + } + + if outAddrStruct.A[0] != (common.Address{1}) { + t.Errorf("expected %x, got %x", common.Address{1}, outAddrStruct.A[0]) + } + + if len(outAddrStruct.B) != 2 { + t.Fatal("expected 1 item, got", len(outAddrStruct.B)) + } + + if outAddrStruct.B[0] != (common.Address{2}) { + t.Errorf("expected %x, got %x", common.Address{2}, outAddrStruct.B[0]) + } + if outAddrStruct.B[1] != (common.Address{3}) { + t.Errorf("expected %x, got %x", common.Address{3}, outAddrStruct.B[1]) + } + + // marshal invalid address slice + buff.Reset() + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000100")) + + err = abi.Unpack(&outAddr, "addressSliceSingle", buff.Bytes()) + if err == nil { + t.Fatal("expected error:", err) + } } diff --git a/accounts/abi/type.go b/accounts/abi/type.go index 18cd04672..b7ce6a13b 100644 --- a/accounts/abi/type.go +++ b/accounts/abi/type.go @@ -113,7 +113,6 @@ func NewType(t string) (typ Type, err error) { case "real": // TODO typ.Kind = reflect.Invalid case "address": - typ.Kind = reflect.Slice typ.Type = address_t typ.Size = 20 typ.T = AddressTy @@ -125,12 +124,12 @@ func NewType(t string) (typ Type, err error) { typ.Size = 32 } case "hash": - typ.Kind = reflect.Slice + typ.Kind = reflect.Array typ.Size = 32 typ.Type = hash_t typ.T = HashTy case "bytes": - typ.Kind = reflect.Slice + typ.Kind = reflect.Array typ.Type = byte_ts typ.Size = vsize if vsize == 0 { From a306e17a26e96e383afe86a2f1cdfa2320ec0f2f Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Thu, 31 Mar 2016 23:54:47 +0200 Subject: [PATCH 3/3] abi: removed implicit type casting & refactored type parsing --- accounts/abi/abi.go | 2 +- accounts/abi/abi_test.go | 33 +++++++++----- accounts/abi/type.go | 98 +++++++++++++++++++++++----------------- 3 files changed, 80 insertions(+), 53 deletions(-) diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index b0765e7db..9ef7c0f0d 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -186,7 +186,7 @@ func toGoSlice(i int, t Argument, output []byte) (interface{}, error) { // argument in T. func toGoType(i int, t Argument, output []byte) (interface{}, error) { // we need to treat slices differently - if t.Type.Kind == reflect.Slice { + if t.Type.IsSlice { return toGoSlice(i, t, output) } diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index a0f0d1034..a1b3e62d9 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -59,7 +59,7 @@ func TestType(t *testing.T) { if err != nil { t.Error(err) } - if typ.Kind != reflect.Ptr { + if typ.Kind != reflect.Uint { t.Error("expected uint32 to have kind Ptr") } @@ -67,8 +67,8 @@ func TestType(t *testing.T) { if err != nil { t.Error(err) } - if typ.Kind != reflect.Slice { - t.Error("expected uint32[] to have type slice") + if !typ.IsSlice { + t.Error("expected uint32[] to be slice") } if typ.Type != ubig_t { t.Error("expcted uith32[] to have type uint64") @@ -78,13 +78,13 @@ func TestType(t *testing.T) { if err != nil { t.Error(err) } - if typ.Kind != reflect.Slice { - t.Error("expected uint32[2] to have kind slice") + if !typ.IsSlice { + t.Error("expected uint32[2] to be slice") } if typ.Type != ubig_t { t.Error("expcted uith32[2] to have type uint64") } - if typ.Size != 2 { + if typ.SliceSize != 2 { t.Error("expected uint32[2] to have a size of 2") } } @@ -149,10 +149,6 @@ func TestTestNumbers(t *testing.T) { t.Errorf("expected send( ptr ) to throw, requires *big.Int instead of *int") } - if _, err := abi.Pack("send", 1000); err != nil { - t.Error("expected send(1000) to cast to big") - } - if _, err := abi.Pack("test", uint32(1000)); err != nil { t.Error(err) } @@ -204,7 +200,7 @@ func TestTestSlice(t *testing.T) { t.FailNow() } - slice := make([]byte, 2) + slice := make([]uint64, 2) if _, err := abi.Pack("uint64[2]", slice); err != nil { t.Error(err) } @@ -214,6 +210,21 @@ func TestTestSlice(t *testing.T) { } } +func TestImplicitTypeCasts(t *testing.T) { + abi, err := JSON(strings.NewReader(jsondata2)) + if err != nil { + t.Error(err) + t.FailNow() + } + + slice := make([]uint8, 2) + _, err = abi.Pack("uint64[2]", slice) + expStr := "`uint64[2]` abi: cannot use type uint8 as type uint64" + if err.Error() != expStr { + t.Errorf("expected %v, got %v", expStr, err) + } +} + func TestMethodSignature(t *testing.T) { String, _ := NewType("string") String32, _ := NewType("string32") diff --git a/accounts/abi/type.go b/accounts/abi/type.go index b7ce6a13b..5a5a5ac49 100644 --- a/accounts/abi/type.go +++ b/accounts/abi/type.go @@ -40,6 +40,9 @@ const ( // Type is the reflection of the supported argument type type Type struct { + IsSlice bool + SliceSize int + Kind reflect.Kind Type reflect.Type Size int @@ -47,6 +50,11 @@ type Type struct { stringKind string // holds the unparsed string for deriving signatures } +var ( + fullTypeRegex = regexp.MustCompile("([a-zA-Z0-9]+)(\\[([0-9]*)?\\])?") + typeRegex = regexp.MustCompile("([a-zA-Z]+)([0-9]*)?") +) + // NewType returns a fully parsed Type given by the input string or an error if it can't be parsed. // // Strings can be in the format of: @@ -61,51 +69,54 @@ type Type struct { // address int256 uint256 real[2] func NewType(t string) (typ Type, err error) { // 1. full string 2. type 3. (opt.) is slice 4. (opt.) size - freg, err := regexp.Compile("([a-zA-Z0-9]+)(\\[([0-9]*)?\\])?") - if err != nil { - return Type{}, err - } - res := freg.FindAllStringSubmatch(t, -1)[0] - var ( - isslice bool - size int - ) + // parse the full representation of the abi-type definition; including: + // * full string + // * type + // * is slice + // * slice size + res := fullTypeRegex.FindAllStringSubmatch(t, -1)[0] + + // check if type is slice and parse type. switch { case res[3] != "": // err is ignored. Already checked for number through the regexp - size, _ = strconv.Atoi(res[3]) - isslice = true + typ.SliceSize, _ = strconv.Atoi(res[3]) + typ.IsSlice = true case res[2] != "": - isslice = true - size = -1 + typ.IsSlice, typ.SliceSize = true, -1 case res[0] == "": - return Type{}, fmt.Errorf("type parse error for `%s`", t) + return Type{}, fmt.Errorf("abi: type parse error: %s", t) } - treg, err := regexp.Compile("([a-zA-Z]+)([0-9]*)?") - if err != nil { - return Type{}, err + // parse the type and size of the abi-type. + parsedType := typeRegex.FindAllStringSubmatch(res[1], -1)[0] + // varSize is the size of the variable + var varSize int + if len(parsedType[2]) > 0 { + var err error + varSize, err = strconv.Atoi(parsedType[2]) + if err != nil { + return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err) + } } - - parsedType := treg.FindAllStringSubmatch(res[1], -1)[0] - vsize, _ := strconv.Atoi(parsedType[2]) - vtype := parsedType[1] - // substitute canonical representation - if vsize == 0 && (vtype == "int" || vtype == "uint") { - vsize = 256 + // varType is the parsed abi type + varType := parsedType[1] + // substitute canonical integer + if varSize == 0 && (varType == "int" || varType == "uint") { + varSize = 256 t += "256" } - switch vtype { + switch varType { case "int": - typ.Kind = reflect.Ptr + typ.Kind = reflect.Int typ.Type = big_t - typ.Size = 256 + typ.Size = varSize typ.T = IntTy case "uint": - typ.Kind = reflect.Ptr + typ.Kind = reflect.Uint typ.Type = ubig_t - typ.Size = 256 + typ.Size = varSize typ.T = UintTy case "bool": typ.Kind = reflect.Bool @@ -120,7 +131,7 @@ func NewType(t string) (typ Type, err error) { typ.Kind = reflect.String typ.Size = -1 typ.T = StringTy - if vsize > 0 { + if varSize > 0 { typ.Size = 32 } case "hash": @@ -131,8 +142,8 @@ func NewType(t string) (typ Type, err error) { case "bytes": typ.Kind = reflect.Array typ.Type = byte_ts - typ.Size = vsize - if vsize == 0 { + typ.Size = varSize + if varSize == 0 { typ.T = BytesTy } else { typ.T = FixedBytesTy @@ -140,13 +151,6 @@ func NewType(t string) (typ Type, err error) { 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 return @@ -173,14 +177,26 @@ func (t Type) pack(v interface{}) ([]byte, error) { value := reflect.ValueOf(v) switch kind := value.Kind(); kind { case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + // check input is unsigned if t.Type != ubig_t { - return nil, fmt.Errorf("type mismatch: %s for %T", t.Type, v) + return nil, fmt.Errorf("abi: type mismatch: %s for %T", t.Type, v) } + + // no implicit type casting + if int(value.Type().Size()*8) != t.Size { + return nil, fmt.Errorf("abi: cannot use type %T as type uint%d", v, t.Size) + } + return packNum(value, t.T), nil case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: if t.Type != ubig_t { return nil, fmt.Errorf("type mismatch: %s for %T", t.Type, v) } + + // no implicit type casting + if int(value.Type().Size()*8) != t.Size { + return nil, fmt.Errorf("abi: cannot use type %T as type uint%d", v, t.Size) + } return packNum(value, t.T), nil case reflect.Ptr: // If the value is a ptr do a assign check (only used by @@ -201,7 +217,7 @@ func (t Type) pack(v interface{}) ([]byte, error) { return packBytesSlice(value.Bytes(), value.Len()), nil } - if t.Size > -1 && value.Len() > t.Size { + if t.SliceSize > -1 && value.Len() > t.SliceSize { return nil, fmt.Errorf("%v out of bound. %d for %d", value.Kind(), value.Len(), t.Size) }