From bdeafabe7760b53d5915ff2b3003840c64814850 Mon Sep 17 00:00:00 2001 From: protolambda Date: Thu, 14 Dec 2017 20:41:59 +0100 Subject: [PATCH 01/11] 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/bind.go | 172 ++++++++++++++++++++++---------------- 1 file changed, 102 insertions(+), 70 deletions(-) diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index e31b454812..a3a40bae71 100644 --- a/accounts/abi/bind/bind.go +++ b/accounts/abi/bind/bind.go @@ -164,118 +164,150 @@ var bindType = map[Lang]func(kind abi.Type) string{ LangJava: bindTypeJava, } + +func wrapArray(stringKind string, innerLen int, innerMapping string) (string, []string) { + remainder := stringKind[innerLen:] + if len(remainder) > 0 { + if remainder[0] != '[' { + //strange, expected an array bracket, default on what was recognized. + return innerMapping, nil + } + parts := strings.Split(remainder[1:], "[") + //fix elements, splitting made the "[" disappear, but not the "]" + for i := 0; i < len(parts); i++ { + v := parts[i] + //check validity; ending with "]" + if len(v) < 1 || v[len(v)-1] != ']' { + //format was not a valid array + return innerMapping, nil + } + //chop off "]" + parts[i] = v[:len(v)-1] + } + return innerMapping, parts + } else { + return innerMapping, nil + } +} + +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 } } + +func arrayBindingJava(inner string, arraySizes []string) string { + out := inner + //append a "[]" for each array size. + for i := 0; i < len(arraySizes); i++ { + //normally, like with Go, you could declare an array size. Not in Java. + out += "[]" + } + return out +} + // 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 + parts := regexp.MustCompile(`bytes([0-9]*)`).FindStringSubmatch(stringKind) + if len(parts) != 2 { + return len(stringKind), stringKind } - if parts[2] != "" { - return "byte[][]" - } - 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 + parts := regexp.MustCompile(`(u)?int([0-9]*)`).FindStringSubmatch(stringKind) + if len(parts) != 3 { + return len(stringKind), 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]) - } + case "8": + return len(parts[0]), "byte" + case "16": + return len(parts[0]), "short" + case "32": + return len(parts[0]), "int" + case "64": + return len(parts[0]), "long" } - if parts[3] == "" { - return fmt.Sprintf("BigInt") - } - return fmt.Sprintf("BigInts") - + return len(parts[0]), "BigInt" 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 +357,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 +378,6 @@ func namedTypeJava(javaKind string, solKind abi.Type) string { default: return javaKind } - default: - return javaKind } } From a31bc94a551847984295a92b8bf546e507999869 Mon Sep 17 00:00:00 2001 From: protolambda Date: Thu, 14 Dec 2017 20:51:40 +0100 Subject: [PATCH 02/11] accounts/abi/bind: Add some more documentation --- accounts/abi/bind/bind.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index a3a40bae71..749dbea537 100644 --- a/accounts/abi/bind/bind.go +++ b/accounts/abi/bind/bind.go @@ -164,7 +164,14 @@ 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:] if len(remainder) > 0 { @@ -190,6 +197,8 @@ func wrapArray(stringKind string, innerLen int, innerMapping string) (string, [] } } +// 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) @@ -241,10 +250,12 @@ func bindUnnestedTypeGo(stringKind string) (int, string) { } } - +// 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 { out := inner //append a "[]" for each array size. + // (Full arraySizes is used in signature for consistency with the go-lang version) for i := 0; i < len(arraySizes); i++ { //normally, like with Go, you could declare an array size. Not in Java. out += "[]" From 0e147cce0f6c9af032e6272dd214d5fd67a6898a Mon Sep 17 00:00:00 2001 From: protolambda Date: Thu, 28 Dec 2017 21:23:21 +0100 Subject: [PATCH 03/11] accounts/abi/bind: Improve code readability --- accounts/abi/bind/bind.go | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index 749dbea537..0109c0a8e4 100644 --- a/accounts/abi/bind/bind.go +++ b/accounts/abi/bind/bind.go @@ -296,21 +296,26 @@ func bindUnnestedTypeJava(stringKind string) (int, string) { return len(parts[0]), "byte[]" case strings.HasPrefix(stringKind, "int") || strings.HasPrefix(stringKind, "uint"): + //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 } - switch parts[2] { - case "8": - return len(parts[0]), "byte" - case "16": - return len(parts[0]), "short" - case "32": - return len(parts[0]), "int" - case "64": - return len(parts[0]), "long" + + namedSize := map[string]string{ + "8": "byte", + "16": "short", + "32": "int", + "64": "long", + }[parts[2]] + + //default to BigInt + if namedSize == "" { + namedSize = "BigInt" } - return len(parts[0]), "BigInt" + return len(parts[0]), namedSize + case strings.HasPrefix(stringKind, "bool"): return len("bool"), "boolean" From f428ef3e5d009846f35ae1691a1de854f120aa93 Mon Sep 17 00:00:00 2001 From: protolambda Date: Fri, 29 Dec 2017 00:34:33 +0100 Subject: [PATCH 04/11] 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/unpack.go | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/accounts/abi/unpack.go b/accounts/abi/unpack.go index 761c80edfd..33b5b994c5 100644 --- a/accounts/abi/unpack.go +++ b/accounts/abi/unpack.go @@ -104,7 +104,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,17 +115,25 @@ 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 - } + for i, j := start, 0; j < size; j++ { inter, err := toGoType(i, *t.Elem, output) if err != nil { return nil, err } + + reflectedInter := reflect.ValueOf(inter) + + //Although we just did the reverse, pack it, to get the length of the actual element. + //Getting the length directly from the "toGoType" would be way better, + // but it requires some refactoring to get it return the *consumed* length. + interPacked, err := t.Elem.pack(reflectedInter) + if err != nil { + return nil, err + } + i += len(interPacked) + // append the item to our reflect slice - refSlice.Index(j).Set(reflect.ValueOf(inter)) + refSlice.Index(j).Set(reflectedInter) } // return the interface From 0e69ad846e5a1516463af65ac75d9133171d0d2b Mon Sep 17 00:00:00 2001 From: protolambda Date: Sat, 30 Dec 2017 17:22:58 +0100 Subject: [PATCH 05/11] 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/unpack.go | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/accounts/abi/unpack.go b/accounts/abi/unpack.go index 33b5b994c5..ac56435d55 100644 --- a/accounts/abi/unpack.go +++ b/accounts/abi/unpack.go @@ -93,6 +93,17 @@ func readFixedBytes(t Type, word []byte) (interface{}, error) { } +func getDeepSizeForType(t *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 t.T == ArrayTy { + size *= t.Size + t = t.Elem + } + return size +} + // iteratively unpack elements func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) { if size < 0 { @@ -115,25 +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 < size; j++ { + elemSize := 32 + if t.T == ArrayTy { + elemSize = getDeepSizeForType(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 } - reflectedInter := reflect.ValueOf(inter) - - //Although we just did the reverse, pack it, to get the length of the actual element. - //Getting the length directly from the "toGoType" would be way better, - // but it requires some refactoring to get it return the *consumed* length. - interPacked, err := t.Elem.pack(reflectedInter) - if err != nil { - return nil, err - } - i += len(interPacked) - // append the item to our reflect slice - refSlice.Index(j).Set(reflectedInter) + refSlice.Index(j).Set(reflect.ValueOf(inter)) } // return the interface From 0aeef1f758880fe9e96f8dbeff3582d8d7f1ee9a Mon Sep 17 00:00:00 2001 From: protolambda Date: Sat, 30 Dec 2017 17:45:21 +0100 Subject: [PATCH 06/11] accounts/abi: deeply nested array unpack test Test unpacking of an array nested more than one level. --- accounts/abi/unpack_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/accounts/abi/unpack_test.go b/accounts/abi/unpack_test.go index 742211244b..fabebb37a9 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", From 8023833bec5535cf060a827ce9090ca258b1e3e6 Mon Sep 17 00:00:00 2001 From: protolambda Date: Sat, 30 Dec 2017 17:53:51 +0100 Subject: [PATCH 07/11] accounts/abi: Add deeply nested array pack test Same as the deep nested array unpack test, but the other way around. --- accounts/abi/pack_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/accounts/abi/pack_test.go b/accounts/abi/pack_test.go index 14ab516ac2..58a5b7a581 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}}, From b915180d04a7eabb989ff858a964568b2a24076e Mon Sep 17 00:00:00 2001 From: protolambda Date: Sat, 30 Dec 2017 18:10:54 +0100 Subject: [PATCH 08/11] 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/bind.go | 2 +- accounts/abi/bind/bind_test.go | 66 ++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index 0109c0a8e4..6d19830eae 100644 --- a/accounts/abi/bind/bind.go +++ b/accounts/abi/bind/bind.go @@ -304,7 +304,7 @@ func bindUnnestedTypeJava(stringKind string) (int, string) { } namedSize := map[string]string{ - "8": "byte", + "8": "byte", "16": "short", "32": "int", "64": "long", diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index c4838e6470..26816ec20d 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 From f0d7ace15523fd4ccd11219b60d25afaed8b0b93 Mon Sep 17 00:00:00 2001 From: protolambda Date: Sat, 6 Jan 2018 13:27:20 +0100 Subject: [PATCH 09/11] accounts/abi/bind: improve array binding wrapArray uses a regex now, and arrayBindingJava is improved. --- accounts/abi/bind/bind.go | 37 +++++++++---------------------------- 1 file changed, 9 insertions(+), 28 deletions(-) diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index 6d19830eae..7fdd2c624f 100644 --- a/accounts/abi/bind/bind.go +++ b/accounts/abi/bind/bind.go @@ -174,27 +174,14 @@ var bindType = map[Lang]func(kind abi.Type) string{ // Array sizes may also be "", indicating a dynamic array. func wrapArray(stringKind string, innerLen int, innerMapping string) (string, []string) { remainder := stringKind[innerLen:] - if len(remainder) > 0 { - if remainder[0] != '[' { - //strange, expected an array bracket, default on what was recognized. - return innerMapping, nil - } - parts := strings.Split(remainder[1:], "[") - //fix elements, splitting made the "[" disappear, but not the "]" - for i := 0; i < len(parts); i++ { - v := parts[i] - //check validity; ending with "]" - if len(v) < 1 || v[len(v)-1] != ']' { - //format was not a valid array - return innerMapping, nil - } - //chop off "]" - parts[i] = v[:len(v)-1] - } - return innerMapping, parts - } else { - return innerMapping, nil + //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. @@ -253,14 +240,8 @@ func bindUnnestedTypeGo(stringKind string) (int, string) { // 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 { - out := inner - //append a "[]" for each array size. - // (Full arraySizes is used in signature for consistency with the go-lang version) - for i := 0; i < len(arraySizes); i++ { - //normally, like with Go, you could declare an array size. Not in Java. - out += "[]" - } - return out + // 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 6f664898d36875cab3a107d7b25c190afedd81bb Mon Sep 17 00:00:00 2001 From: protolambda Date: Sat, 6 Jan 2018 13:45:18 +0100 Subject: [PATCH 10/11] accounts/abi: Improve naming of element size func The full step size for unpacking an array is now retrieved with "getFullElemSize". --- accounts/abi/unpack.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/accounts/abi/unpack.go b/accounts/abi/unpack.go index ac56435d55..793d515adf 100644 --- a/accounts/abi/unpack.go +++ b/accounts/abi/unpack.go @@ -93,13 +93,13 @@ func readFixedBytes(t Type, word []byte) (interface{}, error) { } -func getDeepSizeForType(t *Type) int { +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 t.T == ArrayTy { - size *= t.Size - t = t.Elem + for elem.T == ArrayTy { + size *= elem.Size + elem = elem.Elem } return size } @@ -126,9 +126,11 @@ func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) return nil, fmt.Errorf("abi: invalid type in array/slice unpacking stage") } + // 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 = getDeepSizeForType(t.Elem) + elemSize = getFullElemSize(t.Elem) } for i, j := start, 0; j < size; i, j = i+elemSize, j+1 { From 2c60912b2880d18ab3a77fac52c777c3e3979527 Mon Sep 17 00:00:00 2001 From: protolambda Date: Thu, 25 Jan 2018 16:24:51 +0100 Subject: [PATCH 11/11] 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/argument.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/accounts/abi/argument.go b/accounts/abi/argument.go index f171f4cc63..7a3a04e72c 100644 --- a/accounts/abi/argument.go +++ b/accounts/abi/argument.go @@ -85,7 +85,6 @@ func (arguments Arguments) isTuple() bool { // Unpack performs the operation hexdata -> Go format func (arguments Arguments) Unpack(v interface{}, data []byte) error { - // make sure the passed value is arguments pointer if reflect.Ptr != reflect.ValueOf(v).Kind() { return fmt.Errorf("abi: Unpack(non-pointer %T)", v) @@ -100,6 +99,21 @@ func (arguments Arguments) Unpack(v interface{}, data []byte) error { return arguments.unpackAtomic(v, marshalledValues) } +// 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 +} + func (arguments Arguments) unpackTuple(v interface{}, marshalledValues []interface{}) error { var (