From 1ede68355db6adbf468d198a8d1ecb0ad1a3ea31 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 28 Dec 2017 11:17:45 +0100 Subject: accounts/abi: add another unpack interface --- accounts/abi/abi.go | 6 +- accounts/abi/abi_test.go | 6 +- accounts/abi/argument.go | 59 ++++++-- accounts/abi/unpack_test.go | 2 +- accounts/abi/unpackv2_test.go | 336 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 395 insertions(+), 14 deletions(-) create mode 100644 accounts/abi/unpackv2_test.go (limited to 'accounts') diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index abcb403db..32f041890 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -136,11 +136,11 @@ func (abi *ABI) UnmarshalJSON(data []byte) error { // MethodById looks up a method by the 4-byte id // returns nil if none found -func (abi *ABI) MethodById(sigdata []byte) *Method { +func (abi *ABI) MethodById(sigdata []byte) (*Method, error){ for _, method := range abi.Methods { if bytes.Equal(method.Id(), sigdata[:4]) { - return &method + return &method, nil } } - return nil + return nil, fmt.Errorf("ABI spec does not contain method signature in data: 0x%x", sigdata[:4]) } diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index 2d43b631c..3bef6add5 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -689,7 +689,11 @@ func TestABI_MethodById(t *testing.T) { } for name, m := range abi.Methods { a := fmt.Sprintf("%v", m) - b := fmt.Sprintf("%v", abi.MethodById(m.Id())) + m2,err := abi.MethodById(m.Id()) + if err != nil { + t.Fatal(err) + } + b := fmt.Sprintf("%v", m2) if a != b { t.Errorf("Method %v (id %v) not 'findable' by id in ABI", name, common.ToHex(m.Id())) } diff --git a/accounts/abi/argument.go b/accounts/abi/argument.go index 04ca6150a..b9b537121 100644 --- a/accounts/abi/argument.go +++ b/accounts/abi/argument.go @@ -67,6 +67,16 @@ func (arguments Arguments) LengthNonIndexed() int { return out } +func (arguments Arguments) NonIndexed() Arguments{ + var ret []Argument + for _,arg := range arguments{ + if !arg.Indexed{ + ret = append(ret, arg) + } + } + return ret +} + // isTuple returns true for non-atomic constructs, like (uint,uint) or uint[] func (arguments Arguments) isTuple() bool { return len(arguments) > 1 @@ -114,14 +124,9 @@ func (arguments Arguments) unpackTuple(v interface{}, output []byte) error { // `j` counts the number of complex types. // both `i` and `j` are used to to correctly compute `data` offset. - i, j := -1, 0 - for _, arg := range arguments { + j := 0 + for i, arg := range arguments.NonIndexed() { - if arg.Indexed { - // can't read, continue - continue - } - i++ marshalledValue, err := toGoType((i+j)*32, arg.Type, output) if err != nil { return err @@ -178,7 +183,6 @@ func (arguments Arguments) unpackAtomic(v interface{}, output []byte) error { } value := valueOf.Elem() - marshalledValue, err := toGoType(0, arg.Type, output) if err != nil { return err @@ -186,7 +190,44 @@ func (arguments Arguments) unpackAtomic(v interface{}, output []byte) error { return set(value, reflect.ValueOf(marshalledValue), arg) } -// Unpack performs the operation Go format -> Hexdata +// UnpackValues can be used to unpack ABI-encoded hexdata according to the ABI-specification, +// without supplying a struct to unpack into. Instead, this method returns a list containing the +// values. An atomic argument will be a list with one element. +func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error){ + + retval := make([]interface{},0,arguments.LengthNonIndexed()) + + virtualArgs := 0 + + for index,arg:= range arguments.NonIndexed(){ + + marshalledValue, err := toGoType((index + virtualArgs) * 32, arg.Type, data) + + if arg.Type.T == ArrayTy { + //If we have a static array, like [3]uint256, these are coded as + // just like uint256,uint256,uint256. + // This means that we need to add two 'virtual' arguments when + // we count the index from now on + + virtualArgs += arg.Type.Size - 1 + } + + if err != nil{ + return nil, err + } + retval = append(retval, marshalledValue) + } + return retval, nil +} + +// UnpackValues performs the operation Go format -> Hexdata +// It is the semantic opposite of UnpackValues +func (arguments Arguments) PackValues(args []interface{}) ([]byte, error) { + return arguments.Pack(args...) +} + + +// Pack performs the operation Go format -> Hexdata func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) { // Make sure arguments match up and pack them abiArgs := arguments diff --git a/accounts/abi/unpack_test.go b/accounts/abi/unpack_test.go index a65426a30..e9f910812 100644 --- a/accounts/abi/unpack_test.go +++ b/accounts/abi/unpack_test.go @@ -130,7 +130,7 @@ var unpackTests = []unpackTest{ { def: `[{"type": "bytes32"}]`, enc: "0100000000000000000000000000000000000000000000000000000000000000", - want: common.HexToHash("0100000000000000000000000000000000000000000000000000000000000000"), + want: [32]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, }, { def: `[{"type": "function"}]`, diff --git a/accounts/abi/unpackv2_test.go b/accounts/abi/unpackv2_test.go new file mode 100644 index 000000000..d0074ff7b --- /dev/null +++ b/accounts/abi/unpackv2_test.go @@ -0,0 +1,336 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package abi + +import ( + "bytes" + "encoding/hex" + "fmt" + "math/big" + "reflect" + "strconv" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +func TestUnpackV2(t *testing.T) { + for i, test := range unpackTests { + 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) + } + out, err := abi.Methods["method"].Outputs.UnpackValues(encb) + + if err != nil { + t.Fatal(err) + } + if len(test.err) != 0 { + // The new stuff doesn't have these types of errors + return + } + if !reflect.DeepEqual(test.want, out[0]) { + t.Errorf("test %d (%v) failed: expected %v, got %v", i, test.def, test.want, out[0]) + } + }) + } +} + + +func TestMultiReturnWithArrayV2(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("000000000000000000000000000000000000000000000000000000000000000900000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000007")) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000006")) + + out, err := abi.Methods["multi"].Outputs.UnpackValues(buff.Bytes()) + + ret1Exp := [3]uint64{9, 8, 7} + ret2Exp := uint64(6) + + if !reflect.DeepEqual(out[0], ret1Exp) { + t.Error("array result", out[0], "!= Expected", ret1Exp) + } + if out[1] != ret2Exp { + t.Error("int result", out[1], "!= Expected", ret2Exp) + } +} + +func TestUnmarshalV2(t *testing.T) { + const definition = `[ + { "name" : "int", "constant" : false, "outputs": [ { "type": "uint256" } ] }, + { "name" : "bool", "constant" : false, "outputs": [ { "type": "bool" } ] }, + { "name" : "bytes", "constant" : false, "outputs": [ { "type": "bytes" } ] }, + { "name" : "fixed", "constant" : false, "outputs": [ { "type": "bytes32" } ] }, + { "name" : "multi", "constant" : false, "outputs": [ { "type": "bytes" }, { "type": "bytes" } ] }, + { "name" : "intArraySingle", "constant" : false, "outputs": [ { "type": "uint256[3]" } ] }, + { "name" : "addressSliceSingle", "constant" : false, "outputs": [ { "type": "address[]" } ] }, + { "name" : "addressSliceDouble", "constant" : false, "outputs": [ { "name": "a", "type": "address[]" }, { "name": "b", "type": "address[]" } ] }, + { "name" : "mixedBytes", "constant" : 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) + + // marshall mixed bytes (mixedBytes) + p0Exp := common.Hex2Bytes("01020000000000000000") + p1Exp := common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000ddeeff") + + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040")) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000ddeeff")) + buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000a")) + buff.Write(common.Hex2Bytes("0102000000000000000000000000000000000000000000000000000000000000")) + + mixedBytes, err := abi.Methods["mixedBytes"].Outputs.UnpackValues(buff.Bytes()) + if err != nil { + t.Error(err) + } else { + p0 := mixedBytes[0].([]byte) + p1 := mixedBytes[1].([32]byte) + if !bytes.Equal(p0, p0Exp) { + t.Errorf("unexpected value unpacked: want %x, got %x", p0Exp, p0) + } + + if !bytes.Equal(p1[:], p1Exp) { + t.Errorf("unexpected value unpacked: want %x, got %x", p1Exp, p1) + } + } + + // marshal int + integer, err := abi.Methods["int"].Outputs.UnpackValues(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) + if err != nil { + t.Error(err) + } + if len(integer) == 0 { + t.Error("Expected one integer") + } + intval := integer[0].(*big.Int) + if intval == nil || intval.Cmp(big.NewInt(1)) != 0 { + t.Error("expected Int to be 1 got", intval) + } + + // marshal bool + boolreturns, err := abi.Methods["bool"].Outputs.UnpackValues(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) + if err != nil { + t.Error(err) + } + boolval := boolreturns[0].(bool) + if !boolval { + t.Error("expected Bool to be true") + } + + // marshal dynamic bytes max length 32 + buff.Reset() + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) + bytesOut := common.RightPadBytes([]byte("hello"), 32) + buff.Write(bytesOut) + + bytesreturns, err := abi.Methods["bytes"].Outputs.UnpackValues(buff.Bytes()) + + if err != nil { + t.Error(err) + } + bytesval := bytesreturns[0].([]byte) + if !bytes.Equal(bytesval, bytesOut) { + t.Errorf("expected %x got %x", bytesOut, bytesval) + } + + // marshall dynamic bytes max length 64 + buff.Reset() + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040")) + bytesOut = common.RightPadBytes([]byte("hello"), 64) + buff.Write(bytesOut) + + bytesreturns, err = abi.Methods["bytes"].Outputs.UnpackValues(buff.Bytes()) + if err != nil { + t.Error(err) + } + bytesval = bytesreturns[0].([]byte) + if !bytes.Equal(bytesval, bytesOut) { + t.Errorf("expected %x got %x", bytesOut, bytesval) + } + + // marshall dynamic bytes max length 64 + buff.Reset() + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) + buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000003f")) + bytesOut = common.RightPadBytes([]byte("hello"), 64) + buff.Write(bytesOut) + + bytesreturns, err = abi.Methods["bytes"].Outputs.UnpackValues(buff.Bytes()) + if err != nil { + t.Error(err) + } + bytesval = bytesreturns[0].([]byte) + + if !bytes.Equal(bytesval, bytesOut[:len(bytesOut)-1]) { + t.Errorf("expected %x got %x", bytesOut[:len(bytesOut)-1], bytesval) + } + // marshal dynamic bytes output empty (nil) + bytesreturns, err = abi.Methods["bytes"].Outputs.UnpackValues(nil) + if err == nil { + t.Error("expected error") + } + // marshal dynamic bytes output empty + buff.Reset() + bytesreturns, err = abi.Methods["bytes"].Outputs.UnpackValues(buff.Bytes()) + if err == nil { + t.Error("expected error") + } + + // marshal dynamic bytes length 5 + buff.Reset() + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000005")) + buff.Write(common.RightPadBytes([]byte("hello"), 32)) + + bytesreturns, err = abi.Methods["bytes"].Outputs.UnpackValues(buff.Bytes()) + if err != nil { + t.Error(err) + } + bytesval = bytesreturns[0].([]byte) + + if !bytes.Equal(bytesval, []byte("hello")) { + t.Errorf("expected %x got %x", bytesOut, bytesval) + } + + // marshal dynamic bytes length 5 + buff.Reset() + buff.Write(common.RightPadBytes([]byte("hello"), 32)) + + hashreturns, err := abi.Methods["fixed"].Outputs.UnpackValues(buff.Bytes()) + if err != nil { + t.Error(err) + } + hashval := hashreturns[0].([32]byte) + + helloHash := common.BytesToHash(common.RightPadBytes([]byte("hello"), 32)) + if common.Hash(hashval) != helloHash { + t.Errorf("Expected %x to equal %x", hashval, helloHash) + } + + // marshal error + buff.Reset() + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) + + bytesreturns, err = abi.Methods["bytes"].Outputs.UnpackValues(buff.Bytes()) + if err == nil { + // Error abi: cannot marshal in to go slice: offset 32 would go over slice boundary (len=64) + t.Error("expected error") + } + bytesreturns, err = abi.Methods["multi"].Outputs.UnpackValues(make([]byte, 64)) + + buff.Reset() + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000003")) + // marshal int array + + intArrayReturns, err := abi.Methods["intArraySingle"].Outputs.UnpackValues(buff.Bytes()) + if err != nil { + t.Error(err) + } + intArray := intArrayReturns[0].([3]*big.Int) + + var testAgainstIntArray = [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)} + + for i, intval := range intArray { + if intval.Cmp(testAgainstIntArray[i]) != 0 { + t.Errorf("expected %v, got %v", testAgainstIntArray[i], intval) + } + } + // marshal address slice + buff.Reset() + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) // offset + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // size + buff.Write(common.Hex2Bytes("0000000000000000000000000100000000000000000000000000000000000000")) + + outAddrReturns, err := abi.Methods["addressSliceSingle"].Outputs.UnpackValues(buff.Bytes()) + if err != nil { + t.Fatal("didn't expect error:", err) + } + outAddr := outAddrReturns[0].([]common.Address) + 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")) + + outAddrStructReturns, err := abi.Methods["addressSliceDouble"].Outputs.UnpackValues(buff.Bytes()) + if err != nil { + t.Fatal("didn't expect error:", err) + } + A := outAddrStructReturns[0].([]common.Address) + B := outAddrStructReturns[1].([]common.Address) + + if len(A) != 1 { + t.Fatal("expected 1 item, got", len(A)) + } + + if A[0] != (common.Address{1}) { + t.Errorf("expected %x, got %x", common.Address{1}, A[0]) + } + + if len(B) != 2 { + t.Fatal("expected 1 item, got", len(B)) + } + + if B[0] != (common.Address{2}) { + t.Errorf("expected %x, got %x", common.Address{2}, B[0]) + } + if B[1] != (common.Address{3}) { + t.Errorf("expected %x, got %x", common.Address{3}, B[1]) + } + + // marshal invalid address slice + buff.Reset() + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000100")) + + err = abi.Unpack(&outAddr, "addressSliceSingle", buff.Bytes()) + _, err = abi.Methods["addressSliceSingle"].Outputs.UnpackValues(buff.Bytes()) + if err == nil { + t.Fatal("expected error:", err) + } + +} -- cgit v1.2.3 From f0f594d0453c6f53eaeeac6187785daf12044f58 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Sat, 30 Dec 2017 16:07:12 +0100 Subject: accounts/abi: Deduplicate code in unpacker --- accounts/abi/abi.go | 2 +- accounts/abi/abi_test.go | 2 +- accounts/abi/argument.go | 81 +++++++++++++++++-------------------------- accounts/abi/unpackv2_test.go | 1 - 4 files changed, 33 insertions(+), 53 deletions(-) (limited to 'accounts') diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index 32f041890..fd286c2c2 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -136,7 +136,7 @@ func (abi *ABI) UnmarshalJSON(data []byte) error { // MethodById looks up a method by the 4-byte id // returns nil if none found -func (abi *ABI) MethodById(sigdata []byte) (*Method, error){ +func (abi *ABI) MethodById(sigdata []byte) (*Method, error) { for _, method := range abi.Methods { if bytes.Equal(method.Id(), sigdata[:4]) { return &method, nil diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index 3bef6add5..e66828240 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -689,7 +689,7 @@ func TestABI_MethodById(t *testing.T) { } for name, m := range abi.Methods { a := fmt.Sprintf("%v", m) - m2,err := abi.MethodById(m.Id()) + m2, err := abi.MethodById(m.Id()) if err != nil { t.Fatal(err) } diff --git a/accounts/abi/argument.go b/accounts/abi/argument.go index b9b537121..bdd0894f1 100644 --- a/accounts/abi/argument.go +++ b/accounts/abi/argument.go @@ -67,10 +67,10 @@ func (arguments Arguments) LengthNonIndexed() int { return out } -func (arguments Arguments) NonIndexed() Arguments{ +func (arguments Arguments) NonIndexed() Arguments { var ret []Argument - for _,arg := range arguments{ - if !arg.Indexed{ + for _, arg := range arguments { + if !arg.Indexed { ret = append(ret, arg) } } @@ -84,21 +84,27 @@ func (arguments Arguments) isTuple() bool { // Unpack performs the operation hexdata -> Go format func (arguments Arguments) Unpack(v interface{}, data []byte) error { - if arguments.isTuple() { - return arguments.unpackTuple(v, data) - } - return arguments.unpackAtomic(v, data) -} -func (arguments Arguments) unpackTuple(v interface{}, output []byte) error { // make sure the passed value is arguments pointer - valueOf := reflect.ValueOf(v) - if reflect.Ptr != valueOf.Kind() { + if reflect.Ptr != reflect.ValueOf(v).Kind() { return fmt.Errorf("abi: Unpack(non-pointer %T)", v) } + marshalledValues, err := arguments.UnpackValues(data) + if err != nil { + return err + } + + if arguments.isTuple() { + return arguments.unpackTuple(v, marshalledValues) + } + return arguments.unpackAtomic(v, marshalledValues) +} + +func (arguments Arguments) unpackTuple(v interface{}, marshalledValues []interface{}) error { + var ( - value = valueOf.Elem() + value = reflect.ValueOf(v).Elem() typ = value.Type() kind = value.Kind() ) @@ -120,25 +126,9 @@ func (arguments Arguments) unpackTuple(v interface{}, output []byte) error { exists[field] = true } } - // `i` counts the nonindexed arguments. - // `j` counts the number of complex types. - // both `i` and `j` are used to to correctly compute `data` offset. - - j := 0 for i, arg := range arguments.NonIndexed() { - marshalledValue, err := toGoType((i+j)*32, arg.Type, output) - if err != nil { - return err - } - - if arg.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 += arg.Type.Size - 1 - } - - reflectValue := reflect.ValueOf(marshalledValue) + reflectValue := reflect.ValueOf(marshalledValues[i]) switch kind { case reflect.Struct: @@ -171,37 +161,29 @@ func (arguments Arguments) unpackTuple(v interface{}, output []byte) error { } // unpackAtomic unpacks ( hexdata -> go ) a single value -func (arguments Arguments) unpackAtomic(v interface{}, output []byte) error { - // make sure the passed value is arguments pointer - valueOf := reflect.ValueOf(v) - if reflect.Ptr != valueOf.Kind() { - return fmt.Errorf("abi: Unpack(non-pointer %T)", v) - } - arg := arguments[0] - if arg.Indexed { - return fmt.Errorf("abi: attempting to unpack indexed variable into element.") - } +func (arguments Arguments) unpackAtomic(v interface{}, marshalledValues []interface{}) error { - value := valueOf.Elem() - marshalledValue, err := toGoType(0, arg.Type, output) - if err != nil { - return err + if len(marshalledValues) != 1 { + return fmt.Errorf("abi: wrong length, expected single value, got %d", len(marshalledValues)) } - return set(value, reflect.ValueOf(marshalledValue), arg) + + elem := reflect.ValueOf(v).Elem() + reflectValue := reflect.ValueOf(marshalledValues[0]) + return set(elem, reflectValue, arguments.NonIndexed()[0]) } // UnpackValues can be used to unpack ABI-encoded hexdata according to the ABI-specification, // without supplying a struct to unpack into. Instead, this method returns a list containing the // values. An atomic argument will be a list with one element. -func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error){ +func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error) { - retval := make([]interface{},0,arguments.LengthNonIndexed()) + retval := make([]interface{}, 0, arguments.LengthNonIndexed()) virtualArgs := 0 - for index,arg:= range arguments.NonIndexed(){ + for index, arg := range arguments.NonIndexed() { - marshalledValue, err := toGoType((index + virtualArgs) * 32, arg.Type, data) + marshalledValue, err := toGoType((index+virtualArgs)*32, arg.Type, data) if arg.Type.T == ArrayTy { //If we have a static array, like [3]uint256, these are coded as @@ -212,7 +194,7 @@ func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error){ virtualArgs += arg.Type.Size - 1 } - if err != nil{ + if err != nil { return nil, err } retval = append(retval, marshalledValue) @@ -226,7 +208,6 @@ func (arguments Arguments) PackValues(args []interface{}) ([]byte, error) { return arguments.Pack(args...) } - // Pack performs the operation Go format -> Hexdata func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) { // Make sure arguments match up and pack them diff --git a/accounts/abi/unpackv2_test.go b/accounts/abi/unpackv2_test.go index d0074ff7b..364a09762 100644 --- a/accounts/abi/unpackv2_test.go +++ b/accounts/abi/unpackv2_test.go @@ -57,7 +57,6 @@ func TestUnpackV2(t *testing.T) { } } - func TestMultiReturnWithArrayV2(t *testing.T) { const definition = `[{"name" : "multi", "outputs": [{"type": "uint64[3]"}, {"type": "uint64"}]}]` abi, err := JSON(strings.NewReader(definition)) -- cgit v1.2.3 From 08c5d4dd271c58385df94842f1b5700ca6ef181c Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Sat, 13 Jan 2018 15:12:52 +0100 Subject: accounts/abi: address review concerns --- accounts/abi/abi.go | 2 +- accounts/abi/abi_test.go | 2 +- accounts/abi/argument.go | 18 +-- accounts/abi/unpackv2_test.go | 335 ------------------------------------------ 4 files changed, 5 insertions(+), 352 deletions(-) delete mode 100644 accounts/abi/unpackv2_test.go (limited to 'accounts') diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index fd286c2c2..254b1f7fb 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -142,5 +142,5 @@ func (abi *ABI) MethodById(sigdata []byte) (*Method, error) { return &method, nil } } - return nil, fmt.Errorf("ABI spec does not contain method signature in data: 0x%x", sigdata[:4]) + return nil, fmt.Errorf("no method with id: %#x", sigdata[:4]) } diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index e66828240..35e0094dd 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -691,7 +691,7 @@ func TestABI_MethodById(t *testing.T) { a := fmt.Sprintf("%v", m) m2, err := abi.MethodById(m.Id()) if err != nil { - t.Fatal(err) + t.Fatalf("Failed to look up ABI method: %v", err) } b := fmt.Sprintf("%v", m2) if a != b { diff --git a/accounts/abi/argument.go b/accounts/abi/argument.go index bdd0894f1..f171f4cc6 100644 --- a/accounts/abi/argument.go +++ b/accounts/abi/argument.go @@ -67,6 +67,7 @@ func (arguments Arguments) LengthNonIndexed() int { return out } +// NonIndexed returns the arguments with indexed arguments filtered out func (arguments Arguments) NonIndexed() Arguments { var ret []Argument for _, arg := range arguments { @@ -89,12 +90,10 @@ func (arguments Arguments) Unpack(v interface{}, data []byte) error { if reflect.Ptr != reflect.ValueOf(v).Kind() { return fmt.Errorf("abi: Unpack(non-pointer %T)", v) } - marshalledValues, err := arguments.UnpackValues(data) if err != nil { return err } - if arguments.isTuple() { return arguments.unpackTuple(v, marshalledValues) } @@ -162,11 +161,9 @@ func (arguments Arguments) unpackTuple(v interface{}, marshalledValues []interfa // unpackAtomic unpacks ( hexdata -> go ) a single value func (arguments Arguments) unpackAtomic(v interface{}, marshalledValues []interface{}) error { - if len(marshalledValues) != 1 { return fmt.Errorf("abi: wrong length, expected single value, got %d", len(marshalledValues)) } - elem := reflect.ValueOf(v).Elem() reflectValue := reflect.ValueOf(marshalledValues[0]) return set(elem, reflectValue, arguments.NonIndexed()[0]) @@ -176,24 +173,18 @@ func (arguments Arguments) unpackAtomic(v interface{}, marshalledValues []interf // without supplying a struct to unpack into. Instead, this method returns a list containing the // values. An atomic argument will be a list with one element. func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error) { - retval := make([]interface{}, 0, arguments.LengthNonIndexed()) - virtualArgs := 0 - for index, arg := range arguments.NonIndexed() { - marshalledValue, err := toGoType((index+virtualArgs)*32, arg.Type, data) - if arg.Type.T == ArrayTy { - //If we have a static array, like [3]uint256, these are coded as + // If we have a static array, like [3]uint256, these are coded as // just like uint256,uint256,uint256. // This means that we need to add two 'virtual' arguments when // we count the index from now on virtualArgs += arg.Type.Size - 1 } - if err != nil { return nil, err } @@ -202,7 +193,7 @@ func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error) { return retval, nil } -// UnpackValues performs the operation Go format -> Hexdata +// PackValues performs the operation Go format -> Hexdata // It is the semantic opposite of UnpackValues func (arguments Arguments) PackValues(args []interface{}) ([]byte, error) { return arguments.Pack(args...) @@ -215,7 +206,6 @@ func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) { if len(args) != len(abiArgs) { return nil, fmt.Errorf("argument count mismatch: %d for %d", len(args), len(abiArgs)) } - // variable input is the output appended at the end of packed // output. This is used for strings and bytes types input. var variableInput []byte @@ -229,7 +219,6 @@ func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) { inputOffset += 32 } } - var ret []byte for i, a := range args { input := abiArgs[i] @@ -238,7 +227,6 @@ func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) { if err != nil { return nil, err } - // check for a slice type (string, bytes, slice) if input.Type.requiresLengthPrefix() { // calculate the offset diff --git a/accounts/abi/unpackv2_test.go b/accounts/abi/unpackv2_test.go deleted file mode 100644 index 364a09762..000000000 --- a/accounts/abi/unpackv2_test.go +++ /dev/null @@ -1,335 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package abi - -import ( - "bytes" - "encoding/hex" - "fmt" - "math/big" - "reflect" - "strconv" - "strings" - "testing" - - "github.com/ethereum/go-ethereum/common" -) - -func TestUnpackV2(t *testing.T) { - for i, test := range unpackTests { - 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) - } - out, err := abi.Methods["method"].Outputs.UnpackValues(encb) - - if err != nil { - t.Fatal(err) - } - if len(test.err) != 0 { - // The new stuff doesn't have these types of errors - return - } - if !reflect.DeepEqual(test.want, out[0]) { - t.Errorf("test %d (%v) failed: expected %v, got %v", i, test.def, test.want, out[0]) - } - }) - } -} - -func TestMultiReturnWithArrayV2(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("000000000000000000000000000000000000000000000000000000000000000900000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000007")) - buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000006")) - - out, err := abi.Methods["multi"].Outputs.UnpackValues(buff.Bytes()) - - ret1Exp := [3]uint64{9, 8, 7} - ret2Exp := uint64(6) - - if !reflect.DeepEqual(out[0], ret1Exp) { - t.Error("array result", out[0], "!= Expected", ret1Exp) - } - if out[1] != ret2Exp { - t.Error("int result", out[1], "!= Expected", ret2Exp) - } -} - -func TestUnmarshalV2(t *testing.T) { - const definition = `[ - { "name" : "int", "constant" : false, "outputs": [ { "type": "uint256" } ] }, - { "name" : "bool", "constant" : false, "outputs": [ { "type": "bool" } ] }, - { "name" : "bytes", "constant" : false, "outputs": [ { "type": "bytes" } ] }, - { "name" : "fixed", "constant" : false, "outputs": [ { "type": "bytes32" } ] }, - { "name" : "multi", "constant" : false, "outputs": [ { "type": "bytes" }, { "type": "bytes" } ] }, - { "name" : "intArraySingle", "constant" : false, "outputs": [ { "type": "uint256[3]" } ] }, - { "name" : "addressSliceSingle", "constant" : false, "outputs": [ { "type": "address[]" } ] }, - { "name" : "addressSliceDouble", "constant" : false, "outputs": [ { "name": "a", "type": "address[]" }, { "name": "b", "type": "address[]" } ] }, - { "name" : "mixedBytes", "constant" : 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) - - // marshall mixed bytes (mixedBytes) - p0Exp := common.Hex2Bytes("01020000000000000000") - p1Exp := common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000ddeeff") - - buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040")) - buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000ddeeff")) - buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000a")) - buff.Write(common.Hex2Bytes("0102000000000000000000000000000000000000000000000000000000000000")) - - mixedBytes, err := abi.Methods["mixedBytes"].Outputs.UnpackValues(buff.Bytes()) - if err != nil { - t.Error(err) - } else { - p0 := mixedBytes[0].([]byte) - p1 := mixedBytes[1].([32]byte) - if !bytes.Equal(p0, p0Exp) { - t.Errorf("unexpected value unpacked: want %x, got %x", p0Exp, p0) - } - - if !bytes.Equal(p1[:], p1Exp) { - t.Errorf("unexpected value unpacked: want %x, got %x", p1Exp, p1) - } - } - - // marshal int - integer, err := abi.Methods["int"].Outputs.UnpackValues(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) - if err != nil { - t.Error(err) - } - if len(integer) == 0 { - t.Error("Expected one integer") - } - intval := integer[0].(*big.Int) - if intval == nil || intval.Cmp(big.NewInt(1)) != 0 { - t.Error("expected Int to be 1 got", intval) - } - - // marshal bool - boolreturns, err := abi.Methods["bool"].Outputs.UnpackValues(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) - if err != nil { - t.Error(err) - } - boolval := boolreturns[0].(bool) - if !boolval { - t.Error("expected Bool to be true") - } - - // marshal dynamic bytes max length 32 - buff.Reset() - buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) - buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) - bytesOut := common.RightPadBytes([]byte("hello"), 32) - buff.Write(bytesOut) - - bytesreturns, err := abi.Methods["bytes"].Outputs.UnpackValues(buff.Bytes()) - - if err != nil { - t.Error(err) - } - bytesval := bytesreturns[0].([]byte) - if !bytes.Equal(bytesval, bytesOut) { - t.Errorf("expected %x got %x", bytesOut, bytesval) - } - - // marshall dynamic bytes max length 64 - buff.Reset() - buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) - buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040")) - bytesOut = common.RightPadBytes([]byte("hello"), 64) - buff.Write(bytesOut) - - bytesreturns, err = abi.Methods["bytes"].Outputs.UnpackValues(buff.Bytes()) - if err != nil { - t.Error(err) - } - bytesval = bytesreturns[0].([]byte) - if !bytes.Equal(bytesval, bytesOut) { - t.Errorf("expected %x got %x", bytesOut, bytesval) - } - - // marshall dynamic bytes max length 64 - buff.Reset() - buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) - buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000003f")) - bytesOut = common.RightPadBytes([]byte("hello"), 64) - buff.Write(bytesOut) - - bytesreturns, err = abi.Methods["bytes"].Outputs.UnpackValues(buff.Bytes()) - if err != nil { - t.Error(err) - } - bytesval = bytesreturns[0].([]byte) - - if !bytes.Equal(bytesval, bytesOut[:len(bytesOut)-1]) { - t.Errorf("expected %x got %x", bytesOut[:len(bytesOut)-1], bytesval) - } - // marshal dynamic bytes output empty (nil) - bytesreturns, err = abi.Methods["bytes"].Outputs.UnpackValues(nil) - if err == nil { - t.Error("expected error") - } - // marshal dynamic bytes output empty - buff.Reset() - bytesreturns, err = abi.Methods["bytes"].Outputs.UnpackValues(buff.Bytes()) - if err == nil { - t.Error("expected error") - } - - // marshal dynamic bytes length 5 - buff.Reset() - buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) - buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000005")) - buff.Write(common.RightPadBytes([]byte("hello"), 32)) - - bytesreturns, err = abi.Methods["bytes"].Outputs.UnpackValues(buff.Bytes()) - if err != nil { - t.Error(err) - } - bytesval = bytesreturns[0].([]byte) - - if !bytes.Equal(bytesval, []byte("hello")) { - t.Errorf("expected %x got %x", bytesOut, bytesval) - } - - // marshal dynamic bytes length 5 - buff.Reset() - buff.Write(common.RightPadBytes([]byte("hello"), 32)) - - hashreturns, err := abi.Methods["fixed"].Outputs.UnpackValues(buff.Bytes()) - if err != nil { - t.Error(err) - } - hashval := hashreturns[0].([32]byte) - - helloHash := common.BytesToHash(common.RightPadBytes([]byte("hello"), 32)) - if common.Hash(hashval) != helloHash { - t.Errorf("Expected %x to equal %x", hashval, helloHash) - } - - // marshal error - buff.Reset() - buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) - - bytesreturns, err = abi.Methods["bytes"].Outputs.UnpackValues(buff.Bytes()) - if err == nil { - // Error abi: cannot marshal in to go slice: offset 32 would go over slice boundary (len=64) - t.Error("expected error") - } - bytesreturns, err = abi.Methods["multi"].Outputs.UnpackValues(make([]byte, 64)) - - buff.Reset() - buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) - buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) - buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000003")) - // marshal int array - - intArrayReturns, err := abi.Methods["intArraySingle"].Outputs.UnpackValues(buff.Bytes()) - if err != nil { - t.Error(err) - } - intArray := intArrayReturns[0].([3]*big.Int) - - var testAgainstIntArray = [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)} - - for i, intval := range intArray { - if intval.Cmp(testAgainstIntArray[i]) != 0 { - t.Errorf("expected %v, got %v", testAgainstIntArray[i], intval) - } - } - // marshal address slice - buff.Reset() - buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) // offset - buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // size - buff.Write(common.Hex2Bytes("0000000000000000000000000100000000000000000000000000000000000000")) - - outAddrReturns, err := abi.Methods["addressSliceSingle"].Outputs.UnpackValues(buff.Bytes()) - if err != nil { - t.Fatal("didn't expect error:", err) - } - outAddr := outAddrReturns[0].([]common.Address) - 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")) - - outAddrStructReturns, err := abi.Methods["addressSliceDouble"].Outputs.UnpackValues(buff.Bytes()) - if err != nil { - t.Fatal("didn't expect error:", err) - } - A := outAddrStructReturns[0].([]common.Address) - B := outAddrStructReturns[1].([]common.Address) - - if len(A) != 1 { - t.Fatal("expected 1 item, got", len(A)) - } - - if A[0] != (common.Address{1}) { - t.Errorf("expected %x, got %x", common.Address{1}, A[0]) - } - - if len(B) != 2 { - t.Fatal("expected 1 item, got", len(B)) - } - - if B[0] != (common.Address{2}) { - t.Errorf("expected %x, got %x", common.Address{2}, B[0]) - } - if B[1] != (common.Address{3}) { - t.Errorf("expected %x, got %x", common.Address{3}, B[1]) - } - - // marshal invalid address slice - buff.Reset() - buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000100")) - - err = abi.Unpack(&outAddr, "addressSliceSingle", buff.Bytes()) - _, err = abi.Methods["addressSliceSingle"].Outputs.UnpackValues(buff.Bytes()) - if err == nil { - t.Fatal("expected error:", err) - } - -} -- cgit v1.2.3 From bd6ed23899c8a3b4b6d0db29f0f6298e492cedd6 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Sat, 13 Jan 2018 16:03:24 +0100 Subject: accounts/abi: harden unpacking against malicious input --- accounts/abi/unpack.go | 17 ++++++++--- accounts/abi/unpack_test.go | 70 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 4 deletions(-) (limited to 'accounts') diff --git a/accounts/abi/unpack.go b/accounts/abi/unpack.go index 334245661..51fb9ab9b 100644 --- a/accounts/abi/unpack.go +++ b/accounts/abi/unpack.go @@ -95,6 +95,9 @@ func readFixedBytes(t Type, word []byte) (interface{}, error) { // iteratively unpack elements func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) { + if size < 0 { + return nil, fmt.Errorf("cannot marshal input to array, size is negative (%d)", size) + } if start+32*size > len(output) { return nil, fmt.Errorf("abi: cannot marshal in to go array: offset %d would go over slice boundary (len=%d)", len(output), start+32*size) } @@ -181,16 +184,22 @@ func toGoType(index int, t Type, output []byte) (interface{}, error) { // interprets a 32 byte slice as an offset and then determines which indice to look to decode the type. func lengthPrefixPointsTo(index int, output []byte) (start int, length int, err error) { - offset := int(binary.BigEndian.Uint64(output[index+24 : index+32])) + offsetBig := big.NewInt(0).SetBytes(output[index : index+32]) + if !offsetBig.IsInt64() { + return 0, 0, fmt.Errorf("abi offset larger than int64: %v", offsetBig) + } + offset := int(offsetBig.Int64()) if offset+32 > len(output) { return 0, 0, fmt.Errorf("abi: cannot marshal in to go slice: offset %d would go over slice boundary (len=%d)", len(output), offset+32) } - length = int(binary.BigEndian.Uint64(output[offset+24 : offset+32])) + lengthBig := big.NewInt(0).SetBytes(output[offset : offset+32]) + if !lengthBig.IsInt64() { + return 0, 0, fmt.Errorf("abi length larger than int64: %v", lengthBig) + } + length = int(lengthBig.Int64()) if offset+32+length > len(output) { return 0, 0, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %d require %d", len(output), offset+32+length) } start = offset + 32 - - //fmt.Printf("LENGTH PREFIX INFO: \nsize: %v\noffset: %v\nstart: %v\n", length, offset, start) return } diff --git a/accounts/abi/unpack_test.go b/accounts/abi/unpack_test.go index e9f910812..742211244 100644 --- a/accounts/abi/unpack_test.go +++ b/accounts/abi/unpack_test.go @@ -683,3 +683,73 @@ func TestUnmarshal(t *testing.T) { t.Fatal("expected error:", err) } } + +func TestOOMMaliciousInput(t *testing.T) { + oomTests := []unpackTest{ + { + def: `[{"type": "uint8[]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020" + // offset + "0000000000000000000000000000000000000000000000000000000000000003" + // num elems + "0000000000000000000000000000000000000000000000000000000000000001" + // elem 1 + "0000000000000000000000000000000000000000000000000000000000000002", // elem 2 + }, + { // Length larger than 64 bits + def: `[{"type": "uint8[]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020" + // offset + "00ffffffffffffffffffffffffffffffffffffffffffffff0000000000000002" + // num elems + "0000000000000000000000000000000000000000000000000000000000000001" + // elem 1 + "0000000000000000000000000000000000000000000000000000000000000002", // elem 2 + }, + { // Offset very large (over 64 bits) + def: `[{"type": "uint8[]"}]`, + enc: "00ffffffffffffffffffffffffffffffffffffffffffffff0000000000000020" + // offset + "0000000000000000000000000000000000000000000000000000000000000002" + // num elems + "0000000000000000000000000000000000000000000000000000000000000001" + // elem 1 + "0000000000000000000000000000000000000000000000000000000000000002", // elem 2 + }, + { // Offset very large (below 64 bits) + def: `[{"type": "uint8[]"}]`, + enc: "0000000000000000000000000000000000000000000000007ffffffffff00020" + // offset + "0000000000000000000000000000000000000000000000000000000000000002" + // num elems + "0000000000000000000000000000000000000000000000000000000000000001" + // elem 1 + "0000000000000000000000000000000000000000000000000000000000000002", // elem 2 + }, + { // Offset negative (as 64 bit) + def: `[{"type": "uint8[]"}]`, + enc: "000000000000000000000000000000000000000000000000f000000000000020" + // offset + "0000000000000000000000000000000000000000000000000000000000000002" + // num elems + "0000000000000000000000000000000000000000000000000000000000000001" + // elem 1 + "0000000000000000000000000000000000000000000000000000000000000002", // elem 2 + }, + + { // Negative length + def: `[{"type": "uint8[]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020" + // offset + "000000000000000000000000000000000000000000000000f000000000000002" + // num elems + "0000000000000000000000000000000000000000000000000000000000000001" + // elem 1 + "0000000000000000000000000000000000000000000000000000000000000002", // elem 2 + }, + { // Very large length + def: `[{"type": "uint8[]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020" + // offset + "0000000000000000000000000000000000000000000000007fffffffff000002" + // num elems + "0000000000000000000000000000000000000000000000000000000000000001" + // elem 1 + "0000000000000000000000000000000000000000000000000000000000000002", // elem 2 + }, + } + for i, test := range oomTests { + 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) + } + _, err = abi.Methods["method"].Outputs.UnpackValues(encb) + if err == nil { + t.Fatalf("Expected error on malicious input, test %d", i) + } + } +} -- cgit v1.2.3 From 61f2279bdeac595a4607080028715a8222db6cd4 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 2 Feb 2018 14:03:58 +0100 Subject: abi: fix missing method on go 1.7/1.8 --- accounts/abi/unpack.go | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) (limited to 'accounts') diff --git a/accounts/abi/unpack.go b/accounts/abi/unpack.go index 51fb9ab9b..761c80edf 100644 --- a/accounts/abi/unpack.go +++ b/accounts/abi/unpack.go @@ -184,22 +184,32 @@ func toGoType(index int, t Type, output []byte) (interface{}, error) { // interprets a 32 byte slice as an offset and then determines which indice to look to decode the type. func lengthPrefixPointsTo(index int, output []byte) (start int, length int, err error) { - offsetBig := big.NewInt(0).SetBytes(output[index : index+32]) - if !offsetBig.IsInt64() { - return 0, 0, fmt.Errorf("abi offset larger than int64: %v", offsetBig) + bigOffsetEnd := big.NewInt(0).SetBytes(output[index : index+32]) + bigOffsetEnd.Add(bigOffsetEnd, common.Big32) + outputLength := big.NewInt(int64(len(output))) + + if bigOffsetEnd.Cmp(outputLength) > 0 { + return 0, 0, fmt.Errorf("abi: cannot marshal in to go slice: offset %v would go over slice boundary (len=%v)", bigOffsetEnd, outputLength) } - offset := int(offsetBig.Int64()) - if offset+32 > len(output) { - return 0, 0, fmt.Errorf("abi: cannot marshal in to go slice: offset %d would go over slice boundary (len=%d)", len(output), offset+32) + + if bigOffsetEnd.BitLen() > 63 { + return 0, 0, fmt.Errorf("abi offset larger than int64: %v", bigOffsetEnd) } - lengthBig := big.NewInt(0).SetBytes(output[offset : offset+32]) - if !lengthBig.IsInt64() { - return 0, 0, fmt.Errorf("abi length larger than int64: %v", lengthBig) + + offsetEnd := int(bigOffsetEnd.Uint64()) + lengthBig := big.NewInt(0).SetBytes(output[offsetEnd-32 : offsetEnd]) + + totalSize := big.NewInt(0) + totalSize.Add(totalSize, bigOffsetEnd) + totalSize.Add(totalSize, lengthBig) + if totalSize.BitLen() > 63 { + return 0, 0, fmt.Errorf("abi length larger than int64: %v", totalSize) } - length = int(lengthBig.Int64()) - if offset+32+length > len(output) { - return 0, 0, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %d require %d", len(output), offset+32+length) + + if totalSize.Cmp(outputLength) > 0 { + return 0, 0, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %v require %v", outputLength, totalSize) } - start = offset + 32 + start = int(bigOffsetEnd.Uint64()) + length = int(lengthBig.Uint64()) return } -- cgit v1.2.3 From 5cf1d354704cd2cbc5c64c96d4aaabeeec7dd161 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 22 Feb 2018 12:48:14 +0200 Subject: eth, les, light: filter on logs only, derive receipts on demand --- accounts/abi/bind/backends/simulated.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'accounts') diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index bd342a8cb..fe7dea4da 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -428,10 +428,23 @@ func (fb *filterBackend) HeaderByNumber(ctx context.Context, block rpc.BlockNumb } return fb.bc.GetHeaderByNumber(uint64(block.Int64())), nil } + func (fb *filterBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { return core.GetBlockReceipts(fb.db, hash, core.GetBlockNumber(fb.db, hash)), nil } +func (fb *filterBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*types.Log, error) { + receipts := core.GetBlockReceipts(fb.db, hash, core.GetBlockNumber(fb.db, hash)) + if receipts == nil { + return nil, nil + } + logs := make([][]*types.Log, len(receipts)) + for i, receipt := range receipts { + logs[i] = receipt.Logs + } + return logs, nil +} + func (fb *filterBackend) SubscribeTxPreEvent(ch chan<- core.TxPreEvent) event.Subscription { return event.NewSubscription(func(quit <-chan struct{}) error { <-quit -- cgit v1.2.3 From 0b814d32f8737b194874942f11dc3e9e7399cf7b Mon Sep 17 00:00:00 2001 From: protolambda Date: Sun, 4 Mar 2018 23:24:17 +0100 Subject: accounts/abi: Abi binding support for nested arrays, fixes #15648, including nested array unpack fix (#15676) * accounts/abi/bind: support for multi-dim arrays Also: - reduce usage of regexes a bit. - fix minor Java syntax problems Fixes #15648 * accounts/abi/bind: Add some more documentation * accounts/abi/bind: Improve code readability * accounts/abi: bugfix for unpacking nested arrays The code previously assumed the arrays/slices were always 1 level deep. While the packing supports nested arrays (!!!). The current code for unpacking doesn't return the "consumed" length, so this fix had to work around that by calculating it (i.e. packing and getting resulting length) after the unpacking of the array element. It's far from ideal, but unpacking behaviour is fixed now. * accounts/abi: Fix unpacking of nested arrays Removed the temporary workaround of packing to calculate size, which was incorrect for slice-like types anyway. Full size of nested arrays is used now. * accounts/abi: deeply nested array unpack test Test unpacking of an array nested more than one level. * accounts/abi: Add deeply nested array pack test Same as the deep nested array unpack test, but the other way around. * accounts/abi/bind: deeply nested arrays bind test Test the usage of bindings that were generated for methods with multi-dimensional (and not just a single extra dimension, like foo[2][3]) array arguments and returns. edit: trigger rebuild, CI failed to fetch linter module. * accounts/abi/bind: improve array binding wrapArray uses a regex now, and arrayBindingJava is improved. * accounts/abi: Improve naming of element size func The full step size for unpacking an array is now retrieved with "getFullElemSize". * accounts/abi: support nested nested array args Previously, the code only considered the outer-size of the array, ignoring the size of the contents. This was fine for most types, but nested arrays are packed directly into it, and count towards the total size. This resulted in arguments following a nested array to replicate some of the binary contents of the array. The fix: for arrays, calculate their complete contents size: count the arg.Type.Elem.Size when Elem is an Array, and repeat when their child is an array too, etc. The count is the number of 32 byte elements, similar to how it previously counted, but nested. * accounts/abi: Test deep nested arr multi-arguments Arguments with a deeply nested array should not cause the next arguments to be read from the wrong position. --- accounts/abi/argument.go | 26 ++++++- accounts/abi/bind/bind.go | 169 ++++++++++++++++++++++++----------------- accounts/abi/bind/bind_test.go | 66 ++++++++++++++++ accounts/abi/pack_test.go | 5 ++ accounts/abi/unpack.go | 27 +++++-- accounts/abi/unpack_test.go | 45 +++++++++++ 6 files changed, 259 insertions(+), 79 deletions(-) (limited to 'accounts') diff --git a/accounts/abi/argument.go b/accounts/abi/argument.go index f171f4cc6..1b480da60 100644 --- a/accounts/abi/argument.go +++ b/accounts/abi/argument.go @@ -169,6 +169,21 @@ func (arguments Arguments) unpackAtomic(v interface{}, marshalledValues []interf return set(elem, reflectValue, arguments.NonIndexed()[0]) } +// Computes the full size of an array; +// i.e. counting nested arrays, which count towards size for unpacking. +func getArraySize(arr *Type) int { + size := arr.Size + // Arrays can be nested, with each element being the same size + arr = arr.Elem + for arr.T == ArrayTy { + // Keep multiplying by elem.Size while the elem is an array. + size *= arr.Size + arr = arr.Elem + } + // Now we have the full array size, including its children. + return size +} + // UnpackValues can be used to unpack ABI-encoded hexdata according to the ABI-specification, // without supplying a struct to unpack into. Instead, this method returns a list containing the // values. An atomic argument will be a list with one element. @@ -181,9 +196,14 @@ func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error) { // If we have a static array, like [3]uint256, these are coded as // just like uint256,uint256,uint256. // This means that we need to add two 'virtual' arguments when - // we count the index from now on - - virtualArgs += arg.Type.Size - 1 + // we count the index from now on. + // + // Array values nested multiple levels deep are also encoded inline: + // [2][3]uint256: uint256,uint256,uint256,uint256,uint256,uint256 + // + // Calculate the full array size to get the correct offset for the next argument. + // Decrement it by 1, as the normal index increment is still applied. + virtualArgs += getArraySize(&arg.Type) - 1 } if err != nil { return nil, err diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index e31b45481..7fdd2c624 100644 --- a/accounts/abi/bind/bind.go +++ b/accounts/abi/bind/bind.go @@ -164,118 +164,147 @@ var bindType = map[Lang]func(kind abi.Type) string{ LangJava: bindTypeJava, } +// Helper function for the binding generators. +// It reads the unmatched characters after the inner type-match, +// (since the inner type is a prefix of the total type declaration), +// looks for valid arrays (possibly a dynamic one) wrapping the inner type, +// and returns the sizes of these arrays. +// +// Returned array sizes are in the same order as solidity signatures; inner array size first. +// Array sizes may also be "", indicating a dynamic array. +func wrapArray(stringKind string, innerLen int, innerMapping string) (string, []string) { + remainder := stringKind[innerLen:] + //find all the sizes + matches := regexp.MustCompile(`\[(\d*)\]`).FindAllStringSubmatch(remainder, -1) + parts := make([]string, 0, len(matches)) + for _, match := range matches { + //get group 1 from the regex match + parts = append(parts, match[1]) + } + return innerMapping, parts +} + +// Translates the array sizes to a Go-lang declaration of a (nested) array of the inner type. +// Simply returns the inner type if arraySizes is empty. +func arrayBindingGo(inner string, arraySizes []string) string { + out := "" + //prepend all array sizes, from outer (end arraySizes) to inner (start arraySizes) + for i := len(arraySizes) - 1; i >= 0; i-- { + out += "[" + arraySizes[i] + "]" + } + out += inner + return out +} + // bindTypeGo converts a Solidity type to a Go one. Since there is no clear mapping // from all Solidity types to Go ones (e.g. uint17), those that cannot be exactly // mapped will use an upscaled type (e.g. *big.Int). func bindTypeGo(kind abi.Type) string { stringKind := kind.String() + innerLen, innerMapping := bindUnnestedTypeGo(stringKind) + return arrayBindingGo(wrapArray(stringKind, innerLen, innerMapping)) +} + +// The inner function of bindTypeGo, this finds the inner type of stringKind. +// (Or just the type itself if it is not an array or slice) +// The length of the matched part is returned, with the the translated type. +func bindUnnestedTypeGo(stringKind string) (int, string) { switch { case strings.HasPrefix(stringKind, "address"): - parts := regexp.MustCompile(`address(\[[0-9]*\])?`).FindStringSubmatch(stringKind) - if len(parts) != 2 { - return stringKind - } - return fmt.Sprintf("%scommon.Address", parts[1]) + return len("address"), "common.Address" case strings.HasPrefix(stringKind, "bytes"): - parts := regexp.MustCompile(`bytes([0-9]*)(\[[0-9]*\])?`).FindStringSubmatch(stringKind) - if len(parts) != 3 { - return stringKind - } - return fmt.Sprintf("%s[%s]byte", parts[2], parts[1]) + parts := regexp.MustCompile(`bytes([0-9]*)`).FindStringSubmatch(stringKind) + return len(parts[0]), fmt.Sprintf("[%s]byte", parts[1]) case strings.HasPrefix(stringKind, "int") || strings.HasPrefix(stringKind, "uint"): - parts := regexp.MustCompile(`(u)?int([0-9]*)(\[[0-9]*\])?`).FindStringSubmatch(stringKind) - if len(parts) != 4 { - return stringKind - } + parts := regexp.MustCompile(`(u)?int([0-9]*)`).FindStringSubmatch(stringKind) switch parts[2] { case "8", "16", "32", "64": - return fmt.Sprintf("%s%sint%s", parts[3], parts[1], parts[2]) + return len(parts[0]), fmt.Sprintf("%sint%s", parts[1], parts[2]) } - return fmt.Sprintf("%s*big.Int", parts[3]) + return len(parts[0]), "*big.Int" - case strings.HasPrefix(stringKind, "bool") || strings.HasPrefix(stringKind, "string"): - parts := regexp.MustCompile(`([a-z]+)(\[[0-9]*\])?`).FindStringSubmatch(stringKind) - if len(parts) != 3 { - return stringKind - } - return fmt.Sprintf("%s%s", parts[2], parts[1]) + case strings.HasPrefix(stringKind, "bool"): + return len("bool"), "bool" + + case strings.HasPrefix(stringKind, "string"): + return len("string"), "string" default: - return stringKind + return len(stringKind), stringKind } } +// Translates the array sizes to a Java declaration of a (nested) array of the inner type. +// Simply returns the inner type if arraySizes is empty. +func arrayBindingJava(inner string, arraySizes []string) string { + // Java array type declarations do not include the length. + return inner + strings.Repeat("[]", len(arraySizes)) +} + // bindTypeJava converts a Solidity type to a Java one. Since there is no clear mapping // from all Solidity types to Java ones (e.g. uint17), those that cannot be exactly // mapped will use an upscaled type (e.g. BigDecimal). func bindTypeJava(kind abi.Type) string { stringKind := kind.String() + innerLen, innerMapping := bindUnnestedTypeJava(stringKind) + return arrayBindingJava(wrapArray(stringKind, innerLen, innerMapping)) +} + +// The inner function of bindTypeJava, this finds the inner type of stringKind. +// (Or just the type itself if it is not an array or slice) +// The length of the matched part is returned, with the the translated type. +func bindUnnestedTypeJava(stringKind string) (int, string) { switch { case strings.HasPrefix(stringKind, "address"): parts := regexp.MustCompile(`address(\[[0-9]*\])?`).FindStringSubmatch(stringKind) if len(parts) != 2 { - return stringKind + return len(stringKind), stringKind } if parts[1] == "" { - return fmt.Sprintf("Address") + return len("address"), "Address" } - return fmt.Sprintf("Addresses") + return len(parts[0]), "Addresses" case strings.HasPrefix(stringKind, "bytes"): - parts := regexp.MustCompile(`bytes([0-9]*)(\[[0-9]*\])?`).FindStringSubmatch(stringKind) - if len(parts) != 3 { - return stringKind - } - if parts[2] != "" { - return "byte[][]" + parts := regexp.MustCompile(`bytes([0-9]*)`).FindStringSubmatch(stringKind) + if len(parts) != 2 { + return len(stringKind), stringKind } - return "byte[]" + return len(parts[0]), "byte[]" case strings.HasPrefix(stringKind, "int") || strings.HasPrefix(stringKind, "uint"): - parts := regexp.MustCompile(`(u)?int([0-9]*)(\[[0-9]*\])?`).FindStringSubmatch(stringKind) - if len(parts) != 4 { - return stringKind - } - switch parts[2] { - case "8", "16", "32", "64": - if parts[1] == "" { - if parts[3] == "" { - return fmt.Sprintf("int%s", parts[2]) - } - return fmt.Sprintf("int%s[]", parts[2]) - } + //Note that uint and int (without digits) are also matched, + // these are size 256, and will translate to BigInt (the default). + parts := regexp.MustCompile(`(u)?int([0-9]*)`).FindStringSubmatch(stringKind) + if len(parts) != 3 { + return len(stringKind), stringKind } - if parts[3] == "" { - return fmt.Sprintf("BigInt") + + namedSize := map[string]string{ + "8": "byte", + "16": "short", + "32": "int", + "64": "long", + }[parts[2]] + + //default to BigInt + if namedSize == "" { + namedSize = "BigInt" } - return fmt.Sprintf("BigInts") + return len(parts[0]), namedSize case strings.HasPrefix(stringKind, "bool"): - parts := regexp.MustCompile(`bool(\[[0-9]*\])?`).FindStringSubmatch(stringKind) - if len(parts) != 2 { - return stringKind - } - if parts[1] == "" { - return fmt.Sprintf("bool") - } - return fmt.Sprintf("bool[]") + return len("bool"), "boolean" case strings.HasPrefix(stringKind, "string"): - parts := regexp.MustCompile(`string(\[[0-9]*\])?`).FindStringSubmatch(stringKind) - if len(parts) != 2 { - return stringKind - } - if parts[1] == "" { - return fmt.Sprintf("String") - } - return fmt.Sprintf("String[]") + return len("string"), "String" default: - return stringKind + return len(stringKind), stringKind } } @@ -325,11 +354,13 @@ func namedTypeJava(javaKind string, solKind abi.Type) string { return "String" case "string[]": return "Strings" - case "bool": + case "boolean": return "Bool" - case "bool[]": + case "boolean[]": return "Bools" - case "BigInt": + case "BigInt[]": + return "BigInts" + default: parts := regexp.MustCompile(`(u)?int([0-9]*)(\[[0-9]*\])?`).FindStringSubmatch(solKind.String()) if len(parts) != 4 { return javaKind @@ -344,8 +375,6 @@ func namedTypeJava(javaKind string, solKind abi.Type) string { default: return javaKind } - default: - return javaKind } } diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index c4838e647..26816ec20 100644 --- a/accounts/abi/bind/bind_test.go +++ b/accounts/abi/bind/bind_test.go @@ -737,6 +737,72 @@ var bindTests = []struct { } `, }, + { + `DeeplyNestedArray`, + ` + contract DeeplyNestedArray { + uint64[3][4][5] public deepUint64Array; + function storeDeepUintArray(uint64[3][4][5] arr) public { + deepUint64Array = arr; + } + function retrieveDeepArray() public view returns (uint64[3][4][5]) { + return deepUint64Array; + } + } + `, + `6060604052341561000f57600080fd5b6106438061001e6000396000f300606060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063344248551461005c5780638ed4573a1461011457806398ed1856146101ab575b600080fd5b341561006757600080fd5b610112600480806107800190600580602002604051908101604052809291906000905b828210156101055783826101800201600480602002604051908101604052809291906000905b828210156100f25783826060020160038060200260405190810160405280929190826003602002808284378201915050505050815260200190600101906100b0565b505050508152602001906001019061008a565b5050505091905050610208565b005b341561011f57600080fd5b61012761021d565b604051808260056000925b8184101561019b578284602002015160046000925b8184101561018d5782846020020151600360200280838360005b8381101561017c578082015181840152602081019050610161565b505050509050019260010192610147565b925050509260010192610132565b9250505091505060405180910390f35b34156101b657600080fd5b6101de6004808035906020019091908035906020019091908035906020019091905050610309565b604051808267ffffffffffffffff1667ffffffffffffffff16815260200191505060405180910390f35b80600090600561021992919061035f565b5050565b6102256103b0565b6000600580602002604051908101604052809291906000905b8282101561030057838260040201600480602002604051908101604052809291906000905b828210156102ed578382016003806020026040519081016040528092919082600380156102d9576020028201916000905b82829054906101000a900467ffffffffffffffff1667ffffffffffffffff16815260200190600801906020826007010492830192600103820291508084116102945790505b505050505081526020019060010190610263565b505050508152602001906001019061023e565b50505050905090565b60008360058110151561031857fe5b600402018260048110151561032957fe5b018160038110151561033757fe5b6004918282040191900660080292509250509054906101000a900467ffffffffffffffff1681565b826005600402810192821561039f579160200282015b8281111561039e5782518290600461038e9291906103df565b5091602001919060040190610375565b5b5090506103ac919061042d565b5090565b610780604051908101604052806005905b6103c9610459565b8152602001906001900390816103c15790505090565b826004810192821561041c579160200282015b8281111561041b5782518290600361040b929190610488565b50916020019190600101906103f2565b5b5090506104299190610536565b5090565b61045691905b8082111561045257600081816104499190610562565b50600401610433565b5090565b90565b610180604051908101604052806004905b6104726105a7565b81526020019060019003908161046a5790505090565b82600380016004900481019282156105255791602002820160005b838211156104ef57835183826101000a81548167ffffffffffffffff021916908367ffffffffffffffff16021790555092602001926008016020816007010492830192600103026104a3565b80156105235782816101000a81549067ffffffffffffffff02191690556008016020816007010492830192600103026104ef565b505b50905061053291906105d9565b5090565b61055f91905b8082111561055b57600081816105529190610610565b5060010161053c565b5090565b90565b50600081816105719190610610565b50600101600081816105839190610610565b50600101600081816105959190610610565b5060010160006105a59190610610565b565b6060604051908101604052806003905b600067ffffffffffffffff168152602001906001900390816105b75790505090565b61060d91905b8082111561060957600081816101000a81549067ffffffffffffffff0219169055506001016105df565b5090565b90565b50600090555600a165627a7a7230582087e5a43f6965ab6ef7a4ff056ab80ed78fd8c15cff57715a1bf34ec76a93661c0029`, + `[{"constant":false,"inputs":[{"name":"arr","type":"uint64[3][4][5]"}],"name":"storeDeepUintArray","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"retrieveDeepArray","outputs":[{"name":"","type":"uint64[3][4][5]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"},{"name":"","type":"uint256"},{"name":"","type":"uint256"}],"name":"deepUint64Array","outputs":[{"name":"","type":"uint64"}],"payable":false,"stateMutability":"view","type":"function"}]`, + ` + // Generate a new random account and a funded simulator + key, _ := crypto.GenerateKey() + auth := bind.NewKeyedTransactor(key) + sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}) + + //deploy the test contract + _, _, testContract, err := DeployDeeplyNestedArray(auth, sim) + if err != nil { + t.Fatalf("Failed to deploy test contract: %v", err) + } + + // Finish deploy. + sim.Commit() + + //Create coordinate-filled array, for testing purposes. + testArr := [5][4][3]uint64{} + for i := 0; i < 5; i++ { + testArr[i] = [4][3]uint64{} + for j := 0; j < 4; j++ { + testArr[i][j] = [3]uint64{} + for k := 0; k < 3; k++ { + //pack the coordinates, each array value will be unique, and can be validated easily. + testArr[i][j][k] = uint64(i) << 16 | uint64(j) << 8 | uint64(k) + } + } + } + + if _, err := testContract.StoreDeepUintArray(&bind.TransactOpts{ + From: auth.From, + Signer: auth.Signer, + }, testArr); err != nil { + t.Fatalf("Failed to store nested array in test contract: %v", err) + } + + sim.Commit() + + retrievedArr, err := testContract.RetrieveDeepArray(&bind.CallOpts{ + From: auth.From, + Pending: false, + }) + if err != nil { + t.Fatalf("Failed to retrieve nested array from test contract: %v", err) + } + + //quick check to see if contents were copied + // (See accounts/abi/unpack_test.go for more extensive testing) + if retrievedArr[4][3][2] != testArr[4][3][2] { + t.Fatalf("Retrieved value does not match expected value! got: %d, expected: %d. %v", retrievedArr[4][3][2], testArr[4][3][2], err) + }`, + }, } // Tests that packages generated by the binder can be successfully compiled and diff --git a/accounts/abi/pack_test.go b/accounts/abi/pack_test.go index 14ab516ac..58a5b7a58 100644 --- a/accounts/abi/pack_test.go +++ b/accounts/abi/pack_test.go @@ -299,6 +299,11 @@ func TestPack(t *testing.T) { [32]byte{1}, common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), }, + { + "uint32[2][3][4]", + [4][3][2]uint32{{{1, 2}, {3, 4}, {5, 6}}, {{7, 8}, {9, 10}, {11, 12}}, {{13, 14}, {15, 16}, {17, 18}}, {{19, 20}, {21, 22}, {23, 24}}}, + common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001300000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000015000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000018"), + }, { "address[]", []common.Address{{1}, {2}}, diff --git a/accounts/abi/unpack.go b/accounts/abi/unpack.go index 761c80edf..793d515ad 100644 --- a/accounts/abi/unpack.go +++ b/accounts/abi/unpack.go @@ -93,6 +93,17 @@ func readFixedBytes(t Type, word []byte) (interface{}, error) { } +func getFullElemSize(elem *Type) int { + //all other should be counted as 32 (slices have pointers to respective elements) + size := 32 + //arrays wrap it, each element being the same size + for elem.T == ArrayTy { + size *= elem.Size + elem = elem.Elem + } + return size +} + // iteratively unpack elements func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) { if size < 0 { @@ -104,7 +115,6 @@ func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) // this value will become our slice or our array, depending on the type var refSlice reflect.Value - slice := output[start : start+size*32] if t.T == SliceTy { // declare our slice @@ -116,15 +126,20 @@ func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) return nil, fmt.Errorf("abi: invalid type in array/slice unpacking stage") } - for i, j := start, 0; j*32 < len(slice); i, j = i+32, j+1 { - // this corrects the arrangement so that we get all the underlying array values - if t.Elem.T == ArrayTy && j != 0 { - i = start + t.Elem.Size*32*j - } + // Arrays have packed elements, resulting in longer unpack steps. + // Slices have just 32 bytes per element (pointing to the contents). + elemSize := 32 + if t.T == ArrayTy { + elemSize = getFullElemSize(t.Elem) + } + + for i, j := start, 0; j < size; i, j = i+elemSize, j+1 { + inter, err := toGoType(i, *t.Elem, output) if err != nil { return nil, err } + // append the item to our reflect slice refSlice.Index(j).Set(reflect.ValueOf(inter)) } diff --git a/accounts/abi/unpack_test.go b/accounts/abi/unpack_test.go index 742211244..ee6256709 100644 --- a/accounts/abi/unpack_test.go +++ b/accounts/abi/unpack_test.go @@ -189,6 +189,11 @@ var unpackTests = []unpackTest{ enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", want: [2]uint32{1, 2}, }, + { + def: `[{"type": "uint32[2][3][4]"}]`, + enc: "000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001300000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000015000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000018", + want: [4][3][2]uint32{{{1, 2}, {3, 4}, {5, 6}}, {{7, 8}, {9, 10}, {11, 12}}, {{13, 14}, {15, 16}, {17, 18}}, {{19, 20}, {21, 22}, {23, 24}}}, + }, { def: `[{"type": "uint64[]"}]`, enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", @@ -435,6 +440,46 @@ func TestMultiReturnWithArray(t *testing.T) { } } +func TestMultiReturnWithDeeplyNestedArray(t *testing.T) { + // Similar to TestMultiReturnWithArray, but with a special case in mind: + // values of nested static arrays count towards the size as well, and any element following + // after such nested array argument should be read with the correct offset, + // so that it does not read content from the previous array argument. + const definition = `[{"name" : "multi", "outputs": [{"type": "uint64[3][2][4]"}, {"type": "uint64"}]}]` + abi, err := JSON(strings.NewReader(definition)) + if err != nil { + t.Fatal(err) + } + buff := new(bytes.Buffer) + // construct the test array, each 3 char element is joined with 61 '0' chars, + // to from the ((3 + 61) * 0.5) = 32 byte elements in the array. + buff.Write(common.Hex2Bytes(strings.Join([]string{ + "", //empty, to apply the 61-char separator to the first element as well. + "111", "112", "113", "121", "122", "123", + "211", "212", "213", "221", "222", "223", + "311", "312", "313", "321", "322", "323", + "411", "412", "413", "421", "422", "423", + }, "0000000000000000000000000000000000000000000000000000000000000"))) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000009876")) + + ret1, ret1Exp := new([4][2][3]uint64), [4][2][3]uint64{ + {{0x111, 0x112, 0x113}, {0x121, 0x122, 0x123}}, + {{0x211, 0x212, 0x213}, {0x221, 0x222, 0x223}}, + {{0x311, 0x312, 0x313}, {0x321, 0x322, 0x323}}, + {{0x411, 0x412, 0x413}, {0x421, 0x422, 0x423}}, + } + ret2, ret2Exp := new(uint64), uint64(0x9876) + 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" } ] }, -- cgit v1.2.3