feat(abi): support abiV2 include static/dynamics arrays, struct#212
Open
0xbigapple wants to merge 20 commits into
Open
feat(abi): support abiV2 include static/dynamics arrays, struct#2120xbigapple wants to merge 20 commits into
0xbigapple wants to merge 20 commits into
Conversation
…odeWithOutPrefix/buildEventSignatureWithOutPrefix
…r Encoding, add unitest
…ray elements When encoding DynamicArray<StaticArray<DynamicBytes>> (e.g. bytes[3][]),the encoder was missing offset pointer table for inner elements.StaticArray with dynamic sub-elements was not recognized as needing offsets in encodeArrayValuesOffsets()
- Utils.getStructType: @parameterized on StaticArrayN struct fields now correctly emits "foo[N]" instead of collapsing to "foo[]". Throws RuntimeException on ClassNotFoundException instead of silent fallback; extractStaticArraySize now takes Class<?> and validates that the class is a generated StaticArrayN subclass (rejects raw StaticArray.class with a guidance message). - Utils.getParameterizedTypeName: build canonical ABI names for nested arrays via the new getArrayElementTypeName helper, which recurses on TypeReference rather than collapsing the inner type to a Class. The old path produced non-canonical strings like "staticarray2[]". - DefaultFunctionReturnDecoder.getDataOffset: include DynamicStruct in the dynamic-offset detection. The hand-rolled OR-chain was missing it, causing DynamicStruct return values to be decoded from the wrong offset. - DynamicArray.getTypeAsString: throw when an empty array's component type is the generic DynamicStruct/StaticStruct base or a raw Array subclass — Java type erasure cannot recover the inner element type in those cases. Concrete StructType subclasses still resolve through Utils.getStructType. - Utf8String.bytes32PaddedLength: guard against null value, matching the null-tolerance already in equals()/hashCode(). - Array: remove the unused getNativeValueCopy() method. It was dead code in this PR (zero callers) and only unwrapped one level, so nested arrays/structs still contained ABI Type objects rather than native values. - UtilsTest: positive/negative cases for issue tronprotocol#20, DynamicArray regression guard, and canonical nested array type names (uint256[2][], uint256[2][2]). - EventEncoderTest: nested static array method signature. - DynamicArrayTest: empty concrete struct array (allowed), empty raw Array subclass (throws), empty generic struct array (throws). - Utf8StringTest: bytes32PaddedLength with null value. - CustomErrorTest: assert parameter count before per-element comparison; the previous loop could pass silently if getParameters() returned fewer elements than expected. - TridentAbiEncodeDecodeCompatibilityTest.isSignedIntOutOfRange: treat bare `int` as ABI alias for int256.
jj5419952-stack
approved these changes
Jun 7, 2026
…plify DynamicStruct type-string branches
- decode trcToken[N] static arrays: instantiateStaticArray used the deprecated List-only StaticArrayN constructor, which re-derives the element type via AbiTypes.getType(getTypeAsString()); TrcToken's type string is "trcToken256" while its AbiTypes key is "trcToken", so decoding trcToken[N] failed on every path. Switch to the Class-carrying constructor and take the element type from the decoded elements. - fail fast on truncated input in decodeNumeric: Arrays.copyOfRange silently zero-pads past the end of input, and the padding lands in the low-order bytes — a truncated Uint256 word decodes to value << (8 * missing bytes) with no error. Restore the fail-fast contract with a JDK 8-compatible explicit bounds check. - keep T[0] on the static-array branch in instantiateArrayType: arraySize is -1 only for true dynamic-array references, but the dispatch tested <= 0, so uint256[0] silently instantiated as DynamicArray with type string "uint256[]" — a wrong function selector. With < 0, T[0] constructs a proper StaticArray0 and rejects non-empty values loudly. - surface type-resolution failures in isDynamic(TypeReference): the method swallowed ClassNotFoundException/ClassCastException and answered "static", silently mis-placing every subsequent field's offset. Exclude StructType from the array branch explicitly (StaticStruct extends StaticArray), then rethrow resolution failures as UnsupportedOperationException.
Three defects in the constructor-reflection struct decoding path: 1. StaticArrayN struct fields were undecodable: they fell through to decode(String, int, Class), which rejects every Array subclass. New decodeStaticArrayStructField reads the element type from @parameterized (the same contract getStructType uses for signatures), builds the array reference, and decodes via decodeStaticArray. Static arrays of dynamic elements get a clear error pointing at innerTypes instead of garbage. 2. Nested-struct spans were guessed from reflection metadata — getDeclaredFields()[i]/getConstructors()[0] parameter counts in decodeStaticStructElement (wrong for non-public or extra java fields, arbitrary constructor pick, one-word-per-parameter assumption breaking doubly-nested structs), and the public-fields flat list in decodeDynamicStructElements (undercounts private fields). Both now advance by the decoded value's real span (bytes32PaddedLength), the pattern decodeArrayElements already used. 3. getTypeReferenceForParameterizedField round-tripped StaticArrayN element types through a lossy Solidity-name string (getSimpleTypeName lowercases, AbiTypes' lookup is case-sensitive), so TrcToken/Fixed/struct elements threw "Unsupported type encountered". It now builds the StaticArrayTypeReference directly from the classes at hand; getSimpleTypeName gains the camelCase trcToken token for signatures.
…lection path string[2]-style struct fields are ABI-dynamic (head offset + tail) and already encode correctly, but decodeDynamicStructElements misclassified them as static and threw. Classify fields via isDynamicStructField (declared class OR @parameterized element type) at all three decision points, and decode the tail with the same machinery the innerTypes path uses. Add regression tests; assert zero-length static array rejection at the construction layer per review. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ed nested arrays Address three code-review findings on the runtime-tuple and nested-array paths: - Utils.getTypeName: serialize runtime tuple TypeReferences (innerTypes form) as "(type1,type2,...)" so EventEncoder and CustomErrorEncoder can build signatures like E((uint256,string)) without a generated struct class. Previously this form hit constructor reflection on the DynamicStruct or StaticStruct base class and threw. Composes with arrays via getSubTypeReference, e.g. E((uint256,string)[]). - Utils.getTypeReferenceForParameterizedField: fail fast when the @parameterized element type is itself a (non-struct) array class. The annotation carries a single element-type level, so nested array fields (e.g. uint256[2][2] as StaticArray2<StaticArray2<Uint256>>) previously produced invalid signatures like "(staticarray2[2])", threw ClassCastException, or silently misread tail data during decode. Documented as a known limitation of the constructor-reflection path; runtime innerTypes references (makeTypeReference) still support nesting. TypeDecoder.decodeDynamicParameterFromStruct now routes its DynamicArray branch through the same guard. - TypeEncoder.isSupportingEncodedPacked: reject StaticArray component types. Solidity forbids nested arrays in abi.encodePacked; previously the encoder silently fell back to standard ABI encoding, emitting offset/length words that hash to wrong signatures. Checking the direct component type covers all nesting shapes since a nested array's component is itself an array. Adds regression tests across EventEncoderTest, CustomErrorEncoderTest, UtilsTest and TypeEncoderPackedTest.
jj5419952-stack
approved these changes
Jul 14, 2026
- encodePacked: reject structs up front. DynamicStruct/StaticStruct extend the array classes and fell into arrayEncodePacked, silently producing corrupt packed bytes; Solidity's packed mode does not support structs. - trcToken: emit the canonical camelCase "trcToken" from both AbiTypes.getTypeAString and instance-level TrcToken.getTypeAsString (previously "trctoken" / "trcToken256"), so selectors and the getType round-trip agree on every path; the override mirrors Address over its underlying Uint160. - decoding: bound decode()'s numeric/address dispatch slices to one 32-byte word and pass offsets instead of whole-tail substrings in the dynamic-struct paths. Decoding uint256[n] copied O(n^2) characters: uint256[16000] (1 MiB hex) took ~8.4s and now ~28ms. Chain responses are untrusted input, so this was a DoS amplification vector. - StaticArray.getTypeAsString: handle empty struct components the way DynamicArray already does — tuple string via Utils.getStructType, descriptive error for generic struct / nested array / null component — instead of silently hashing lowercased Java class names into selectors. - TypeReference depth validation: allow exactly MAX_TYPEREF_DEPTH nesting (off-by-one) and replace the iterative walker plus cycle set with bounded recursion; cycles necessarily trip the depth cap. - consolidate StaticArrayN size parsing onto Utils.extractStaticArraySize, preferring StaticArrayTypeReference.getSize() where available; decoding uint256[33][2] now fails naming the missing generated StaticArray33 instead of a misleading zero-length-array error. - TypeReference.getSubTypeReference: make the base declaration public (matching current upstream web3j) so subclasses outside the package can actually override it — package-private meant external same-signature methods only shadowed it, so wrapping a runtime-tuple innerTypes reference in a caller-built array reference decoded against a null subtype and threw.
- decode(String, int, TypeReference): every call throws. getRawType().getClass() always yields java.lang.Class, so both isAssignableFrom branches are unreachable. Inherited from web3j and never called inside the SDK; document the defect, the working alternatives (decodeStaticArray / decodeDynamicArray) and the intended fix instead of changing behavior. - Address(Uint) packed width: encodePacked slices Address by the stored Uint's bit size, so only the 160-bit-normalizing constructors (Address(BigInteger), Address(String)) produce the canonical 20 packed bytes, while Address(Uint) packs to 32. Document the pitfall at the constructor and at the removePadding slice.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Adds ABI v2 support for tuples/structs and nested arrays with packed encoding and custom errors.
Why are these changes required?
This PR has been tested by:
Follow up
Extra details