Skip to content

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

Open
0xbigapple wants to merge 4 commits intorelease_1.0.0from
feature/support-dynamics-abi
Open

feat(abi): support abiV2 include static/dynamics arrays, struct#5
0xbigapple wants to merge 4 commits intorelease_1.0.0from
feature/support-dynamics-abi

Conversation

@0xbigapple
Copy link
Copy Markdown
Owner

@0xbigapple 0xbigapple commented Mar 25, 2026

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


Summary by cubic

Adds ABI v2 support for tuples/structs, nested arrays, and custom errors with packed encoding. Also adds selector/event helpers and fixes encoding bugs with nested dynamic arrays and UTF‑8 handling.

  • New Features

    • Encode/decode tuples/structs and nested arrays (static and dynamic), including zero-length arrays via StaticArray0.
    • Custom errors: CustomError and CustomErrorEncoder for signature hashing and encoding.
    • Packed encoding for bytes/string and complex types in TypeEncoder.
    • DefaultFunctionEncoder.encodeWithSelector(...) to build calls from a precomputed selector.
    • Event signature helpers without the 0x prefix: EventEncoder.encodeWithOutPrefix(...) and buildEventSignatureWithOutPrefix(...).
    • Better struct/array introspection and reflection, including inner types in TypeReference and class-by-name resolution in AbiTypes.
  • Bug Fixes

    • Proper offset pointer tables for DynamicArray<StaticArray<DynamicBytes>> (e.g., bytes[3][]) so nested dynamic elements encode correctly.
    • Correct Utf8String.bytes32PaddedLength() for empty and multi-chunk strings; fix non-ASCII UTF‑8 length/encoding.
    • Correct BytesType.bytes32PaddedLength() for payloads >32 bytes and exact multiples.

Written for commit 83d18d3. Summary will update on new commits.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added custom error encoding support for Ethereum custom errors
    • Added packed ABI encoding (abi.encodePacked) for multiple data types
    • Added convenience decoding methods for dynamic bytes and addresses
    • Enhanced struct support for nested types and multi-dimensional arrays
  • Improvements

    • Improved handling of dynamic and static structs in encoding/decoding
    • Better support for arrays of structs and complex nested types
    • Optimized encoder/decoder initialization for improved performance

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 25, 2026

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.49% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(abi): support abiV2 include static/dynamics arrays, struct' directly addresses the main changes: adding ABI v2 support for static/dynamic arrays and structs as documented in the PR objectives.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/support-dynamics-abi

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the abi module to support Solidity ABI v2 features (tuples/structs, nested static/dynamic arrays) and introduces packed encoding plus custom error signature encoding, with expanded unit test coverage.

Changes:

  • Add ABI v2 tuple/struct + nested array type-string handling and decoding/encoding support (including empty static arrays via StaticArray0).
  • Introduce abi.encodePacked-style APIs (TypeEncoder.encodePacked, FunctionEncoder.encodeConstructorPacked) and custom errors (CustomError, CustomErrorEncoder).
  • Change EventEncoder.buildEventSignature(...)/encode(...) to return topic hash without the 0x prefix and update tests accordingly.

Reviewed changes

Copilot reviewed 36 out of 37 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
abi/src/test/java/org/tron/trident/abi/datatypes/StaticStructTest.java Adds type-string test for static structs containing arrays.
abi/src/test/java/org/tron/trident/abi/datatypes/StaticArrayTest.java Adds empty static array (StaticArray0) type-string test.
abi/src/test/java/org/tron/trident/abi/datatypes/DynamicArrayTest.java Adds dynamic array type-string tests (empty, nested, structs).
abi/src/test/java/org/tron/trident/abi/datatypes/CustomErrorTest.java Adds basic CustomError construction test.
abi/src/test/java/org/tron/trident/abi/TypeEncoderTest.java Adds ABI v2 encoding tests for arrays/structs and dynamic bytes in structs.
abi/src/test/java/org/tron/trident/abi/TypeEncoderPackedTest.java Adds packed-encoding test coverage across many ABI types.
abi/src/test/java/org/tron/trident/abi/TypeDecoderTest.java Updates/extends decoding tests for nested arrays and structs.
abi/src/test/java/org/tron/trident/abi/EventEncoderTest.java Updates expected event topic hashes (no 0x) + adds complex signature tests.
abi/src/test/java/org/tron/trident/abi/DefaultFunctionEncoderTest.java Adds ABI v2 signature + packed-constructor encoding tests.
abi/src/test/java/org/tron/trident/abi/CustomErrorEncoderTest.java Adds custom error signature hashing and signature-building tests.
abi/src/test/java/org/tron/trident/abi/AbiV2TestFixture.java Expands fixture types/functions/events used by ABI v2 tests.
abi/src/main/java/org/tron/trident/abi/datatypes/reflection/Parameterized.java Adds annotation to preserve erased generic element types for reflection.
abi/src/main/java/org/tron/trident/abi/datatypes/primitive/Int.java Adjusts primitive Int generic parameter type.
abi/src/main/java/org/tron/trident/abi/datatypes/generated/StaticArray0.java Introduces zero-length static array type.
abi/src/main/java/org/tron/trident/abi/datatypes/Utf8String.java Alters padded-length calculation for strings.
abi/src/main/java/org/tron/trident/abi/datatypes/StaticStruct.java Updates tuple type-string rendering to handle nested arrays/structs.
abi/src/main/java/org/tron/trident/abi/datatypes/StaticArray.java Updates component-type inference + type-string rendering for arrays/struct elements.
abi/src/main/java/org/tron/trident/abi/datatypes/Function.java Uses static import for Utils.convert(...).
abi/src/main/java/org/tron/trident/abi/datatypes/Event.java Uses static import for Utils.convert(...).
abi/src/main/java/org/tron/trident/abi/datatypes/DynamicStruct.java Updates tuple type-string rendering for nested arrays/structs.
abi/src/main/java/org/tron/trident/abi/datatypes/DynamicArray.java Updates component-type inference + type-string rendering (including empty arrays).
abi/src/main/java/org/tron/trident/abi/datatypes/CustomError.java Adds custom error wrapper type.
abi/src/main/java/org/tron/trident/abi/datatypes/BytesType.java Alters padded-length calculation for bytes types.
abi/src/main/java/org/tron/trident/abi/datatypes/Array.java Adds getNativeValueCopy() helper.
abi/src/main/java/org/tron/trident/abi/datatypes/AbiTypes.java Extends type resolution + clarifies type-string behavior for structs.
abi/src/main/java/org/tron/trident/abi/Utils.java Adds reflection helpers for struct signatures, constructor discovery, nested field flattening.
abi/src/main/java/org/tron/trident/abi/TypeReference.java Adds innerTypes support and improves type-name resolution for older Java.
abi/src/main/java/org/tron/trident/abi/TypeEncoder.java Adds dynamic-type detection improvements + packed encoding + array/struct encoding adjustments.
abi/src/main/java/org/tron/trident/abi/TypeDecoder.java Major ABI v2 decoding updates for structs/arrays (including nested and innerTypes).
abi/src/main/java/org/tron/trident/abi/FunctionReturnDecoder.java Refactors decoder initialization; adds convenience decode methods.
abi/src/main/java/org/tron/trident/abi/FunctionEncoder.java Refactors encoder initialization; adds encode(methodId, params) + packed constructor encoding API.
abi/src/main/java/org/tron/trident/abi/EventEncoder.java Removes 0x prefix from event signature hashes.
abi/src/main/java/org/tron/trident/abi/DefaultFunctionReturnDecoder.java Updates offset computation for ABI v2 structs/arrays.
abi/src/main/java/org/tron/trident/abi/DefaultFunctionEncoder.java Adds encodeWithSelector + packed parameter encoding + ABI v2 tuple-head sizing changes.
abi/src/main/java/org/tron/trident/abi/CustomErrorEncoder.java Adds custom error signature building + hashing.
Comments suppressed due to low confidence (1)

abi/src/main/java/org/tron/trident/abi/TypeEncoder.java:379

  • encodeArrayValuesOffsets() computes Utf8String element sizes using ((String) ...).length(), which counts UTF-16 code units, not UTF-8 bytes. Offsets will be wrong for non-ASCII strings. Use the UTF-8 byte length (e.g., getBytes(UTF_8).length) when computing words/padding for string elements.
          int bytesLength =
              arrayOfBytes
                  ? ((byte[]) value.getValue().get(i - 1).getValue()).length
                  : ((String) value.getValue().get(i - 1).getValue()).length();
          int numberOfWords = (bytesLength + MAX_BYTE_LENGTH - 1) / MAX_BYTE_LENGTH;
          int totalBytesLength = numberOfWords * MAX_BYTE_LENGTH;
          offset += totalBytesLength + MAX_BYTE_LENGTH;

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +439 to +450
if (isSupportingEncodedPacked(values)) {
if (values.getValue().isEmpty()) {
return "";
}
if (values instanceof DynamicArray) {
return encode(values).substring(64);
} else if (values instanceof StaticArray) {
return encode(values);
}
}
throw new UnsupportedOperationException(
"Type cannot be packed encoded: " + values.getClass());
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arrayEncodePacked() currently derives packed array encoding by calling encode(values) and stripping the dynamic array length (or returning the static array encoding as-is). This keeps 32-byte padding per element, which is inconsistent with encodePacked(Type) for scalar numeric/address/bool types (which removes padding based on bit size) and does not implement tight packing for arrays. Consider implementing packed array encoding as concatenation of encodePacked(element) for each element (and for DynamicArray, omitting the length prefix) to match the method’s documented behavior.

Suggested change
if (isSupportingEncodedPacked(values)) {
if (values.getValue().isEmpty()) {
return "";
}
if (values instanceof DynamicArray) {
return encode(values).substring(64);
} else if (values instanceof StaticArray) {
return encode(values);
}
}
throw new UnsupportedOperationException(
"Type cannot be packed encoded: " + values.getClass());
if (!isSupportingEncodedPacked(values)) {
throw new UnsupportedOperationException(
"Type cannot be packed encoded: " + values.getClass());
}
if (values.getValue().isEmpty()) {
return "";
}
StringBuilder result = new StringBuilder();
for (T element : values.getValue()) {
result.append(encodePacked(element));
}
return result.toString();

