-
Notifications
You must be signed in to change notification settings - Fork 21k
accounts/abi: Abi binding support for nested arrays, fixes #15648, including nested array unpack fix #15676
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
accounts/abi: Abi binding support for nested arrays, fixes #15648, including nested array unpack fix #15676
Changes from all commits
faa190b
b6f0296
2444b61
4ed1e02
c4bf02a
016928c
e2be9e9
7da01ea
cc3a827
8929a3b
1536b4c
c788f13
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be simpler, more readable and arguably more efficient to use the following regular expression There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup, probably a good idea. I'll simplify the implementation 👍 |
||
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 | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've done a rebase on master, and find that if don't even use this method, but leave it as is, I get no test failures. I'll push my rebased branch, with the method still present (but unused), and you can take a look and check if
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check out my my branch: https://github.com/ethereum/go-ethereum/compare/master...holiman:pr_15676?expand=1 . It's rebased on master. PTAL that I didn't miss something, and why it's still working without using
getArraySize
(I guess the corresponding place in the master-code is here: https://github.com/ethereum/go-ethereum/compare/master...holiman:pr_15676?expand=1#diff-04b076f4fee5b5afe22e6efa614e4aceR200 )There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably need tests for the argument unpacking then, it may be very uncommon, but there was definitely a bug.
How to reproduce it (will test later with your rebase, this is what needed to be fixed in the original PR starting point):
So it shows up very rarely, doesn't cause a panic, and only affects multi-valued arguments.
Also consider that there can be multiple levels of nested arrays, all packed together. Hence the for-loop that multiplies them. For completeness: multiplying in the size calculation works because of the array-size symmetry, no need to recursively sum all sizes. Other collections such as slices are just pointers, same size as any other argument, so nothing wrong there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://github.com/ethereum/go-ethereum/compare/master...holiman:pr_15676?expand=1#diff-04b076f4fee5b5afe22e6efa614e4aceR200
Is indeed the relevant part, but it doesn't cover arrays with multiple levels of nesting, only the surface. I'll rebase it with the multi-level size calculation, and add a test for it 👍