Skip to content

feat(abi): support abiV2 include static/dynamics arrays, struct#212

Open
0xbigapple wants to merge 20 commits into
tronprotocol:release_1.0.0from
0xbigapple:feature/support-dynamics-abi
Open

feat(abi): support abiV2 include static/dynamics arrays, struct#212
0xbigapple wants to merge 20 commits into
tronprotocol:release_1.0.0from
0xbigapple:feature/support-dynamics-abi

Conversation

@0xbigapple

Copy link
Copy Markdown
Collaborator

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:

  • Unit Tests
  • Manual Testing

Follow up

Extra details

…odeWithOutPrefix/buildEventSignatureWithOutPrefix
…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()
@0xbigapple 0xbigapple closed this Apr 24, 2026
  - 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.
@0xbigapple 0xbigapple reopened this May 14, 2026
0xbigapple and others added 5 commits July 13, 2026 13:54
…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.
- 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants