From fe45210c552f5de2ec6293817dc0363a34d0ebfb Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Tue, 15 Mar 2016 14:06:12 +0100 Subject: [PATCH] accounts/abi: Fixed bytes input accept []byte and variable input support Fixed up `[]byte` slice support such that `function print(bytes input)` accepts `[]byte` as input and treats it as 1 element rather than a slice of multiple elements. Added support for variable length input parameters like `bytes` and `strings`. --- accounts/abi/abi.go | 23 ++++++- accounts/abi/abi_test.go | 128 +++++++++++++++++++++++++++++++++++++++ accounts/abi/numbers.go | 58 +++++++++--------- accounts/abi/type.go | 15 ++++- 4 files changed, 193 insertions(+), 31 deletions(-) diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index 324d3c76f..e299d43c8 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -56,17 +56,36 @@ func JSON(reader io.Reader) (ABI, error) { func (abi ABI) pack(name string, args ...interface{}) ([]byte, error) { method := abi.Methods[name] + // variable input is the output appended at the end of packed + // output. This is used for strings and bytes types input. + var variableInput []byte + var ret []byte for i, a := range args { input := method.Inputs[i] - + // pack the input packed, err := input.Type.pack(a) if err != nil { return nil, fmt.Errorf("`%s` %v", name, err) } - ret = append(ret, packed...) + // check for a string or bytes input type + switch input.Type.T { + case StringTy, BytesTy: + // calculate the offset + offset := len(method.Inputs)*32 + len(variableInput) + // set the offset + ret = append(ret, packNum(reflect.ValueOf(offset), UintTy)...) + // 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: + // append the packed value to the input + ret = append(ret, packed...) + } } + // append the variable input at the end of the packed input + ret = append(ret, variableInput...) return ret, nil } diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index d1b8330e3..170f3f74b 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -365,6 +365,134 @@ func ExampleJSON() { // 1f2c40920000000000000000000000000000000000000000000000000000000000000001 } +func TestInputVariableInputLength(t *testing.T) { + const definition = `[ + { "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 { + t.Fatal(err) + } + + // test one string + strin := "hello world" + strpack, err := abi.Pack("strOne", strin) + if err != nil { + t.Error(err) + } + + offset := make([]byte, 32) + offset[31] = 32 + length := make([]byte, 32) + length[31] = byte(len(strin)) + value := common.RightPadBytes([]byte(strin), 32) + exp := append(offset, append(length, value...)...) + + // ignore first 4 bytes of the output. This is the function identifier + strpack = strpack[4:] + if !bytes.Equal(strpack, exp) { + t.Errorf("expected %x, got %x\n", exp, strpack) + } + + // test one bytes + btspack, err := abi.Pack("bytesOne", []byte(strin)) + if err != nil { + t.Error(err) + } + // ignore first 4 bytes of the output. This is the function identifier + btspack = btspack[4:] + if !bytes.Equal(btspack, exp) { + t.Errorf("expected %x, got %x\n", exp, btspack) + } + + // test two strings + str1 := "hello" + str2 := "world" + str2pack, err := abi.Pack("strTwo", str1, str2) + if err != nil { + t.Error(err) + } + + offset1 := make([]byte, 32) + offset1[31] = 64 + length1 := make([]byte, 32) + length1[31] = byte(len(str1)) + value1 := common.RightPadBytes([]byte(str1), 32) + + offset2 := make([]byte, 32) + offset2[31] = 128 + length2 := make([]byte, 32) + length2[31] = byte(len(str2)) + value2 := common.RightPadBytes([]byte(str2), 32) + + exp2 := append(offset1, offset2...) + exp2 = append(exp2, append(length1, value1...)...) + exp2 = append(exp2, append(length2, value2...)...) + + // ignore first 4 bytes of the output. This is the function identifier + str2pack = str2pack[4:] + if !bytes.Equal(str2pack, exp2) { + t.Errorf("expected %x, got %x\n", exp, str2pack) + } + + // test two strings, first > 32, second < 32 + str1 = strings.Repeat("a", 33) + str2pack, err = abi.Pack("strTwo", str1, str2) + if err != nil { + t.Error(err) + } + + offset1 = make([]byte, 32) + offset1[31] = 64 + length1 = make([]byte, 32) + length1[31] = byte(len(str1)) + value1 = common.RightPadBytes([]byte(str1), 64) + offset2[31] = 160 + + exp2 = append(offset1, offset2...) + exp2 = append(exp2, append(length1, value1...)...) + exp2 = append(exp2, append(length2, value2...)...) + + // ignore first 4 bytes of the output. This is the function identifier + str2pack = str2pack[4:] + if !bytes.Equal(str2pack, exp2) { + t.Errorf("expected %x, got %x\n", exp, str2pack) + } + + // test two strings, first > 32, second >32 + str1 = strings.Repeat("a", 33) + str2 = strings.Repeat("a", 33) + str2pack, err = abi.Pack("strTwo", str1, str2) + if err != nil { + t.Error(err) + } + + offset1 = make([]byte, 32) + offset1[31] = 64 + length1 = make([]byte, 32) + length1[31] = byte(len(str1)) + value1 = common.RightPadBytes([]byte(str1), 64) + + offset2 = make([]byte, 32) + offset2[31] = 160 + length2 = make([]byte, 32) + length2[31] = byte(len(str2)) + value2 = common.RightPadBytes([]byte(str2), 64) + + exp2 = append(offset1, offset2...) + exp2 = append(exp2, append(length1, value1...)...) + exp2 = append(exp2, append(length2, value2...)...) + + // ignore first 4 bytes of the output. This is the function identifier + str2pack = str2pack[4:] + if !bytes.Equal(str2pack, exp2) { + t.Errorf("expected %x, got %x\n", exp, str2pack) + } +} + func TestBytes(t *testing.T) { const definition = `[ { "type" : "function", "name" : "balance", "const" : true, "inputs" : [ { "name" : "address", "type" : "bytes20" } ] }, diff --git a/accounts/abi/numbers.go b/accounts/abi/numbers.go index c37cd5f68..02609d567 100644 --- a/accounts/abi/numbers.go +++ b/accounts/abi/numbers.go @@ -23,36 +23,38 @@ import ( "github.com/ethereum/go-ethereum/common" ) -var big_t = reflect.TypeOf(&big.Int{}) -var ubig_t = reflect.TypeOf(&big.Int{}) -var byte_t = reflect.TypeOf(byte(0)) -var byte_ts = reflect.TypeOf([]byte(nil)) -var uint_t = reflect.TypeOf(uint(0)) -var uint8_t = reflect.TypeOf(uint8(0)) -var uint16_t = reflect.TypeOf(uint16(0)) -var uint32_t = reflect.TypeOf(uint32(0)) -var uint64_t = reflect.TypeOf(uint64(0)) -var int_t = reflect.TypeOf(int(0)) -var int8_t = reflect.TypeOf(int8(0)) -var int16_t = reflect.TypeOf(int16(0)) -var int32_t = reflect.TypeOf(int32(0)) -var int64_t = reflect.TypeOf(int64(0)) -var hash_t = reflect.TypeOf(common.Hash{}) -var address_t = reflect.TypeOf(common.Address{}) +var ( + big_t = reflect.TypeOf(&big.Int{}) + ubig_t = reflect.TypeOf(&big.Int{}) + byte_t = reflect.TypeOf(byte(0)) + byte_ts = reflect.TypeOf([]byte(nil)) + uint_t = reflect.TypeOf(uint(0)) + uint8_t = reflect.TypeOf(uint8(0)) + uint16_t = reflect.TypeOf(uint16(0)) + uint32_t = reflect.TypeOf(uint32(0)) + uint64_t = reflect.TypeOf(uint64(0)) + int_t = reflect.TypeOf(int(0)) + int8_t = reflect.TypeOf(int8(0)) + int16_t = reflect.TypeOf(int16(0)) + int32_t = reflect.TypeOf(int32(0)) + int64_t = reflect.TypeOf(int64(0)) + hash_t = reflect.TypeOf(common.Hash{}) + address_t = reflect.TypeOf(common.Address{}) -var uint_ts = reflect.TypeOf([]uint(nil)) -var uint8_ts = reflect.TypeOf([]uint8(nil)) -var uint16_ts = reflect.TypeOf([]uint16(nil)) -var uint32_ts = reflect.TypeOf([]uint32(nil)) -var uint64_ts = reflect.TypeOf([]uint64(nil)) -var ubig_ts = reflect.TypeOf([]*big.Int(nil)) + uint_ts = reflect.TypeOf([]uint(nil)) + uint8_ts = reflect.TypeOf([]uint8(nil)) + uint16_ts = reflect.TypeOf([]uint16(nil)) + uint32_ts = reflect.TypeOf([]uint32(nil)) + uint64_ts = reflect.TypeOf([]uint64(nil)) + ubig_ts = reflect.TypeOf([]*big.Int(nil)) -var int_ts = reflect.TypeOf([]int(nil)) -var int8_ts = reflect.TypeOf([]int8(nil)) -var int16_ts = reflect.TypeOf([]int16(nil)) -var int32_ts = reflect.TypeOf([]int32(nil)) -var int64_ts = reflect.TypeOf([]int64(nil)) -var big_ts = reflect.TypeOf([]*big.Int(nil)) + int_ts = reflect.TypeOf([]int(nil)) + int8_ts = reflect.TypeOf([]int8(nil)) + int16_ts = reflect.TypeOf([]int16(nil)) + int32_ts = reflect.TypeOf([]int32(nil)) + int64_ts = reflect.TypeOf([]int64(nil)) + big_ts = reflect.TypeOf([]*big.Int(nil)) +) // U256 will ensure unsigned 256bit on big nums func U256(n *big.Int) []byte { diff --git a/accounts/abi/type.go b/accounts/abi/type.go index 6fb2950ba..c08b744f7 100644 --- a/accounts/abi/type.go +++ b/accounts/abi/type.go @@ -163,6 +163,13 @@ func (t Type) String() (out string) { return t.stringKind } +// packBytesSlice packs the given bytes as [L, V] as the canonical representation +// bytes slice +func packBytesSlice(bytes []byte, l int) []byte { + len := packNum(reflect.ValueOf(l), UintTy) + return append(len, common.RightPadBytes(bytes, (l+31)/32*32)...) +} + // Test the given input parameter `v` and checks if it matches certain // criteria // * Big integers are checks for ptr types and if the given value is @@ -193,8 +200,14 @@ func (t Type) pack(v interface{}) ([]byte, error) { if t.Size > -1 && value.Len() > t.Size { return nil, fmt.Errorf("%v out of bound. %d for %d", value.Kind(), value.Len(), t.Size) } - return []byte(common.LeftPadString(t.String(), 32)), nil + + 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 + if t.T == BytesTy { + return packBytesSlice(value.Bytes(), value.Len()), nil + } + if t.Size > -1 && value.Len() > t.Size { return nil, fmt.Errorf("%v out of bound. %d for %d", value.Kind(), value.Len(), t.Size) }