Copilot uses AI. Check for mistakes.
Comment on lines +30 to +40
/**
* Returns the Bytes32 Padded length. If the string is empty, we only encode its length. Else,
* we concatenate its length along of its value
*/
@Override
public int bytes32PaddedLength() {
return 2 * MAX_BYTE_LENGTH;
if (value.isEmpty()) {
return MAX_BYTE_LENGTH;
} else {
return 2 * MAX_BYTE_LENGTH;
}
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Utf8String.bytes32PaddedLength() currently returns 32 for empty strings and 64 for all non-empty strings, which is incorrect for strings longer than 32 bytes (and can break offset calculations for ABI v2). bytes32PaddedLength should be 32 (length word) + ceil(utf8ByteLen/32)*32, computed from the UTF-8 byte length.

Copilot uses AI. Check for mistakes.
Comment on lines +33 to +38
if (value.length < MAX_BYTE_LENGTH) {
return MAX_BYTE_LENGTH;
} else if (value.length % MAX_BYTE_LENGTH == 0) {
return value.length;
}
return (value.length / MAX_BYTE_LENGTH + 1) * MAX_BYTE_LENGTH;
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BytesType.bytes32PaddedLength() now returns the padded data length only, but DynamicBytes inherits this and needs to account for the 32-byte length word as well (and fixed-size BytesN types should always be 32 bytes). As-is, bytes32PaddedLength for DynamicBytes is too small and will miscompute offsets/lengths. Consider making BytesType return 32 for fixed bytes, and overriding bytes32PaddedLength in DynamicBytes (or branching on type == "bytes") to return 32 + ceil(len/32)*32.

Suggested change
if (value.length < MAX_BYTE_LENGTH) {
return MAX_BYTE_LENGTH;
} else if (value.length % MAX_BYTE_LENGTH == 0) {
return value.length;
}
return (value.length / MAX_BYTE_LENGTH + 1) * MAX_BYTE_LENGTH;
// Dynamic bytes ("bytes") are encoded as a 32-byte length word followed by
// the data right-padded to a multiple of 32 bytes.
if ("bytes".equals(type)) {
int dataLength = value != null ? value.length : 0;
int paddedDataLength =
((dataLength + MAX_BYTE_LENGTH - 1) / MAX_BYTE_LENGTH) * MAX_BYTE_LENGTH;
return MAX_BYTE_LENGTH + paddedDataLength;
}
// Fixed-size bytesN types are always encoded into a single 32-byte word.
return MAX_BYTE_LENGTH;

Copilot uses AI. Check for mistakes.
Comment on lines +501 to +503
if (classType.isAssignableFrom(DynamicStruct.class)) {
return (T) new DynamicStruct((List<Type>) parameters);
} else if (classType.isAssignableFrom(StaticStruct.class)) {
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeDecoder.instantiateStruct() uses reversed assignability checks: classType.isAssignableFrom(DynamicStruct.class) / StaticStruct.class. For user-defined structs that extend DynamicStruct/StaticStruct this will be false, so the special-casing never triggers. This should be DynamicStruct.class.isAssignableFrom(classType) (and similarly for StaticStruct) so subclasses are handled correctly.

Suggested change
if (classType.isAssignableFrom(DynamicStruct.class)) {
return (T) new DynamicStruct((List<Type>) parameters);
} else if (classType.isAssignableFrom(StaticStruct.class)) {
if (DynamicStruct.class.isAssignableFrom(classType)) {
return (T) new DynamicStruct((List<Type>) parameters);
} else if (StaticStruct.class.isAssignableFrom(classType)) {

Copilot uses AI. Check for mistakes.
Comment on lines +543 to +545
if (typeReference.getClassType().isAssignableFrom(DynamicStruct.class)
&& typeReference.getInnerTypes() != null) {
return decodeDynamicStructElementsFromInnerTypes(
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

decodeDynamicStruct() has the same reversed assignability check: typeReference.getClassType().isAssignableFrom(DynamicStruct.class) will be false for structs that extend DynamicStruct, so the innerTypes-based decoding path is skipped even when innerTypes are provided. Flip the check to DynamicStruct.class.isAssignableFrom(typeReference.getClassType()).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Nitpick comments (16)
abi/src/main/java/org/tron/trident/abi/datatypes/primitive/Int.java (1)

19-19: Consider using the simple name Integer instead of fully qualified java.lang.Integer.

The fully qualified name is functionally identical but unnecessarily verbose. Java convention prefers the simple name when there's no ambiguity, since java.lang is implicitly imported.

♻️ Simplify to idiomatic Java style
-public final class Int extends Number<java.lang.Integer> {
+public final class Int extends Number<Integer> {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@abi/src/main/java/org/tron/trident/abi/datatypes/primitive/Int.java` at line
19, Replace the fully-qualified type java.lang.Integer with the simple name
Integer in the generic declaration of the Int class (the declaration "public
final class Int extends Number<java.lang.Integer>"). Update the declaration to
use Integer so it follows Java conventions and removes unnecessary verbosity
while keeping the same semantics.
abi/src/main/java/org/tron/trident/abi/datatypes/DynamicStruct.java (1)

55-62: Consider simplifying the conditional branches.

Both the StructType/DynamicArray branch and the Array branch call getValue().get(i).getTypeAsString(). These could be combined:

♻️ Optional simplification
-      if (StructType.class.isAssignableFrom(cls)
-          || DynamicArray.class.isAssignableFrom(cls)) {
-        type.append(getValue().get(i).getTypeAsString());
-      } else if (Array.class.isAssignableFrom(cls)) {
+      if (StructType.class.isAssignableFrom(cls)
+          || Array.class.isAssignableFrom(cls)) {
         type.append(getValue().get(i).getTypeAsString());
       } else {

Since DynamicArray extends Array, checking Array.class.isAssignableFrom(cls) would cover both cases.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@abi/src/main/java/org/tron/trident/abi/datatypes/DynamicStruct.java` around
lines 55 - 62, The three-branch conditional in DynamicStruct (checking
StructType.class, DynamicArray.class, and Array.class) is redundant because
DynamicArray is an Array; simplify by first checking
Array.class.isAssignableFrom(cls) (which will cover DynamicArray and Array) and
calling type.append(getValue().get(i).getTypeAsString()), else fall back to
type.append(AbiTypes.getTypeAString(cls)); update the conditional around cls,
getValue().get(i).getTypeAsString(), and AbiTypes.getTypeAString to remove the
duplicated branch.
abi/src/test/java/org/tron/trident/abi/datatypes/StaticStructTest.java (1)

23-31: Make the expected ABI string literal explicit.

Lines 24-30 build expected from getTypeAsString() on the component values. If Address or StaticArray4 rendering regresses in the same way as StaticStruct, this test still passes. Hardcode the ABI literal so the assertion stays independent.

🧪 Suggested change
-    // (address,address,uint256[4])
-    String expected =
-        "("
-            + address1.getTypeAsString()
-            + ","
-            + address2.getTypeAsString()
-            + ","
-            + array4.getTypeAsString()
-            + ")";
+    String expected = "(address,address,uint256[4])";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@abi/src/test/java/org/tron/trident/abi/datatypes/StaticStructTest.java`
around lines 23 - 31, Replace the dynamically-built `expected` string in
StaticStructTest with a hardcoded ABI literal so the test no longer depends on
component `getTypeAsString()` implementations: instead of concatenating
address1.getTypeAsString(), address2.getTypeAsString(), and
array4.getTypeAsString(), set `expected` to the explicit ABI string
"(address,address,uint256[4])" (refer to the `expected` variable in
StaticStructTest and the Address/StaticArray4 component types to locate the
code).
abi/src/test/java/org/tron/trident/abi/FunctionReturnDecoderTest.java (1)

1666-1707: Consider removing or tracking the commented-out test.

This commented-out test for multi-dimensional dynamic array decoding should either be removed if no longer needed, or have a tracking issue/TODO with context explaining why it's disabled and when it should be re-enabled.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@abi/src/test/java/org/tron/trident/abi/FunctionReturnDecoderTest.java` around
lines 1666 - 1707, The test method testDecodeMultiDimDynamicArrayFunction in
FunctionReturnDecoderTest is commented out; either remove it or add a
TODO/ticket reference explaining why it's disabled and when to re-enable
it—update the file by deleting the commented block if obsolete, or replace the
comment with a short "// TODO: <tracking-issue-id> - reason and re-enable
criteria" referencing the related fixture
AbiV2TestFixture.setGetMultiDimDynamicArrayFunction and the decoding logic in
FunctionReturnDecoder so future readers know the intent.
abi/src/test/java/org/tron/trident/abi/CustomErrorEncoderTest.java (1)

56-77: Test methods have inconsistent visibility modifiers.

Tests testBuildErrorSignatureWithDynamicStructs and testBuildErrorSignatureWithDynamicArrays use package-private visibility (void) while other tests in this class use public void. Consider making visibility consistent.

♻️ Proposed fix for consistency
   `@Test`
-  void testBuildErrorSignatureWithDynamicStructs() {
+  public void testBuildErrorSignatureWithDynamicStructs() {
     ...
   }

   `@Test`
-  void testBuildErrorSignatureWithDynamicArrays() {
+  public void testBuildErrorSignatureWithDynamicArrays() {
     ...
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@abi/src/test/java/org/tron/trident/abi/CustomErrorEncoderTest.java` around
lines 56 - 77, Two test methods, testBuildErrorSignatureWithDynamicStructs and
testBuildErrorSignatureWithDynamicArrays, use package-private visibility while
the rest of the tests are public; update their method signatures in
CustomErrorEncoderTest (the two methods named exactly) to be public void so
visibility is consistent with other tests and the test framework picks them up
consistently.
abi/src/main/java/org/tron/trident/abi/datatypes/DynamicArray.java (1)

84-90: Consider consolidating duplicate conditional branches.

Lines 84-87 and 86-87 both return value.get(0).getTypeAsString() for StructType and Array cases. These can be merged into a single condition.

♻️ Proposed simplification
     } else {
-      if (StructType.class.isAssignableFrom(value.get(0).getClass())) {
-        type = value.get(0).getTypeAsString();
-      } else if (Array.class.isAssignableFrom(value.get(0).getClass())) {
+      if (StructType.class.isAssignableFrom(value.get(0).getClass())
+          || Array.class.isAssignableFrom(value.get(0).getClass())) {
         type = value.get(0).getTypeAsString();
       } else {
         type = AbiTypes.getTypeAString(getComponentType());
       }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@abi/src/main/java/org/tron/trident/abi/datatypes/DynamicArray.java` around
lines 84 - 90, In DynamicArray, consolidate the duplicate branch that returns
value.get(0).getTypeAsString() by combining the StructType and Array checks into
a single condition (e.g., if (StructType.class.isAssignableFrom(...) ||
Array.class.isAssignableFrom(...))) so both cases use the same return; keep the
existing else branch that falls back to
AbiTypes.getTypeAString(getComponentType()) and ensure you reference
value.get(0).getTypeAsString() and getComponentType() exactly as in the current
code.
abi/src/test/java/org/tron/trident/abi/datatypes/DynamicArrayTest.java (1)

38-50: Suppress raw type warnings or use proper generics.

Line 40-41 uses raw DynamicArray type which will generate unchecked/raw type compiler warnings.

♻️ Proposed fix to add suppression
   `@Test`
+  `@SuppressWarnings`("unchecked")
   public void testMultidimensionalDynamicArray() {
-    DynamicArray<DynamicArray> array =
+    DynamicArray<DynamicArray<DynamicArray<Uint256>>> array =
         new DynamicArray<>(
-            DynamicArray.class,
+            (Class<DynamicArray<DynamicArray<Uint256>>>) (Class<?>) DynamicArray.class,

Or simply add @SuppressWarnings("rawtypes") to the test method.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@abi/src/test/java/org/tron/trident/abi/datatypes/DynamicArrayTest.java`
around lines 38 - 50, The test method testMultidimensionalDynamicArray uses raw
DynamicArray types causing unchecked/raw-type warnings; fix by using proper
generics (e.g., parameterize each level as DynamicArray<DynamicArray<Uint256>>
and ultimately DynamicArray<DynamicArray<DynamicArray<Uint256>>> when
constructing the nested arrays) so all DynamicArray instances are fully generic,
or if you prefer to keep the current shape, add `@SuppressWarnings`("rawtypes") to
the test method declaration to silence the warning; update the references to
DynamicArray and Uint256 in the method accordingly.
core/src/test/java/org/tron/trident/core/AbiV2ContractTest.java (3)

492-493: Unchecked cast warning for generic DynamicArray.

The cast (DynamicArray<Uint256>) scores is unchecked. While this is common with generic types in Java, consider adding @SuppressWarnings("unchecked") to the constructor if this generates compiler warnings.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/src/test/java/org/tron/trident/core/AbiV2ContractTest.java` around lines
492 - 493, The cast to (DynamicArray<Uint256>) in AbiV2ContractTest (the
assignment to this.scores) produces an unchecked cast warning; suppress it by
annotating the enclosing constructor (or the smallest enclosing method) with
`@SuppressWarnings`("unchecked") so the compiler warning is silenced, keeping the
cast as-is and limiting the annotation scope to that constructor/method.

618-619: Remove debug print statements.

These System.out.println statements appear to be debug artifacts and should be removed for cleaner test output.

♻️ Suggested fix
     assertEquals(1, result.getConstantResultCount());
-    System.out.println(FunctionEncoder.encode(function));
-    System.out.println(ByteArray.toHexString(result.getConstantResult(0).toByteArray()));

     List<Type> outputStructs
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/src/test/java/org/tron/trident/core/AbiV2ContractTest.java` around lines
618 - 619, Remove the two debug print statements in the test: delete the
System.out.println(FunctionEncoder.encode(function)) and
System.out.println(ByteArray.toHexString(result.getConstantResult(0).toByteArray()))
lines in AbiV2ContractTest (they are only printing
FunctionEncoder.encode(function) and
ByteArray.toHexString(result.getConstantResult(0).toByteArray())). Leave the
rest of the test intact so assertions/assert logging remain; run tests to
confirm no behavioral changes.

677-681: Fragile manual offset prepending for nested struct decoding.

The manual prepending of "0000...0020" to simulate an offset pointer is fragile and may hide issues with the actual decoder. Consider investigating why the decoder requires this workaround or documenting this as expected behavior for the specific contract function output format.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/src/test/java/org/tron/trident/core/AbiV2ContractTest.java` around lines
677 - 681, The test currently fakes a 32-byte offset by string-prepending
("0000...0020") to inputRaw before calling FunctionReturnDecoder.decode, which
is fragile; instead construct the correct ABI-encoded payload by
programmatically computing a 32-byte head containing the offset (0x20) and
concatenating it with the actual bytes from result.getConstantResult(0) (use
ByteArray.toHexString/ByteArray.concat or equivalent) and pass that full byte
array into FunctionReturnDecoder.decode for the NestedInfo TypeReference, or if
the decoder should accept the raw constant result bytes, remove the manual
prepend and update the test to call FunctionReturnDecoder.decode directly with
result.getConstantResult(0) and document the expected behavior.
abi/src/main/java/org/tron/trident/abi/TypeEncoder.java (1)

149-175: Consider handling FixedPointType explicitly in removePadding.

At lines 151-153, Ufixed and Fixed return the full encoded value without padding removal, which is correct since fixed-point types aren't well-supported for packed encoding. However, if someone passes another FixedPointType subclass, it would fall through to the UnsupportedOperationException. This is fine given the current type hierarchy, but consider using FixedPointType in the check for future-proofing:

♻️ Suggested improvement
   static String removePadding(String encodedValue, Type parameter) {
     if (parameter instanceof NumericType) {
-      if (parameter instanceof Ufixed || parameter instanceof Fixed) {
+      if (parameter instanceof FixedPointType) {
         return encodedValue;
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@abi/src/main/java/org/tron/trident/abi/TypeEncoder.java` around lines 149 -
175, The removePadding method currently checks for Ufixed and Fixed specifically
and returns the full encodedValue for those, but other FixedPointType subclasses
would fall through to an exception; update the type check in removePadding to
use FixedPointType (or include it alongside Ufixed/Fixed) when deciding to
return encodedValue so all fixed-point subclasses are handled uniformly; locate
the removePadding method and replace the existing Ufixed/Fixed instanceof branch
with a FixedPointType check (or add instanceof FixedPointType) to future-proof
the logic.
abi/src/main/java/org/tron/trident/abi/FunctionReturnDecoder.java (1)

66-82: Consider using parameterized type for outputParameters list.

Lines 67 and 76 use raw ArrayList which triggers unchecked warnings. While functional, using the parameterized type improves type safety.

♻️ Suggested improvement
   public static byte[] decodeDynamicBytes(String rawInput) {
-    List outputParameters = new ArrayList<TypeReference<Type>>();
+    List<TypeReference<Type>> outputParameters = new ArrayList<>();
     outputParameters.add(new TypeReference<DynamicBytes>() {});
     ...
   }

   public static String decodeAddress(String rawInput) {
-    List outputParameters = new ArrayList<TypeReference<Type>>();
+    List<TypeReference<Type>> outputParameters = new ArrayList<>();
     outputParameters.add(new TypeReference<Address>() {});
     ...
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@abi/src/main/java/org/tron/trident/abi/FunctionReturnDecoder.java` around
lines 66 - 82, The outputParameters list in decodeDynamicBytes and decodeAddress
is declared with a raw ArrayList causing unchecked warnings; change its
declaration to use a parameterized type (e.g., List<TypeReference<Type>>
outputParameters = new ArrayList<>()) and ensure the added TypeReference
instances (new TypeReference<DynamicBytes>() {} and new TypeReference<Address>()
{}) match that generic so decoder.decodeFunctionResult(rawInput,
outputParameters) receives a properly typed List; update both methods
(decodeDynamicBytes and decodeAddress) accordingly.
abi/src/main/java/org/tron/trident/abi/Utils.java (3)

300-317: Minor: Field ordering in flattened list may not match declaration order.

The method collects canonical (non-StaticStruct) fields first, then appends nested fields. This may not preserve declaration order (e.g., for struct { int a; Nested n; int b; }, result would be [a, b, x] instead of [a, x, b]).

This appears acceptable since the method is used for counting total fields (for size calculations), not for ordered field access during encoding/decoding. If ordered iteration is ever needed, this would need revision.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@abi/src/main/java/org/tron/trident/abi/Utils.java` around lines 300 - 317,
The current staticStructsNestedFieldsFlatList method builds canonicalFields then
nestedFields separately which breaks original declaration order; change the
algorithm to iterate classType.getDeclaredFields() in declaration order and for
each Field if StaticStruct.class.isAssignableFrom(field.getType()) recursively
call staticStructsNestedFieldsFlatList((Class<Type>) field.getType()) and append
its returned list in-place, otherwise append the Field itself — this preserves
the original declaration order while keeping the same recursion and return type;
update references in the method to use a single List accumulator instead of
separate canonicalFields/nestedFields.

319-341: Avoid catching Throwable.

Line 335 catches Throwable which includes Error types (like OutOfMemoryError) that should generally propagate. Consider catching Exception instead if backward compatibility is truly needed.

♻️ Suggested fix
       }
-      } catch (Throwable e) {
+      } catch (Exception e) {
         /*FALLTHRU*/
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@abi/src/main/java/org/tron/trident/abi/Utils.java` around lines 319 - 341,
The getClassName method is catching Throwable (which can swallow Errors like
OutOfMemoryError); change the catch to a narrower type (e.g., catch Exception)
so only recoverable exceptions are handled; update the try/catch around the
array-handling block in getClassName (variables cl, dimensions, sb) to catch
Exception (or specific reflective exceptions) and let serious Errors propagate.

215-227: Potential ClassCastException if type structure doesn't match expectations.

The method assumes the type argument at index 0 is a ParameterizedType. If the type structure is different (e.g., raw type or unexpected nesting), a ClassCastException will occur at line 225.

Consider adding a type check or documenting the expected input constraints.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@abi/src/main/java/org/tron/trident/abi/Utils.java` around lines 215 - 227,
The getFullParameterizedTypeFromArray method currently casts nested type
arguments to ParameterizedType without checks, which can throw
ClassCastException for raw or unexpected type structures; update
getFullParameterizedTypeFromArray to validate each cast using instanceof before
casting (check that type is a ParameterizedType and that the inner typeArgument
is a ParameterizedType), handle raw/class cases (e.g., if inner typeArgument is
a Class, extract its name directly) and throw a clear IllegalArgumentException
or ClassNotFoundException with a descriptive message when the structure is
unsupported so callers of getFullParameterizedTypeFromArray receive a
deterministic error instead of a ClassCastException.
abi/src/main/java/org/tron/trident/abi/TypeDecoder.java (1)

858-1029: Complex but well-structured array element decoding.

The rewrite correctly handles:

  • Struct types (both static and dynamic) with proper offset tracking
  • Nested arrays (both static and dynamic) with recursive decoding
  • Primitive types with dynamic/static distinction

One observation: Lines 872-875 and 899-902 use identical Optional chains to check for innerTypes. Consider extracting this to a helper method for clarity.

♻️ Optional: Extract repeated Optional chain to helper
private static boolean hasSubTypeInnerTypes(TypeReference<?> typeReference) {
    return Optional.ofNullable(typeReference)
        .map(TypeReference::getSubTypeReference)
        .map(TypeReference::getInnerTypes)
        .isPresent();
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@abi/src/main/java/org/tron/trident/abi/TypeDecoder.java` around lines 858 -
1029, The repeated Optional.ofNullable(typeReference).map(x ->
x.getSubTypeReference()).map(x -> x.getInnerTypes()).isPresent() logic inside
TypeDecoder.decodeArrayElements (used in the DynamicStruct branch and the
StaticStruct branch) should be extracted into a helper like
hasSubTypeInnerTypes(TypeReference<?>), then replace both identical chains with
a call to that helper to improve readability and avoid duplication; update
references inside decodeArrayElements to call
hasSubTypeInnerTypes(typeReference) where the checks occur.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@abi/src/main/java/org/tron/trident/abi/CustomErrorEncoder.java`:
- Around line 37-41: The calculateSignatureHash method uses platform-default
encoding via errorSignature.getBytes(); update
CustomErrorEncoder.calculateSignatureHash to explicitly encode the
errorSignature as UTF-8 before hashing (use a UTF-8 byte conversion), then pass
that byte[] to Hash.sha3 and return the hex substring as before; reference
calculateSignatureHash, Hash.sha3, and Numeric.toHexString when locating the
change.

In `@abi/src/main/java/org/tron/trident/abi/datatypes/AbiTypes.java`:
- Around line 370-377: Replace the unchecked Class.forName(type) in the AbiTypes
fallback with a non-initializing lookup and subclass validation: call
Class.forName(type, false,
Thread.currentThread().getContextClassLoader()).asSubclass(Type.class) and
return that result; update the exception handling to catch
ClassNotFoundException and ClassCastException (or IllegalArgumentException) and
rethrow the same UnsupportedOperationException("Unsupported type encountered: "
+ type) so only classes that actually implement Type are accepted.

In `@abi/src/main/java/org/tron/trident/abi/datatypes/Array.java`:
- Around line 74-79: The getNativeValueCopy() method in Array<T> currently
unwraps only the outer Type and is therefore shallow; update it to deep-unwrap
nested ABI composites by detecting composite element types and calling their
native-unwrapping methods instead of t.getValue() directly: for each element t
in value, if t is an Array (e.g. instanceof Array) call
((Array<?>)t).getNativeValueCopy(), if t is a struct/composite call that
composite's native-unwrap method (e.g. Struct.getNativeValueCopy() or
equivalent), otherwise fall back to t.getValue(); ensure you reference the Array
class and the getNativeValueCopy() method in your changes and add any needed
imports.

In `@abi/src/main/java/org/tron/trident/abi/datatypes/DynamicArray.java`:
- Around line 38-45: The DynamicArray(List<T> values) constructor currently
dereferences values.get(0) which throws IndexOutOfBoundsException for an empty
list; update the constructor (DynamicArray(List<T> values)) to validate values
is non-empty (e.g., if (values == null || values.isEmpty()) throw new
IllegalArgumentException("values must not be empty")) before using values.get(0)
so callers get a clear, documented error instead of an OOB exception.
- Around line 27-34: The DynamicArray(T... values) constructor dereferences
values[0] which will throw ArrayIndexOutOfBoundsException for empty varargs; add
a guard at the start of the constructor that checks if values == null ||
values.length == 0 and handle it explicitly (e.g., throw a clear
IllegalArgumentException stating empty varargs are not allowed or delegate to an
existing constructor that accepts an explicit element Class), then proceed to
compute the element class as currently done using values[0] and
AbiTypes.getType(values[0].getTypeAsString()).

In `@abi/src/main/java/org/tron/trident/abi/datatypes/StaticArray.java`:
- Around line 87-95: The current logic builds the element type from the first
element in value or falls back to AbiTypes.getTypeAString(getComponentType())
when value is empty, which yields incorrect ABI strings for empty composite
arrays; change the fallback to inspect getComponentType(): if getComponentType()
is a StructType use its getTypeAsString(), if it's an Array use its
getTypeAsString(), otherwise use AbiTypes.getTypeAString(getComponentType());
update the branch in StaticArray (the block referencing value,
getComponentType(), StructType, Array, and AbiTypes.getTypeAString) so
zero-length composite arrays produce correct tuple or nested-array type syntax
before appending "[" + value.size() + "]".
- Around line 51-56: The constructor currently calls values.get(0) unguarded,
which throws for empty lists; fix by computing the element class before calling
super with a guard: if values.isEmpty() then derive the element class via
AbiTypes.getType(...) for the element type (fallback path) otherwise use the
existing logic that checks
StructType.class.isAssignableFrom(values.get(0).getClass()) ||
Array.class.isAssignableFrom(values.get(0).getClass()) ? (Class<T>)
values.get(0).getClass() : (Class<T>)
AbiTypes.getType(values.get(0).getTypeAsString()); then pass that computed
Class<T> and values into super instead of directly calling values.get(0) in the
super call.

In `@abi/src/main/java/org/tron/trident/abi/TypeReference.java`:
- Around line 76-80: The getInnerTypes() method can return null because the
field innerTypes is only set by the constructor that accepts inner types; update
the class so callers don't have to handle null by either initializing innerTypes
to an empty List by default (e.g., new ArrayList<>() in the field declaration or
in all constructors) or annotate and document the nullable contract (add
`@Nullable` to getInnerTypes and Javadoc explaining null return). Locate the
innerTypes field and all constructors (including the one that currently sets
innerTypes) and ensure every constructor initializes innerTypes or add the
`@Nullable` annotation and Javadoc to getInnerTypes to make the contract explicit.

In `@abi/src/main/java/org/tron/trident/abi/Utils.java`:
- Around line 71-78: Remove the unnecessary NoSuchMethodError handling in
Utils.getTypeName: simplify the method to directly return type.getTypeName() and
delete the try/catch and fallback to getClassName((Class) type), since the
project targets Java 8+ where java.lang.reflect.Type.getTypeName() is
guaranteed; update the method signature body for
getTypeName(java.lang.reflect.Type type) accordingly and remove any unused
imports if they become redundant.

In `@abi/src/test/java/org/tron/trident/abi/datatypes/CustomErrorTest.java`:
- Around line 23-26: The test currently only iterates and compares a prefix of
event.getParameters(), so add an explicit size equality assertion (compare
parameters.size() with event.getParameters().size() or assertEquals on those)
before the loop in CustomErrorTest so the full parameter list is validated; keep
the existing iterator/loop (using expectedParameter and event.getParameters())
but fail fast if lengths differ.

---

Nitpick comments:
In `@abi/src/main/java/org/tron/trident/abi/datatypes/DynamicArray.java`:
- Around line 84-90: In DynamicArray, consolidate the duplicate branch that
returns value.get(0).getTypeAsString() by combining the StructType and Array
checks into a single condition (e.g., if (StructType.class.isAssignableFrom(...)
|| Array.class.isAssignableFrom(...))) so both cases use the same return; keep
the existing else branch that falls back to
AbiTypes.getTypeAString(getComponentType()) and ensure you reference
value.get(0).getTypeAsString() and getComponentType() exactly as in the current
code.

In `@abi/src/main/java/org/tron/trident/abi/datatypes/DynamicStruct.java`:
- Around line 55-62: The three-branch conditional in DynamicStruct (checking
StructType.class, DynamicArray.class, and Array.class) is redundant because
DynamicArray is an Array; simplify by first checking
Array.class.isAssignableFrom(cls) (which will cover DynamicArray and Array) and
calling type.append(getValue().get(i).getTypeAsString()), else fall back to
type.append(AbiTypes.getTypeAString(cls)); update the conditional around cls,
getValue().get(i).getTypeAsString(), and AbiTypes.getTypeAString to remove the
duplicated branch.

In `@abi/src/main/java/org/tron/trident/abi/datatypes/primitive/Int.java`:
- Line 19: Replace the fully-qualified type java.lang.Integer with the simple
name Integer in the generic declaration of the Int class (the declaration
"public final class Int extends Number<java.lang.Integer>"). Update the
declaration to use Integer so it follows Java conventions and removes
unnecessary verbosity while keeping the same semantics.

In `@abi/src/main/java/org/tron/trident/abi/FunctionReturnDecoder.java`:
- Around line 66-82: The outputParameters list in decodeDynamicBytes and
decodeAddress is declared with a raw ArrayList causing unchecked warnings;
change its declaration to use a parameterized type (e.g.,
List<TypeReference<Type>> outputParameters = new ArrayList<>()) and ensure the
added TypeReference instances (new TypeReference<DynamicBytes>() {} and new
TypeReference<Address>() {}) match that generic so
decoder.decodeFunctionResult(rawInput, outputParameters) receives a properly
typed List; update both methods (decodeDynamicBytes and decodeAddress)
accordingly.

In `@abi/src/main/java/org/tron/trident/abi/TypeDecoder.java`:
- Around line 858-1029: The repeated Optional.ofNullable(typeReference).map(x ->
x.getSubTypeReference()).map(x -> x.getInnerTypes()).isPresent() logic inside
TypeDecoder.decodeArrayElements (used in the DynamicStruct branch and the
StaticStruct branch) should be extracted into a helper like
hasSubTypeInnerTypes(TypeReference<?>), then replace both identical chains with
a call to that helper to improve readability and avoid duplication; update
references inside decodeArrayElements to call
hasSubTypeInnerTypes(typeReference) where the checks occur.

In `@abi/src/main/java/org/tron/trident/abi/TypeEncoder.java`:
- Around line 149-175: The removePadding method currently checks for Ufixed and
Fixed specifically and returns the full encodedValue for those, but other
FixedPointType subclasses would fall through to an exception; update the type
check in removePadding to use FixedPointType (or include it alongside
Ufixed/Fixed) when deciding to return encodedValue so all fixed-point subclasses
are handled uniformly; locate the removePadding method and replace the existing
Ufixed/Fixed instanceof branch with a FixedPointType check (or add instanceof
FixedPointType) to future-proof the logic.

In `@abi/src/main/java/org/tron/trident/abi/Utils.java`:
- Around line 300-317: The current staticStructsNestedFieldsFlatList method
builds canonicalFields then nestedFields separately which breaks original
declaration order; change the algorithm to iterate classType.getDeclaredFields()
in declaration order and for each Field if
StaticStruct.class.isAssignableFrom(field.getType()) recursively call
staticStructsNestedFieldsFlatList((Class<Type>) field.getType()) and append its
returned list in-place, otherwise append the Field itself — this preserves the
original declaration order while keeping the same recursion and return type;
update references in the method to use a single List accumulator instead of
separate canonicalFields/nestedFields.
- Around line 319-341: The getClassName method is catching Throwable (which can
swallow Errors like OutOfMemoryError); change the catch to a narrower type
(e.g., catch Exception) so only recoverable exceptions are handled; update the
try/catch around the array-handling block in getClassName (variables cl,
dimensions, sb) to catch Exception (or specific reflective exceptions) and let
serious Errors propagate.
- Around line 215-227: The getFullParameterizedTypeFromArray method currently
casts nested type arguments to ParameterizedType without checks, which can throw
ClassCastException for raw or unexpected type structures; update
getFullParameterizedTypeFromArray to validate each cast using instanceof before
casting (check that type is a ParameterizedType and that the inner typeArgument
is a ParameterizedType), handle raw/class cases (e.g., if inner typeArgument is
a Class, extract its name directly) and throw a clear IllegalArgumentException
or ClassNotFoundException with a descriptive message when the structure is
unsupported so callers of getFullParameterizedTypeFromArray receive a
deterministic error instead of a ClassCastException.

In `@abi/src/test/java/org/tron/trident/abi/CustomErrorEncoderTest.java`:
- Around line 56-77: Two test methods, testBuildErrorSignatureWithDynamicStructs
and testBuildErrorSignatureWithDynamicArrays, use package-private visibility
while the rest of the tests are public; update their method signatures in
CustomErrorEncoderTest (the two methods named exactly) to be public void so
visibility is consistent with other tests and the test framework picks them up
consistently.

In `@abi/src/test/java/org/tron/trident/abi/datatypes/DynamicArrayTest.java`:
- Around line 38-50: The test method testMultidimensionalDynamicArray uses raw
DynamicArray types causing unchecked/raw-type warnings; fix by using proper
generics (e.g., parameterize each level as DynamicArray<DynamicArray<Uint256>>
and ultimately DynamicArray<DynamicArray<DynamicArray<Uint256>>> when
constructing the nested arrays) so all DynamicArray instances are fully generic,
or if you prefer to keep the current shape, add `@SuppressWarnings`("rawtypes") to
the test method declaration to silence the warning; update the references to
DynamicArray and Uint256 in the method accordingly.

In `@abi/src/test/java/org/tron/trident/abi/datatypes/StaticStructTest.java`:
- Around line 23-31: Replace the dynamically-built `expected` string in
StaticStructTest with a hardcoded ABI literal so the test no longer depends on
component `getTypeAsString()` implementations: instead of concatenating
address1.getTypeAsString(), address2.getTypeAsString(), and
array4.getTypeAsString(), set `expected` to the explicit ABI string
"(address,address,uint256[4])" (refer to the `expected` variable in
StaticStructTest and the Address/StaticArray4 component types to locate the
code).

In `@abi/src/test/java/org/tron/trident/abi/FunctionReturnDecoderTest.java`:
- Around line 1666-1707: The test method testDecodeMultiDimDynamicArrayFunction
in FunctionReturnDecoderTest is commented out; either remove it or add a
TODO/ticket reference explaining why it's disabled and when to re-enable
it—update the file by deleting the commented block if obsolete, or replace the
comment with a short "// TODO: <tracking-issue-id> - reason and re-enable
criteria" referencing the related fixture
AbiV2TestFixture.setGetMultiDimDynamicArrayFunction and the decoding logic in
FunctionReturnDecoder so future readers know the intent.

In `@core/src/test/java/org/tron/trident/core/AbiV2ContractTest.java`:
- Around line 492-493: The cast to (DynamicArray<Uint256>) in AbiV2ContractTest
(the assignment to this.scores) produces an unchecked cast warning; suppress it
by annotating the enclosing constructor (or the smallest enclosing method) with
`@SuppressWarnings`("unchecked") so the compiler warning is silenced, keeping the
cast as-is and limiting the annotation scope to that constructor/method.
- Around line 618-619: Remove the two debug print statements in the test: delete
the System.out.println(FunctionEncoder.encode(function)) and
System.out.println(ByteArray.toHexString(result.getConstantResult(0).toByteArray()))
lines in AbiV2ContractTest (they are only printing
FunctionEncoder.encode(function) and
ByteArray.toHexString(result.getConstantResult(0).toByteArray())). Leave the
rest of the test intact so assertions/assert logging remain; run tests to
confirm no behavioral changes.
- Around line 677-681: The test currently fakes a 32-byte offset by
string-prepending ("0000...0020") to inputRaw before calling
FunctionReturnDecoder.decode, which is fragile; instead construct the correct
ABI-encoded payload by programmatically computing a 32-byte head containing the
offset (0x20) and concatenating it with the actual bytes from
result.getConstantResult(0) (use ByteArray.toHexString/ByteArray.concat or
equivalent) and pass that full byte array into FunctionReturnDecoder.decode for
the NestedInfo TypeReference, or if the decoder should accept the raw constant
result bytes, remove the manual prepend and update the test to call
FunctionReturnDecoder.decode directly with result.getConstantResult(0) and
document the expected behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6d93dd71-06a7-4e0f-847b-57aae36dea1f

📥 Commits

Reviewing files that changed from the base of the PR and between 62dc495 and 4b6b23a.

⛔ Files ignored due to path filters (1)
  • abi/src/main/java/org/tron/trident/abi/datatypes/generated/StaticArray0.java is excluded by !**/generated/**
📒 Files selected for processing (36)
  • abi/src/main/java/org/tron/trident/abi/CustomErrorEncoder.java
  • abi/src/main/java/org/tron/trident/abi/DefaultFunctionEncoder.java
  • abi/src/main/java/org/tron/trident/abi/DefaultFunctionReturnDecoder.java
  • abi/src/main/java/org/tron/trident/abi/EventEncoder.java
  • abi/src/main/java/org/tron/trident/abi/FunctionEncoder.java
  • abi/src/main/java/org/tron/trident/abi/FunctionReturnDecoder.java
  • abi/src/main/java/org/tron/trident/abi/TypeDecoder.java
  • abi/src/main/java/org/tron/trident/abi/TypeEncoder.java
  • abi/src/main/java/org/tron/trident/abi/TypeReference.java
  • abi/src/main/java/org/tron/trident/abi/Utils.java
  • abi/src/main/java/org/tron/trident/abi/datatypes/AbiTypes.java
  • abi/src/main/java/org/tron/trident/abi/datatypes/Array.java
  • abi/src/main/java/org/tron/trident/abi/datatypes/BytesType.java
  • abi/src/main/java/org/tron/trident/abi/datatypes/CustomError.java
  • abi/src/main/java/org/tron/trident/abi/datatypes/DynamicArray.java
  • abi/src/main/java/org/tron/trident/abi/datatypes/DynamicStruct.java
  • abi/src/main/java/org/tron/trident/abi/datatypes/Event.java
  • abi/src/main/java/org/tron/trident/abi/datatypes/Function.java
  • abi/src/main/java/org/tron/trident/abi/datatypes/StaticArray.java
  • abi/src/main/java/org/tron/trident/abi/datatypes/StaticStruct.java
  • abi/src/main/java/org/tron/trident/abi/datatypes/Utf8String.java
  • abi/src/main/java/org/tron/trident/abi/datatypes/primitive/Int.java
  • abi/src/main/java/org/tron/trident/abi/datatypes/reflection/Parameterized.java
  • abi/src/test/java/org/tron/trident/abi/AbiV2TestFixture.java
  • abi/src/test/java/org/tron/trident/abi/CustomErrorEncoderTest.java
  • abi/src/test/java/org/tron/trident/abi/DefaultFunctionEncoderTest.java
  • abi/src/test/java/org/tron/trident/abi/EventEncoderTest.java
  • abi/src/test/java/org/tron/trident/abi/FunctionReturnDecoderTest.java
  • abi/src/test/java/org/tron/trident/abi/TypeDecoderTest.java
  • abi/src/test/java/org/tron/trident/abi/TypeEncoderPackedTest.java
  • abi/src/test/java/org/tron/trident/abi/TypeEncoderTest.java
  • abi/src/test/java/org/tron/trident/abi/datatypes/CustomErrorTest.java
  • abi/src/test/java/org/tron/trident/abi/datatypes/DynamicArrayTest.java
  • abi/src/test/java/org/tron/trident/abi/datatypes/StaticArrayTest.java
  • abi/src/test/java/org/tron/trident/abi/datatypes/StaticStructTest.java
  • core/src/test/java/org/tron/trident/core/AbiV2ContractTest.java

Comment on lines +37 to +41
public static String calculateSignatureHash(String errorSignature) {
byte[] input = errorSignature.getBytes();
byte[] hash = Hash.sha3(input);
return Numeric.toHexString(hash).substring(2);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Specify UTF-8 charset explicitly for consistent hashing.

errorSignature.getBytes() uses the platform's default charset, which may vary across systems and could produce different hashes. For ABI signature hashing, UTF-8 should be used explicitly to ensure consistent behavior.

🔧 Proposed fix
+import java.nio.charset.StandardCharsets;
+
 public static String calculateSignatureHash(String errorSignature) {
-  byte[] input = errorSignature.getBytes();
+  byte[] input = errorSignature.getBytes(StandardCharsets.UTF_8);
   byte[] hash = Hash.sha3(input);
   return Numeric.toHexString(hash).substring(2);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public static String calculateSignatureHash(String errorSignature) {
byte[] input = errorSignature.getBytes();
byte[] hash = Hash.sha3(input);
return Numeric.toHexString(hash).substring(2);
}
import java.nio.charset.StandardCharsets;
public static String calculateSignatureHash(String errorSignature) {
byte[] input = errorSignature.getBytes(StandardCharsets.UTF_8);
byte[] hash = Hash.sha3(input);
return Numeric.toHexString(hash).substring(2);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@abi/src/main/java/org/tron/trident/abi/CustomErrorEncoder.java` around lines
37 - 41, The calculateSignatureHash method uses platform-default encoding via
errorSignature.getBytes(); update CustomErrorEncoder.calculateSignatureHash to
explicitly encode the errorSignature as UTF-8 before hashing (use a UTF-8 byte
conversion), then pass that byte[] to Hash.sha3 and return the hex substring as
before; reference calculateSignatureHash, Hash.sha3, and Numeric.toHexString
when locating the change.

Comment on lines +370 to +377
default: {
try {
return (Class<? extends Type>) Class.forName(type);
} catch (ClassNotFoundException e) {
throw new UnsupportedOperationException(
"Unsupported type encountered: " + type);
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n abi/src/main/java/org/tron/trident/abi/datatypes/AbiTypes.java | sed -n '350,390p'

Repository: 0xbigapple/trident

Length of output: 1647


🏁 Script executed:

cat -n abi/src/main/java/org/tron/trident/abi/datatypes/AbiTypes.java | sed -n '320,380p'

Repository: 0xbigapple/trident

Length of output: 2066


🏁 Script executed:

cat -n abi/src/main/java/org/tron/trident/abi/datatypes/AbiTypes.java | sed -n '280,320p'

Repository: 0xbigapple/trident

Length of output: 1381


🏁 Script executed:

cat -n abi/src/main/java/org/tron/trident/abi/datatypes/AbiTypes.java | sed -n '160,220p'

Repository: 0xbigapple/trident

Length of output: 2373


🏁 Script executed:

cat -n abi/src/main/java/org/tron/trident/abi/datatypes/AbiTypes.java | sed -n '140,165p'

Repository: 0xbigapple/trident

Length of output: 968


🏁 Script executed:

find . -name "Type.java" -o -name "Type.class" | head -20

Repository: 0xbigapple/trident

Length of output: 122


🏁 Script executed:

cat -n abi/src/main/java/org/tron/trident/abi/datatypes/Type.java

Repository: 0xbigapple/trident

Length of output: 1146


🏁 Script executed:

rg -A 5 -B 5 "getType\(" abi/src/main/java/org/tron/trident/abi/datatypes/AbiTypes.java | head -60

Repository: 0xbigapple/trident

Length of output: 798


🏁 Script executed:

rg "getType\(" --type java -B 2 -A 2 | grep -v "test" | head -100

Repository: 0xbigapple/trident

Length of output: 7065


🏁 Script executed:

rg "AbiTypes\.getType" --type java -B 3 -A 3 | head -150

Repository: 0xbigapple/trident

Length of output: 8546


🏁 Script executed:

rg -B 10 "getType\(" abi/src/main/java/org/tron/trident/abi/TypeReference.java | grep -A 10 "getAtomicTypeClass"

Repository: 0xbigapple/trident

Length of output: 780


🏁 Script executed:

cat -n abi/src/main/java/org/tron/trident/abi/TypeDecoder.java | sed -n '1,50p'

Repository: 0xbigapple/trident

Length of output: 2582


🏁 Script executed:

rg -A 8 "BiFunction.*AbiTypes.getType" abi/src/main/java/org/tron/trident/abi/TypeDecoder.java

Repository: 0xbigapple/trident

Length of output: 44


🏁 Script executed:

rg "AbiTypes.getType" abi/src/main/java/org/tron/trident/abi/TypeDecoder.java -B 2 -A 8

Repository: 0xbigapple/trident

Length of output: 475


🏁 Script executed:

rg -B 5 -A 10 "new DynamicArray" abi/src/main/java/org/tron/trident/abi/ --type java

Repository: 0xbigapple/trident

Length of output: 2952


🏁 Script executed:

rg "getType\(" --type java abi/src/main/java/org/tron/trident/abi/ -B 1 -A 1 | grep -E "(getType|solidityType|typeName)" | head -40

Repository: 0xbigapple/trident

Length of output: 4029


Add type validation to the Class.forName fallback.

Line 372 will return any class name without checking that it implements Type. Use asSubclass(Type.class) to validate the resolved class, and switch to the non-initializing Class.forName overload so invalid types fail immediately rather than causing issues downstream.

Proposed fix
       default: {
         try {
-          return (Class<? extends Type>) Class.forName(type);
-        } catch (ClassNotFoundException e) {
+          return Class.forName(type, false, AbiTypes.class.getClassLoader())
+              .asSubclass(Type.class);
+        } catch (ClassNotFoundException | ClassCastException e) {
           throw new UnsupportedOperationException(
                   "Unsupported type encountered: " + type);
         }
       }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
default: {
try {
return (Class<? extends Type>) Class.forName(type);
} catch (ClassNotFoundException e) {
throw new UnsupportedOperationException(
"Unsupported type encountered: " + type);
}
}
default: {
try {
return Class.forName(type, false, AbiTypes.class.getClassLoader())
.asSubclass(Type.class);
} catch (ClassNotFoundException | ClassCastException e) {
throw new UnsupportedOperationException(
"Unsupported type encountered: " + type);
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@abi/src/main/java/org/tron/trident/abi/datatypes/AbiTypes.java` around lines
370 - 377, Replace the unchecked Class.forName(type) in the AbiTypes fallback
with a non-initializing lookup and subclass validation: call Class.forName(type,
false, Thread.currentThread().getContextClassLoader()).asSubclass(Type.class)
and return that result; update the exception handling to catch
ClassNotFoundException and ClassCastException (or IllegalArgumentException) and
rethrow the same UnsupportedOperationException("Unsupported type encountered: "
+ type) so only classes that actually implement Type are accepted.

Comment on lines +74 to +79
public List<Object> getNativeValueCopy() {
List<Object> copy = new ArrayList<>(value.size());
for (T t : value) {
copy.add(t.getValue());
}
return Collections.unmodifiableList(copy);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

getNativeValueCopy() is still shallow for ABI v2 composites.

Line 77 unwraps only the outer Type. For nested arrays/structs, this returns List<Type> again, so callers still receive wrapped ABI objects from a method that promises native values. A StaticArray<StaticArray<Uint256>> would come back as List<List<Uint256>>, not native numbers.

🔁 Proposed fix
   public List<Object> getNativeValueCopy() {
     List<Object> copy = new ArrayList<>(value.size());
     for (T t : value) {
-      copy.add(t.getValue());
+      copy.add(t instanceof Array ? ((Array<?>) t).getNativeValueCopy() : t.getValue());
     }
     return Collections.unmodifiableList(copy);
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public List<Object> getNativeValueCopy() {
List<Object> copy = new ArrayList<>(value.size());
for (T t : value) {
copy.add(t.getValue());
}
return Collections.unmodifiableList(copy);
public List<Object> getNativeValueCopy() {
List<Object> copy = new ArrayList<>(value.size());
for (T t : value) {
copy.add(t instanceof Array ? ((Array<?>) t).getNativeValueCopy() : t.getValue());
}
return Collections.unmodifiableList(copy);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@abi/src/main/java/org/tron/trident/abi/datatypes/Array.java` around lines 74
- 79, The getNativeValueCopy() method in Array<T> currently unwraps only the
outer Type and is therefore shallow; update it to deep-unwrap nested ABI
composites by detecting composite element types and calling their
native-unwrapping methods instead of t.getValue() directly: for each element t
in value, if t is an Array (e.g. instanceof Array) call
((Array<?>)t).getNativeValueCopy(), if t is a struct/composite call that
composite's native-unwrap method (e.g. Struct.getNativeValueCopy() or
equivalent), otherwise fall back to t.getValue(); ensure you reference the Array
class and the getNativeValueCopy() method in your changes and add any needed
imports.

Comment on lines 27 to 34
public DynamicArray(T... values) {
super((Class<T>) AbiTypes.getType(values[0].getTypeAsString()), values);
super(
StructType.class.isAssignableFrom(values[0].getClass())
|| Array.class.isAssignableFrom(values[0].getClass())
? (Class<T>) values[0].getClass()
: (Class<T>) AbiTypes.getType(values[0].getTypeAsString()),
values);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Potential ArrayIndexOutOfBoundsException when called with empty array.

The constructor accesses values[0] without checking if the array is empty. If called with zero arguments, this will throw ArrayIndexOutOfBoundsException. Consider adding a guard or documenting that empty arrays are not supported by this constructor variant.

🛡️ Proposed fix to add empty check
 `@SafeVarargs`
 `@SuppressWarnings`({"unchecked"})
 public DynamicArray(T... values) {
+  if (values.length == 0) {
+    throw new IllegalArgumentException("Empty arrays not supported; use DynamicArray(Class<T>, T...) instead");
+  }
   super(
           StructType.class.isAssignableFrom(values[0].getClass())
               || Array.class.isAssignableFrom(values[0].getClass())
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public DynamicArray(T... values) {
super((Class<T>) AbiTypes.getType(values[0].getTypeAsString()), values);
super(
StructType.class.isAssignableFrom(values[0].getClass())
|| Array.class.isAssignableFrom(values[0].getClass())
? (Class<T>) values[0].getClass()
: (Class<T>) AbiTypes.getType(values[0].getTypeAsString()),
values);
}
public DynamicArray(T... values) {
if (values.length == 0) {
throw new IllegalArgumentException("Empty arrays not supported; use DynamicArray(Class<T>, T...) instead");
}
super(
StructType.class.isAssignableFrom(values[0].getClass())
|| Array.class.isAssignableFrom(values[0].getClass())
? (Class<T>) values[0].getClass()
: (Class<T>) AbiTypes.getType(values[0].getTypeAsString()),
values);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@abi/src/main/java/org/tron/trident/abi/datatypes/DynamicArray.java` around
lines 27 - 34, The DynamicArray(T... values) constructor dereferences values[0]
which will throw ArrayIndexOutOfBoundsException for empty varargs; add a guard
at the start of the constructor that checks if values == null || values.length
== 0 and handle it explicitly (e.g., throw a clear IllegalArgumentException
stating empty varargs are not allowed or delegate to an existing constructor
that accepts an explicit element Class), then proceed to compute the element
class as currently done using values[0] and
AbiTypes.getType(values[0].getTypeAsString()).

Comment on lines 38 to 45
public DynamicArray(List<T> values) {
super((Class<T>) AbiTypes.getType(values.get(0).getTypeAsString()), values);
super(
StructType.class.isAssignableFrom(values.get(0).getClass())
|| Array.class.isAssignableFrom(values.get(0).getClass())
? (Class<T>) values.get(0).getClass()
: (Class<T>) AbiTypes.getType(values.get(0).getTypeAsString()),
values);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Potential IndexOutOfBoundsException when called with empty list.

Similar to the varargs constructor, this method accesses values.get(0) without verifying the list is non-empty. An empty list will cause IndexOutOfBoundsException.

🛡️ Proposed fix to add empty check
 `@SuppressWarnings`("unchecked")
 public DynamicArray(List<T> values) {
+  if (values.isEmpty()) {
+    throw new IllegalArgumentException("Empty lists not supported; use DynamicArray(Class<T>, List<T>) instead");
+  }
   super(
           StructType.class.isAssignableFrom(values.get(0).getClass())
               || Array.class.isAssignableFrom(values.get(0).getClass())
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@abi/src/main/java/org/tron/trident/abi/datatypes/DynamicArray.java` around
lines 38 - 45, The DynamicArray(List<T> values) constructor currently
dereferences values.get(0) which throws IndexOutOfBoundsException for an empty
list; update the constructor (DynamicArray(List<T> values)) to validate values
is non-empty (e.g., if (values == null || values.isEmpty()) throw new
IllegalArgumentException("values must not be empty")) before using values.get(0)
so callers get a clear, documented error instead of an OOB exception.

Comment on lines +51 to +56
super(
StructType.class.isAssignableFrom(values.get(0).getClass())
|| Array.class.isAssignableFrom(values.get(0).getClass())
? (Class<T>) values.get(0).getClass()
: (Class<T>) AbiTypes.getType(values.get(0).getTypeAsString()),
values);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Empty static arrays still crash in this constructor path.

Line 52 reads values.get(0) before any emptiness guard, so expectedSize == 0 with an empty list throws IndexOutOfBoundsException. That breaks the new zero-length-array support whenever this overload is used, or at least prevents it from failing with a clear error.

🛠️ Suggested guard
   `@Deprecated`
   `@SuppressWarnings`("unchecked")
   public StaticArray(int expectedSize, List<T> values) {
-    super(
-            StructType.class.isAssignableFrom(values.get(0).getClass())
-            || Array.class.isAssignableFrom(values.get(0).getClass())
-                    ? (Class<T>) values.get(0).getClass()
-                    : (Class<T>) AbiTypes.getType(values.get(0).getTypeAsString()),
-            values);
+    super(resolveType(values), values);
     checkValid(expectedSize);
   }
+
+  `@SuppressWarnings`("unchecked")
+  private static <T extends Type> Class<T> resolveType(List<T> values) {
+    if (values.isEmpty()) {
+      throw new UnsupportedOperationException(
+          "Use the typed StaticArray(Class<T>, ...) constructor for empty static arrays.");
+    }
+    return StructType.class.isAssignableFrom(values.get(0).getClass())
+            || Array.class.isAssignableFrom(values.get(0).getClass())
+        ? (Class<T>) values.get(0).getClass()
+        : (Class<T>) AbiTypes.getType(values.get(0).getTypeAsString());
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@abi/src/main/java/org/tron/trident/abi/datatypes/StaticArray.java` around
lines 51 - 56, The constructor currently calls values.get(0) unguarded, which
throws for empty lists; fix by computing the element class before calling super
with a guard: if values.isEmpty() then derive the element class via
AbiTypes.getType(...) for the element type (fallback path) otherwise use the
existing logic that checks
StructType.class.isAssignableFrom(values.get(0).getClass()) ||
Array.class.isAssignableFrom(values.get(0).getClass()) ? (Class<T>)
values.get(0).getClass() : (Class<T>)
AbiTypes.getType(values.get(0).getTypeAsString()); then pass that computed
Class<T> and values into super instead of directly calling values.get(0) in the
super call.

Comment on lines +87 to +95
String type;
if (!value.isEmpty() && StructType.class.isAssignableFrom(value.get(0).getClass())) {
type = value.get(0).getTypeAsString();
} else if (!value.isEmpty() && Array.class.isAssignableFrom(value.get(0).getClass())) {
type = value.get(0).getTypeAsString();
} else {
type = AbiTypes.getTypeAString(getComponentType());
}
return type + "[" + value.size() + "]";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Empty composite arrays still produce invalid ABI type strings.

When value is empty, Line 93 falls back to AbiTypes.getTypeAString(getComponentType()). For StaticArray0<MyStruct> or StaticArray0<StaticArray4<Uint256>>, that yields mystruct[0] / staticarray4[0] instead of tuple or nested-array syntax, so selectors and event signatures for zero-length composite arrays are wrong.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@abi/src/main/java/org/tron/trident/abi/datatypes/StaticArray.java` around
lines 87 - 95, The current logic builds the element type from the first element
in value or falls back to AbiTypes.getTypeAString(getComponentType()) when value
is empty, which yields incorrect ABI strings for empty composite arrays; change
the fallback to inspect getComponentType(): if getComponentType() is a
StructType use its getTypeAsString(), if it's an Array use its
getTypeAsString(), otherwise use AbiTypes.getTypeAString(getComponentType());
update the branch in StaticArray (the block referencing value,
getComponentType(), StructType, Array, and AbiTypes.getTypeAString) so
zero-length composite arrays produce correct tuple or nested-array type syntax
before appending "[" + value.size() + "]".

Comment on lines +76 to +80
public List<TypeReference<?>> getInnerTypes() {
return this.innerTypes;
}


Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

getInnerTypes() may return null; consider documenting or initializing.

The innerTypes field is only initialized when using the new constructor at line 51. When using other constructors, getInnerTypes() will return null. Callers need to handle this, or consider initializing to an empty list by default.

🛡️ Option: Initialize to empty list or document nullability
-  protected List<TypeReference<?>> innerTypes;
+  protected List<TypeReference<?>> innerTypes = Collections.emptyList();

Or add @Nullable annotation and document the contract.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@abi/src/main/java/org/tron/trident/abi/TypeReference.java` around lines 76 -
80, The getInnerTypes() method can return null because the field innerTypes is
only set by the constructor that accepts inner types; update the class so
callers don't have to handle null by either initializing innerTypes to an empty
List by default (e.g., new ArrayList<>() in the field declaration or in all
constructors) or annotate and document the nullable contract (add `@Nullable` to
getInnerTypes and Javadoc explaining null return). Locate the innerTypes field
and all constructors (including the one that currently sets innerTypes) and
ensure every constructor initializes innerTypes or add the `@Nullable` annotation
and Javadoc to getInnerTypes to make the contract explicit.

Comment on lines +71 to +78
/** Ports {@link java.lang.reflect.Type#getTypeName()}. */
public static String getTypeName(java.lang.reflect.Type type) {
try {
return type.getTypeName();
} catch (NoSuchMethodError e) {
return getClassName((Class) type);
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the Java version requirement in build configuration
fd -t f "build.gradle" --exec cat {} \; 2>/dev/null | grep -E "sourceCompatibility|targetCompatibility|JavaVersion"
fd -t f "pom.xml" --exec grep -A2 -E "<source>|<target>|<release>" {} \; 2>/dev/null

Repository: 0xbigapple/trident

Length of output: 167


Remove unnecessary NoSuchMethodError catch block.

The project targets Java 8 minimum (sourceCompatibility = JavaVersion.VERSION_1_8), so Type.getTypeName() is guaranteed to be available. The catch block and fallback to getClassName() will never execute and should be removed to simplify the code.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@abi/src/main/java/org/tron/trident/abi/Utils.java` around lines 71 - 78,
Remove the unnecessary NoSuchMethodError handling in Utils.getTypeName: simplify
the method to directly return type.getTypeName() and delete the try/catch and
fallback to getClassName((Class) type), since the project targets Java 8+ where
java.lang.reflect.Type.getTypeName() is guaranteed; update the method signature
body for getTypeName(java.lang.reflect.Type type) accordingly and remove any
unused imports if they become redundant.

Comment on lines +23 to +26
Iterator<TypeReference<?>> expectedParameter = parameters.iterator();
for (TypeReference<?> actualParameter : event.getParameters()) {
assertEquals(expectedParameter.next(), actualParameter);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

This test still passes if the implementation drops the last parameter.

Lines 23-26 only compare the prefix of event.getParameters(). If getParameters() returns one entry instead of two, the loop exits cleanly and the test stays green. Add a size assertion so the full parameter list is verified.

✅ Tighten the assertion
+    assertEquals(parameters.size(), event.getParameters().size());
     Iterator<TypeReference<?>> expectedParameter = parameters.iterator();
     for (TypeReference<?> actualParameter : event.getParameters()) {
       assertEquals(expectedParameter.next(), actualParameter);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@abi/src/test/java/org/tron/trident/abi/datatypes/CustomErrorTest.java` around
lines 23 - 26, The test currently only iterates and compares a prefix of
event.getParameters(), so add an explicit size equality assertion (compare
parameters.size() with event.getParameters().size() or assertEquals on those)
before the loop in CustomErrorTest so the full parameter list is validated; keep
the existing iterator/loop (using expectedParameter and event.getParameters())
but fail fast if lengths differ.

…odeWithOutPrefix/buildEventSignatureWithOutPrefix
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

18 issues found across 37 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="abi/src/main/java/org/tron/trident/abi/CustomErrorEncoder.java">

<violation number="1" location="abi/src/main/java/org/tron/trident/abi/CustomErrorEncoder.java:40">
P1: Custom error selectors should be the first 4 bytes of the hash, not the full 32-byte digest.</violation>
</file>

<file name="abi/src/main/java/org/tron/trident/abi/DefaultFunctionReturnDecoder.java">

<violation number="1" location="abi/src/main/java/org/tron/trident/abi/DefaultFunctionReturnDecoder.java:127">
P1: This offset update still treats `StaticArrayN<DynamicBytes>` and `StaticArrayN<DynamicArray<...>>` as inline arrays. After decoding one of those returns, the next output is read from the wrong tuple offset.</violation>

<violation number="2" location="abi/src/main/java/org/tron/trident/abi/DefaultFunctionReturnDecoder.java:188">
P1: This dynamic-array check is only one level deep. Nested static arrays like `uint256[][3][3]` are still treated as inline, so their function return decoding starts from the pointer word instead of the array payload.</violation>
</file>

<file name="abi/src/main/java/org/tron/trident/abi/datatypes/Array.java">

<violation number="1" location="abi/src/main/java/org/tron/trident/abi/datatypes/Array.java:77">
P2: `getNativeValueCopy()` only unwraps one level, so nested arrays/structs still contain ABI `Type` objects instead of native values.</violation>
</file>

<file name="abi/src/test/java/org/tron/trident/abi/TypeEncoderPackedTest.java">

<violation number="1" location="abi/src/test/java/org/tron/trident/abi/TypeEncoderPackedTest.java:828">
P1: These packed-array expectations keep 32-byte element padding, so they lock in non-packed output for sub-32-byte array elements.</violation>

<violation number="2" location="abi/src/test/java/org/tron/trident/abi/TypeEncoderPackedTest.java:850">
P2: These assertions lock in `encodePacked` rejecting ABI v2 struct arrays, which contradicts the feature this PR is adding.</violation>
</file>

<file name="abi/src/main/java/org/tron/trident/abi/Utils.java">

<violation number="1" location="abi/src/main/java/org/tron/trident/abi/Utils.java:92">
P1: Annotated static-array fields inside structs are emitted as dynamic arrays because this branch always formats them with `[]`.</violation>

<violation number="2" location="abi/src/main/java/org/tron/trident/abi/Utils.java:208">
P2: Nested array type names are reduced to the raw inner array class, so multidimensional `TypeReference`s produce non-canonical ABI names like `staticarray2[3]`.</violation>

<violation number="3" location="abi/src/main/java/org/tron/trident/abi/Utils.java:226">
P1: This helper breaks 3D dynamic-array decoding because `Class.forName` is given a parameterized type string, not a class name.</violation>
</file>

<file name="abi/src/main/java/org/tron/trident/abi/TypeDecoder.java">

<violation number="1" location="abi/src/main/java/org/tron/trident/abi/TypeDecoder.java:139">
P1: This overload bypasses the TypeReference-aware array/struct decoders, so array or tuple TypeReferences still throw at runtime.</violation>

<violation number="2" location="abi/src/main/java/org/tron/trident/abi/TypeDecoder.java:432">
P1: The new innerTypes tuple path still decodes fixed-array fields as plain classes, which throws instead of decoding the array member.</violation>
</file>

<file name="abi/src/test/java/org/tron/trident/abi/AbiV2TestFixture.java">

<violation number="1" location="abi/src/test/java/org/tron/trident/abi/AbiV2TestFixture.java:880">
P3: Specify the charset for these string-to-bytes conversions so the ABI fixture is deterministic across JVM default encodings.</violation>
</file>

<file name="abi/src/test/java/org/tron/trident/abi/datatypes/CustomErrorTest.java">

<violation number="1" location="abi/src/test/java/org/tron/trident/abi/datatypes/CustomErrorTest.java:24">
P2: This loop can pass even when `getParameters()` returns too few elements, because it never asserts that all expected parameters were visited.</violation>
</file>

<file name="abi/src/main/java/org/tron/trident/abi/datatypes/DynamicArray.java">

<violation number="1" location="abi/src/main/java/org/tron/trident/abi/datatypes/DynamicArray.java:81">
P1: Empty nested arrays still produce invalid ABI type strings on the zero-length path. `AbiTypes.getTypeAString(getComponentType())` returns raw Java class names for array subclasses, so encoding an empty nested array can generate `dynamicarray[]`/`staticarrayN[]` instead of a canonical ABI type.</violation>
</file>

<file name="abi/src/main/java/org/tron/trident/abi/datatypes/Utf8String.java">

<violation number="1" location="abi/src/main/java/org/tron/trident/abi/datatypes/Utf8String.java:36">
P2: Compute the padded length from the UTF-8 byte count; non-empty strings can span more than one 32-byte word.</violation>
</file>

<file name="abi/src/main/java/org/tron/trident/abi/datatypes/AbiTypes.java">

<violation number="1" location="abi/src/main/java/org/tron/trident/abi/datatypes/AbiTypes.java:372">
P2: Reject reflected classes that do not implement `Type`. Accepting any loadable class here turns invalid ABI type names into later runtime failures instead of the expected unsupported-type error.</violation>
</file>

<file name="abi/src/main/java/org/tron/trident/abi/datatypes/StaticArray.java">

<violation number="1" location="abi/src/main/java/org/tron/trident/abi/datatypes/StaticArray.java:54">
P1: Validate all nested array/struct elements before inferring the component type from `values.get(0)`. As written, heterogeneous nested arrays can be encoded under the first element’s ABI type and produce malformed calldata.</violation>
</file>

<file name="abi/src/main/java/org/tron/trident/abi/DefaultFunctionEncoder.java">

<violation number="1" location="abi/src/main/java/org/tron/trident/abi/DefaultFunctionEncoder.java:119">
P1: Static arrays of static structs are mis-sized when the struct contains static array fields, so later dynamic offsets can be encoded incorrectly.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

public static String calculateSignatureHash(String errorSignature) {
byte[] input = errorSignature.getBytes();
byte[] hash = Hash.sha3(input);
return Numeric.toHexString(hash).substring(2);
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Custom error selectors should be the first 4 bytes of the hash, not the full 32-byte digest.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At abi/src/main/java/org/tron/trident/abi/CustomErrorEncoder.java, line 40:

<comment>Custom error selectors should be the first 4 bytes of the hash, not the full 32-byte digest.</comment>

<file context>
@@ -0,0 +1,42 @@
+  public static String calculateSignatureHash(String errorSignature) {
+    byte[] input = errorSignature.getBytes();
+    byte[] hash = Hash.sha3(input);
+    return Numeric.toHexString(hash).substring(2);
+  }
+}
</file context>
Fix with Cubic

return StaticArray.class.isAssignableFrom(type)
&& (DynamicStruct.class.isAssignableFrom(
getParameterizedTypeFromArray(typeReference))
|| isDynamic(getParameterizedTypeFromArray(typeReference)));
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: This dynamic-array check is only one level deep. Nested static arrays like uint256[][3][3] are still treated as inline, so their function return decoding starts from the pointer word instead of the array payload.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At abi/src/main/java/org/tron/trident/abi/DefaultFunctionReturnDecoder.java, line 188:

<comment>This dynamic-array check is only one level deep. Nested static arrays like `uint256[][3][3]` are still treated as inline, so their function return decoding starts from the pointer word instead of the array payload.</comment>

<file context>
@@ -137,13 +155,39 @@ private static List<Type> build(String input, List<TypeReference<Type>> outputPa
+      return StaticArray.class.isAssignableFrom(type)
+              && (DynamicStruct.class.isAssignableFrom(
+              getParameterizedTypeFromArray(typeReference))
+              || isDynamic(getParameterizedTypeFromArray(typeReference)));
+    } catch (ClassCastException e) {
+      return false;
</file context>
Fix with Cubic

Comment on lines +127 to +144
if (DynamicStruct.class.isAssignableFrom(
getParameterizedTypeFromArray(typeReference))) {
offset += MAX_BYTE_LENGTH_FOR_HEX_STRING;
} else if (StaticStruct.class.isAssignableFrom(
getParameterizedTypeFromArray(typeReference))) {
offset +=
staticStructNestedPublicFieldsFlatList(
getParameterizedTypeFromArray(
typeReference))
.size()
* length
* MAX_BYTE_LENGTH_FOR_HEX_STRING;
} else if (Utf8String.class.isAssignableFrom(
getParameterizedTypeFromArray(typeReference))) {
offset += MAX_BYTE_LENGTH_FOR_HEX_STRING;
} else {
offset += length * MAX_BYTE_LENGTH_FOR_HEX_STRING;
}
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: This offset update still treats StaticArrayN<DynamicBytes> and StaticArrayN<DynamicArray<...>> as inline arrays. After decoding one of those returns, the next output is read from the wrong tuple offset.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At abi/src/main/java/org/tron/trident/abi/DefaultFunctionReturnDecoder.java, line 127:

<comment>This offset update still treats `StaticArrayN<DynamicBytes>` and `StaticArrayN<DynamicArray<...>>` as inline arrays. After decoding one of those returns, the next output is read from the wrong tuple offset.</comment>

<file context>
@@ -78,55 +83,68 @@ private static List<Type> build(String input, List<TypeReference<Type>> outputPa
-
+                  TypeDecoder.decodeStaticArray(
+                          input, hexStringDataOffset, typeReference, length);
+          if (DynamicStruct.class.isAssignableFrom(
+                  getParameterizedTypeFromArray(typeReference))) {
+            offset += MAX_BYTE_LENGTH_FOR_HEX_STRING;
</file context>
Suggested change
if (DynamicStruct.class.isAssignableFrom(
getParameterizedTypeFromArray(typeReference))) {
offset += MAX_BYTE_LENGTH_FOR_HEX_STRING;
} else if (StaticStruct.class.isAssignableFrom(
getParameterizedTypeFromArray(typeReference))) {
offset +=
staticStructNestedPublicFieldsFlatList(
getParameterizedTypeFromArray(
typeReference))
.size()
* length
* MAX_BYTE_LENGTH_FOR_HEX_STRING;
} else if (Utf8String.class.isAssignableFrom(
getParameterizedTypeFromArray(typeReference))) {
offset += MAX_BYTE_LENGTH_FOR_HEX_STRING;
} else {
offset += length * MAX_BYTE_LENGTH_FOR_HEX_STRING;
}
Class<Type> elementType = getParameterizedTypeFromArray(typeReference);
if (StaticStruct.class.isAssignableFrom(elementType)) {
offset +=
staticStructNestedPublicFieldsFlatList(elementType).size()
* length
* MAX_BYTE_LENGTH_FOR_HEX_STRING;
} else if (isDynamic(elementType)) {
offset += MAX_BYTE_LENGTH_FOR_HEX_STRING;
} else {
offset += length * MAX_BYTE_LENGTH_FOR_HEX_STRING;
}
Fix with Cubic

@@ -0,0 +1,1082 @@
package org.tron.trident.abi;
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: These packed-array expectations keep 32-byte element padding, so they lock in non-packed output for sub-32-byte array elements.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At abi/src/test/java/org/tron/trident/abi/TypeEncoderPackedTest.java, line 828:

<comment>These packed-array expectations keep 32-byte element padding, so they lock in non-packed output for sub-32-byte array elements.</comment>

<file context>
@@ -0,0 +1,1082 @@
+            Uint16.class, new Uint16(0x45), new Uint16(0x7), new Uint16(65535));
+
+    assertEquals(
+        "0000000000000000000000000000000000000000000000000000000000000045"
+            + "0000000000000000000000000000000000000000000000000000000000000007"
+            + "000000000000000000000000000000000000000000000000000000000000ffff",
</file context>
Fix with Cubic

return (Class<T>)
Class.forName(
((ParameterizedType) typeArgument)
.getActualTypeArguments()[0].getTypeName());
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: This helper breaks 3D dynamic-array decoding because Class.forName is given a parameterized type string, not a class name.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At abi/src/main/java/org/tron/trident/abi/Utils.java, line 226:

<comment>This helper breaks 3D dynamic-array decoding because `Class.forName` is given a parameterized type string, not a class name.</comment>

<file context>
@@ -94,18 +182,50 @@ static <T extends Type, U extends Type> String getParameterizedTypeName(
+    return (Class<T>)
+            Class.forName(
+                    ((ParameterizedType) typeArgument)
+                            .getActualTypeArguments()[0].getTypeName());
+  }
+
</file context>
Fix with Cubic


if (typeArguments[0] instanceof ParameterizedType) {
return (Class<T>)
Class.forName(getTypeName(((ParameterizedType) typeArguments[0]).getRawType()));
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Nested array type names are reduced to the raw inner array class, so multidimensional TypeReferences produce non-canonical ABI names like staticarray2[3].

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At abi/src/main/java/org/tron/trident/abi/Utils.java, line 208:

<comment>Nested array type names are reduced to the raw inner array class, so multidimensional `TypeReference`s produce non-canonical ABI names like `staticarray2[3]`.</comment>

<file context>
@@ -94,18 +182,50 @@ static <T extends Type, U extends Type> String getParameterizedTypeName(
 
+    if (typeArguments[0] instanceof ParameterizedType) {
+      return (Class<T>)
+              Class.forName(getTypeName(((ParameterizedType) typeArguments[0]).getRawType()));
+    }
+
</file context>
Fix with Cubic

Comment on lines +24 to +26
for (TypeReference<?> actualParameter : event.getParameters()) {
assertEquals(expectedParameter.next(), actualParameter);
}
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This loop can pass even when getParameters() returns too few elements, because it never asserts that all expected parameters were visited.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At abi/src/test/java/org/tron/trident/abi/datatypes/CustomErrorTest.java, line 24:

<comment>This loop can pass even when `getParameters()` returns too few elements, because it never asserts that all expected parameters were visited.</comment>

<file context>
@@ -0,0 +1,29 @@
+    assertEquals(event.getName(), "MyError");
+
+    Iterator<TypeReference<?>> expectedParameter = parameters.iterator();
+    for (TypeReference<?> actualParameter : event.getParameters()) {
+      assertEquals(expectedParameter.next(), actualParameter);
+    }
</file context>
Suggested change
for (TypeReference<?> actualParameter : event.getParameters()) {
assertEquals(expectedParameter.next(), actualParameter);
}
assertEquals(parameters.size(), event.getParameters().size());
for (TypeReference<?> actualParameter : event.getParameters()) {
assertEquals(expectedParameter.next(), actualParameter);
}
Fix with Cubic

Comment thread abi/src/main/java/org/tron/trident/abi/datatypes/Utf8String.java
throw new UnsupportedOperationException("Unsupported type encountered: " + type);
default: {
try {
return (Class<? extends Type>) Class.forName(type);
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Reject reflected classes that do not implement Type. Accepting any loadable class here turns invalid ABI type names into later runtime failures instead of the expected unsupported-type error.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At abi/src/main/java/org/tron/trident/abi/datatypes/AbiTypes.java, line 372:

<comment>Reject reflected classes that do not implement `Type`. Accepting any loadable class here turns invalid ABI type names into later runtime failures instead of the expected unsupported-type error.</comment>

<file context>
@@ -367,11 +367,22 @@ public static Class<? extends Type> getType(String type, boolean primitives) {
-        throw new UnsupportedOperationException("Unsupported type encountered: " + type);
+      default: {
+        try {
+          return (Class<? extends Type>) Class.forName(type);
+        } catch (ClassNotFoundException e) {
+          throw new UnsupportedOperationException(
</file context>
Suggested change
return (Class<? extends Type>) Class.forName(type);
Class<?> resolvedType = Class.forName(type);
if (!Type.class.isAssignableFrom(resolvedType)) {
throw new UnsupportedOperationException("Unsupported type encountered: " + type);
}
return (Class<? extends Type>) resolvedType;
Fix with Cubic

FUNC_addDynamicBytesArray,
Arrays.<Type>asList(
new BytesStruct(
"dynamic".getBytes(), BigInteger.ZERO, "Bytes".getBytes())),
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Specify the charset for these string-to-bytes conversions so the ABI fixture is deterministic across JVM default encodings.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At abi/src/test/java/org/tron/trident/abi/AbiV2TestFixture.java, line 880:

<comment>Specify the charset for these string-to-bytes conversions so the ABI fixture is deterministic across JVM default encodings.</comment>

<file context>
@@ -322,5 +817,146 @@ public Wiz(Foo foo, Utf8String data) {
+          FUNC_addDynamicBytesArray,
+          Arrays.<Type>asList(
+              new BytesStruct(
+                  "dynamic".getBytes(), BigInteger.ZERO, "Bytes".getBytes())),
+          Collections.<TypeReference<?>>emptyList());
+
</file context>
Suggested change
"dynamic".getBytes(), BigInteger.ZERO, "Bytes".getBytes())),
"dynamic".getBytes(java.nio.charset.StandardCharsets.UTF_8), BigInteger.ZERO, "Bytes".getBytes(java.nio.charset.StandardCharsets.UTF_8))),
Fix with Cubic

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="abi/src/main/java/org/tron/trident/abi/EventEncoder.java">

<violation number="1" location="abi/src/main/java/org/tron/trident/abi/EventEncoder.java:65">
P1: This changes `buildEventSignature()`/`encode()` to return `0x`-prefixed hashes under the same public API. That is a breaking behavior change and makes event topic encoding inconsistent with the other ABI encoders.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@@ -54,4 +64,14 @@ public static String buildEventSignature(String methodSignature) {
byte[] hash = Hash.sha3(input);
return Numeric.toHexString(hash);
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: This changes buildEventSignature()/encode() to return 0x-prefixed hashes under the same public API. That is a breaking behavior change and makes event topic encoding inconsistent with the other ABI encoders.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At abi/src/main/java/org/tron/trident/abi/EventEncoder.java, line 65:

<comment>This changes `buildEventSignature()`/`encode()` to return `0x`-prefixed hashes under the same public API. That is a breaking behavior change and makes event topic encoding inconsistent with the other ABI encoders.</comment>

<file context>
@@ -52,6 +62,16 @@ static <T extends Type> String buildMethodSignature(
     byte[] input = methodSignature.getBytes();
     byte[] hash = Hash.sha3(input);
-    return Numeric.toHexString(hash).substring(2);
+    return Numeric.toHexString(hash);
+  }
+
</file context>
Fix with Cubic

…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()
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