iterator = loader.iterator();
- return iterator.hasNext() ? iterator.next().get() : defaultDecoder();
- }
-
- private static FunctionReturnDecoder defaultDecoder() {
- if (DEFAULT_DECODER == null) {
- DEFAULT_DECODER = new DefaultFunctionReturnDecoder();
- }
- return DEFAULT_DECODER;
- }
}
diff --git a/abi/src/main/java/org/tron/trident/abi/TypeDecoder.java b/abi/src/main/java/org/tron/trident/abi/TypeDecoder.java
index 87f120fb..b8abed6b 100644
--- a/abi/src/main/java/org/tron/trident/abi/TypeDecoder.java
+++ b/abi/src/main/java/org/tron/trident/abi/TypeDecoder.java
@@ -13,6 +13,12 @@
package org.tron.trident.abi;
+import static org.tron.trident.abi.DefaultFunctionReturnDecoder.getDataOffset;
+import static org.tron.trident.abi.TypeReference.makeTypeReference;
+import static org.tron.trident.abi.Utils.findStructConstructor;
+import static org.tron.trident.abi.Utils.getSimpleTypeName;
+import static org.tron.trident.abi.Utils.staticStructNestedPublicFieldsFlatList;
+
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
@@ -24,6 +30,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.function.BiFunction;
import org.tron.trident.abi.datatypes.AbiTypes;
import org.tron.trident.abi.datatypes.Address;
@@ -41,6 +48,7 @@
import org.tron.trident.abi.datatypes.NumericType;
import org.tron.trident.abi.datatypes.StaticArray;
import org.tron.trident.abi.datatypes.StaticStruct;
+import org.tron.trident.abi.datatypes.StructType;
import org.tron.trident.abi.datatypes.Type;
import org.tron.trident.abi.datatypes.Ufixed;
import org.tron.trident.abi.datatypes.Uint;
@@ -54,20 +62,30 @@
* Ethereum Contract Application Binary Interface (ABI) decoding for types. Decoding is not
* documented, but is the reverse of the encoding details located here.
+ *
+ * The public API is composed of "decode*" methods and provides backward-compatibility. See
+ * https://github.com/hyperledger/web3j/issues/1591 for a discussion about decoding and possible
+ * improvements.
*/
public class TypeDecoder {
static final int MAX_BYTE_LENGTH_FOR_HEX_STRING = Type.MAX_BYTE_LENGTH << 1;
public static Type instantiateType(String solidityType, Object value)
- throws InvocationTargetException, NoSuchMethodException, InstantiationException,
- IllegalAccessException, ClassNotFoundException {
- return instantiateType(TypeReference.makeTypeReference(solidityType), value);
+ throws InvocationTargetException,
+ NoSuchMethodException,
+ InstantiationException,
+ IllegalAccessException,
+ ClassNotFoundException {
+ return instantiateType(makeTypeReference(solidityType), value);
}
public static Type instantiateType(TypeReference ref, Object value)
- throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
- InstantiationException, ClassNotFoundException {
+ throws NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException,
+ InstantiationException,
+ ClassNotFoundException {
Class rc = ref.getClassType();
if (Array.class.isAssignableFrom(rc)) {
return instantiateArrayType(ref, value);
@@ -116,6 +134,11 @@ static T decode(String input, Class type) {
return decode(input, 0, type);
}
+ public static T decode(String input, TypeReference> type)
+ throws ClassNotFoundException {
+ return decode(input, 0, ((TypeReference) type).getClassType());
+ }
+
public static Address decodeAddress(String input) {
return new Address(decodeNumeric(input, Uint160.class));
}
@@ -124,17 +147,16 @@ public static T decodeNumeric(String input, Class typ
try {
byte[] inputByteArray = Numeric.hexStringToByteArray(input);
int typeLengthAsBytes = getTypeLengthInBytes(type);
-
- byte[] resultByteArray = new byte[typeLengthAsBytes + 1];
-
- if (Int.class.isAssignableFrom(type) || Fixed.class.isAssignableFrom(type)) {
- resultByteArray[0] = inputByteArray[0]; // take MSB as sign bit
- }
-
int valueOffset = Type.MAX_BYTE_LENGTH - typeLengthAsBytes;
- System.arraycopy(inputByteArray, valueOffset, resultByteArray, 1, typeLengthAsBytes);
+ byte[] slice =
+ Arrays.copyOfRange(inputByteArray, valueOffset, valueOffset + typeLengthAsBytes);
- BigInteger numericValue = new BigInteger(resultByteArray);
+ BigInteger numericValue;
+ if (Uint.class.isAssignableFrom(type) || Ufixed.class.isAssignableFrom(type)) {
+ numericValue = new BigInteger(1, slice);
+ } else {
+ numericValue = new BigInteger(slice);
+ }
return type.getConstructor(BigInteger.class).newInstance(numericValue);
} catch (NoSuchMethodException
@@ -172,8 +194,11 @@ static int getTypeLength(Class type) {
}
static Type instantiateArrayType(TypeReference ref, Object value)
- throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
- InstantiationException, ClassNotFoundException {
+ throws NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException,
+ InstantiationException,
+ ClassNotFoundException {
List values;
if (value instanceof List) {
values = (List) value;
@@ -183,7 +208,7 @@ static Type instantiateArrayType(TypeReference ref, Object value)
throw new ClassCastException(
"Arg of type "
+ value.getClass()
- + " should be a list to instantiate web3j Array");
+ + " should be a list to instantiate trident Array");
}
Constructor listcons;
int arraySize =
@@ -207,8 +232,11 @@ static Type instantiateArrayType(TypeReference ref, Object value)
}
static Type instantiateAtomicType(Class> referenceClass, Object value)
- throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
- InstantiationException, ClassNotFoundException {
+ throws NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException,
+ InstantiationException,
+ ClassNotFoundException {
Object constructorArg = null;
if (NumericType.class.isAssignableFrom(referenceClass)) {
constructorArg = asBigInteger(value);
@@ -250,6 +278,7 @@ static Type instantiateAtomicType(Class> referenceClass, Object value)
return (Type) cons.newInstance(constructorArg);
}
+ @SuppressWarnings("unchecked")
static int getSingleElementLength(String input, int offset, Class type) {
if (input.length() == offset) {
return 0;
@@ -257,6 +286,8 @@ static int getSingleElementLength(String input, int offset, Cla
|| Utf8String.class.isAssignableFrom(type)) {
// length field + data value
return (decodeUintAsInt(input, offset) / Type.MAX_BYTE_LENGTH) + 2;
+ } else if (StaticStruct.class.isAssignableFrom(type)) {
+ return staticStructNestedPublicFieldsFlatList((Class) type).size();
} else {
return 1;
}
@@ -331,7 +362,7 @@ static T decodeStaticArray(
throw new UnsupportedOperationException(
"Zero length fixed array is invalid type");
} else {
- return instantiateStaticArray(typeReference, elements, length);
+ return instantiateStaticArray(elements, length);
}
};
@@ -350,9 +381,67 @@ public static T decodeStaticStruct(
}
};
+ if (typeReference.getInnerTypes() != null) {
+ return decodeStaticStructElementFromInnerTypes(input, offset, typeReference, function);
+ }
+
return decodeStaticStructElement(input, offset, typeReference, function);
}
+ private static int extractStaticArrayLength(TypeReference> typeReference) {
+ if (typeReference instanceof TypeReference.StaticArrayTypeReference) {
+ return ((TypeReference.StaticArrayTypeReference>) typeReference).getSize();
+ }
+ try {
+ Class> cls = typeReference.getClassType();
+ return Integer.parseInt(cls.getSimpleName().replaceAll("\\D+", ""));
+ } catch (Exception e) {
+ throw new UnsupportedOperationException(
+ "Cannot determine StaticArray length from " + typeReference.getType(), e);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static T decodeStaticStructElementFromInnerTypes(
+ final String input,
+ final int offset,
+ final TypeReference typeReference,
+ final BiFunction, String, T> consumer) {
+ try {
+ final List> innerTypes = typeReference.getInnerTypes();
+ List elements = new ArrayList<>(innerTypes.size());
+
+ for (int i = 0, currOffset = offset; i < innerTypes.size(); i++) {
+ T value;
+ final TypeReference innerType = (TypeReference) innerTypes.get(i);
+ final Class declaredField = innerType.getClassType();
+
+ if (StaticStruct.class.isAssignableFrom(declaredField)) {
+ value = decodeStaticStruct(input, currOffset, innerType);
+ currOffset += (value.bytes32PaddedLength() / Type.MAX_BYTE_LENGTH)
+ * MAX_BYTE_LENGTH_FOR_HEX_STRING;
+ } else if (StaticArray.class.isAssignableFrom(declaredField)) {
+ int staticLength = extractStaticArrayLength(innerType);
+ value = (T) decodeStaticArray(input, currOffset, innerType, staticLength);
+ currOffset += (value.bytes32PaddedLength() / Type.MAX_BYTE_LENGTH)
+ * MAX_BYTE_LENGTH_FOR_HEX_STRING;
+ } else {
+ value = decode(input.substring(currOffset, currOffset + 64), 0, declaredField);
+ currOffset += MAX_BYTE_LENGTH_FOR_HEX_STRING;
+ }
+ elements.add(value);
+ }
+
+ return consumer.apply(elements, getSimpleTypeName(typeReference.getClassType()));
+ } catch (ClassNotFoundException e) {
+ throw new UnsupportedOperationException(
+ "Unable to access parameterized type "
+ + Utils.getTypeName(typeReference.getType()),
+ e);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
private static T decodeStaticStructElement(
final String input,
final int offset,
@@ -360,23 +449,14 @@ private static T decodeStaticStructElement(
final BiFunction, String, T> consumer) {
try {
Class classType = typeReference.getClassType();
- Constructor> constructor = Arrays.stream(classType.getDeclaredConstructors())
- .filter(
- declaredConstructor ->
- Arrays.stream(declaredConstructor.getParameterTypes())
- .allMatch(Type.class::isAssignableFrom))
- .findAny()
- .orElseThrow(() ->
- new RuntimeException(
- "TypeReferenced struct must contain a constructor with types that extend Type"));
+ Constructor> constructor = findStructConstructor(classType);
final int length = constructor.getParameterCount();
List elements = new ArrayList<>(length);
- for (int i = 0, currOffset = 0; i < length; i++) {
+ for (int i = 0, currOffset = offset; i < length; i++) {
T value;
final Class declaredField = (Class) constructor.getParameterTypes()[i];
- //System.out.println(currOffset);
if (StaticStruct.class.isAssignableFrom(declaredField)) {
final int nestedStructLength =
classType
@@ -399,31 +479,31 @@ private static T decodeStaticStructElement(
elements.add(value);
}
- String typeName = Utils.getSimpleTypeName(classType);
+ String typeName = getSimpleTypeName(classType);
return consumer.apply(elements, typeName);
} catch (ClassNotFoundException e) {
throw new UnsupportedOperationException(
- "Unable to access parameterized type " + typeReference.getType().getTypeName(),
+ "Unable to access parameterized type "
+ + Utils.getTypeName(typeReference.getType()),
e);
}
}
+ @SuppressWarnings("unchecked")
private static T instantiateStruct(
final TypeReference typeReference, final List parameters) {
try {
- Constructor ctor = Arrays.stream(typeReference.getClassType().getDeclaredConstructors())
- .filter(
- declaredConstructor ->
- Arrays.stream(declaredConstructor.getParameterTypes())
- .allMatch(Type.class::isAssignableFrom))
- .findAny()
- .orElseThrow(() ->
- new RuntimeException(
- "TypeReference struct must contain a constructor with types that extend Type"));
-
- ctor.setAccessible(true);
- return (T) ctor.newInstance(parameters.toArray());
+ Class classType = typeReference.getClassType();
+ if (classType.isAssignableFrom(DynamicStruct.class)) {
+ return (T) new DynamicStruct((List) parameters);
+ } else if (classType.isAssignableFrom(StaticStruct.class)) {
+ return (T) new StaticStruct((List) parameters);
+ } else {
+ Constructor ctor = findStructConstructor(classType);
+ ctor.setAccessible(true);
+ return (T) ctor.newInstance(parameters.toArray());
+ }
} catch (ReflectiveOperationException e) {
throw new UnsupportedOperationException(
"Constructor cannot accept" + Arrays.toString(parameters.toArray()), e);
@@ -445,21 +525,141 @@ public static T decodeDynamicArray(
}
public static T decodeDynamicStruct(
- String input, int offset, TypeReference typeReference) {
+ String input, int offset, TypeReference typeReference)
+ throws ClassNotFoundException {
BiFunction, String, T> function =
(elements, typeName) -> {
if (elements.isEmpty()) {
throw new UnsupportedOperationException(
"Zero length fixed array is invalid type");
- } else {
- return instantiateStruct(typeReference, elements);
}
+ return instantiateStruct(typeReference, elements);
};
+ if (typeReference.getClassType().isAssignableFrom(DynamicStruct.class)
+ && typeReference.getInnerTypes() != null) {
+ return decodeDynamicStructElementsFromInnerTypes(
+ input, offset, typeReference, function);
+ }
+
return decodeDynamicStructElements(input, offset, typeReference, function);
}
+ private static class ParameterOffsetTracker {
+ public final Map parameters;
+ public final List parameterOffsets;
+ public int staticOffset;
+ public int dynamicParametersToProcess;
+
+ ParameterOffsetTracker(
+ final Map parametersIn,
+ final List parameterOffsetsIn,
+ int staticOffsetIn,
+ int dynamicParametersToProcessIn) {
+ this.parameters = parametersIn;
+ this.parameterOffsets = parameterOffsetsIn;
+ this.staticOffset = staticOffsetIn;
+ this.dynamicParametersToProcess = dynamicParametersToProcessIn;
+ }
+ }
+
+ private static
+ ParameterOffsetTracker getDynamicOffsetsAndNonDynamicParameters(
+ final String input, final int offset, final TypeReference typeReference)
+ throws ClassNotFoundException {
+ ParameterOffsetTracker tracker =
+ new ParameterOffsetTracker(new HashMap<>(), new ArrayList<>(), 0, 0);
+
+ final List> innerTypes = typeReference.getInnerTypes();
+ for (int i = 0; i < innerTypes.size(); ++i) {
+ final TypeReference innerType = (TypeReference) innerTypes.get(i);
+ final Class declaredField = innerType.getClassType();
+ final T value;
+ final int beginIndex = offset + tracker.staticOffset;
+ if (isDynamic(innerType)) {
+ final int parameterOffset =
+ decodeDynamicStructDynamicParameterOffset(
+ input.substring(beginIndex, beginIndex + 64))
+ + offset;
+ tracker.parameterOffsets.add(parameterOffset);
+ tracker.staticOffset += 64;
+ tracker.dynamicParametersToProcess += 1;
+ } else {
+ if (StaticStruct.class.isAssignableFrom(declaredField)) {
+ value = decodeStaticStruct(input.substring(beginIndex), 0, innerType);
+ tracker.staticOffset += (value.bytes32PaddedLength() / Type.MAX_BYTE_LENGTH)
+ * MAX_BYTE_LENGTH_FOR_HEX_STRING;
+ } else if (StaticArray.class.isAssignableFrom(declaredField)) {
+ int staticLength = extractStaticArrayLength(innerType);
+ value = (T) decodeStaticArray(input, beginIndex, innerType, staticLength);
+ tracker.staticOffset += (value.bytes32PaddedLength() / Type.MAX_BYTE_LENGTH)
+ * MAX_BYTE_LENGTH_FOR_HEX_STRING;
+ } else {
+ value = decode(input.substring(beginIndex), 0, declaredField);
+ tracker.staticOffset += value.bytes32PaddedLength() * 2;
+ }
+ tracker.parameters.put(i, value);
+ }
+ }
+
+ return tracker;
+ }
+
+ private static List getDynamicParametersWithTracker(
+ final String input,
+ final TypeReference typeReference,
+ final ParameterOffsetTracker tracker)
+ throws ClassNotFoundException {
+
+ final List> innerTypes = typeReference.getInnerTypes();
+ int dynamicParametersProcessed = 0;
+ for (int i = 0; i < innerTypes.size(); ++i) {
+ final TypeReference parameterTypeReference = (TypeReference) innerTypes.get(i);
+ if (isDynamic(parameterTypeReference)) {
+ final boolean isLastParameterInStruct =
+ dynamicParametersProcessed == (tracker.dynamicParametersToProcess - 1);
+ final int parameterLength =
+ isLastParameterInStruct
+ ? input.length()
+ - tracker.parameterOffsets.get(dynamicParametersProcessed)
+ : tracker.parameterOffsets.get(dynamicParametersProcessed + 1)
+ - tracker.parameterOffsets.get(dynamicParametersProcessed);
+
+ tracker.parameters.put(
+ i,
+ decodeDynamicParameterFromStructWithTypeReference(
+ input,
+ tracker.parameterOffsets.get(dynamicParametersProcessed),
+ parameterLength,
+ parameterTypeReference));
+ dynamicParametersProcessed++;
+ }
+ }
+
+ final List elements = new ArrayList<>();
+ for (int i = 0; i < innerTypes.size(); ++i) {
+ elements.add(tracker.parameters.get(i));
+ }
+
+ return elements;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static T decodeDynamicStructElementsFromInnerTypes(
+ final String input,
+ final int offset,
+ final TypeReference typeReference,
+ final BiFunction, String, T> consumer)
+ throws ClassNotFoundException {
+ ParameterOffsetTracker tracker =
+ getDynamicOffsetsAndNonDynamicParameters(input, offset, typeReference);
+ final List parameters = getDynamicParametersWithTracker(input, typeReference, tracker);
+ String typeName = getSimpleTypeName(typeReference.getClassType());
+ return consumer.apply(parameters, typeName);
+ }
+
+ @SuppressWarnings("unchecked")
private static T decodeDynamicStructElements(
final String input,
final int offset,
@@ -467,15 +667,7 @@ private static T decodeDynamicStructElements(
final BiFunction, String, T> consumer) {
try {
final Class classType = typeReference.getClassType();
- Constructor> constructor = Arrays.stream(classType.getDeclaredConstructors())
- .filter(
- declaredConstructor ->
- Arrays.stream(declaredConstructor.getParameterTypes())
- .allMatch(Type.class::isAssignableFrom))
- .findAny()
- .orElseThrow(() ->
- new RuntimeException(
- "TypeReferenced struct must contain a constructor with types that extend Type"));
+ Constructor> constructor = findStructConstructor(classType);
final int length = constructor.getParameterCount();
final Map parameters = new HashMap<>();
int staticOffset = 0;
@@ -485,12 +677,10 @@ private static T decodeDynamicStructElements(
final T value;
final int beginIndex = offset + staticOffset;
if (isDynamic(declaredField)) {
- final boolean isOnlyParameterInStruct = length == 1;
final int parameterOffset =
- isOnlyParameterInStruct
- ? offset
- : decodeDynamicStructDynamicParameterOffset(
- input.substring(beginIndex, beginIndex + 64));
+ decodeDynamicStructDynamicParameterOffset(
+ input.substring(beginIndex, beginIndex + 64))
+ + offset;
parameterOffsets.add(parameterOffset);
staticOffset += 64;
} else {
@@ -500,11 +690,15 @@ private static T decodeDynamicStructElements(
input.substring(beginIndex),
0,
TypeReference.create(declaredField));
+ staticOffset +=
+ staticStructNestedPublicFieldsFlatList((Class) declaredField)
+ .size()
+ * MAX_BYTE_LENGTH_FOR_HEX_STRING;
} else {
value = decode(input.substring(beginIndex), 0, declaredField);
+ staticOffset += value.bytes32PaddedLength() * 2;
}
parameters.put(i, value);
- staticOffset += value.bytes32PaddedLength() * 2;
}
}
int dynamicParametersProcessed = 0;
@@ -521,18 +715,22 @@ private static T decodeDynamicStructElements(
- parameterOffsets.get(dynamicParametersProcessed)
: parameterOffsets.get(dynamicParametersProcessed + 1)
- parameterOffsets.get(dynamicParametersProcessed);
+ final Class parameterFromAnnotation =
+ Utils.extractParameterFromAnnotation(
+ constructor.getParameterAnnotations()[i]);
parameters.put(
i,
decodeDynamicParameterFromStruct(
input,
parameterOffsets.get(dynamicParametersProcessed),
parameterLength,
- declaredField));
+ declaredField,
+ parameterFromAnnotation));
dynamicParametersProcessed++;
}
}
- String typeName = Utils.getSimpleTypeName(classType);
+ String typeName = getSimpleTypeName(classType);
final List elements = new ArrayList<>();
for (int i = 0; i < length; ++i) {
@@ -542,7 +740,8 @@ private static T decodeDynamicStructElements(
return consumer.apply(elements, typeName);
} catch (ClassNotFoundException e) {
throw new UnsupportedOperationException(
- "Unable to access parameterized type " + typeReference.getType().getTypeName(),
+ "Unable to access parameterized type "
+ + Utils.getTypeName(typeReference.getType()),
e);
}
}
@@ -557,15 +756,51 @@ private static T decodeDynamicParameterFromStruct(
final String input,
final int parameterOffset,
final int parameterLength,
- final Class declaredField) {
+ final Class declaredField,
+ final Class parameter)
+ throws ClassNotFoundException {
final String dynamicElementData =
input.substring(parameterOffset, parameterOffset + parameterLength);
final T value;
if (DynamicStruct.class.isAssignableFrom(declaredField)) {
+ value = decodeDynamicStruct(dynamicElementData, 0, TypeReference.create(declaredField));
+ } else if (DynamicArray.class.isAssignableFrom(declaredField)) {
+ if (parameter == null) {
+ throw new RuntimeException(
+ "parameter can not be null, try to use annotation @Parameterized "
+ + "to specify the parameter type");
+ }
value =
- decodeDynamicStruct(
- dynamicElementData, 64, TypeReference.create(declaredField));
+ (T)
+ decodeDynamicArray(
+ dynamicElementData,
+ 0,
+ Utils.getDynamicArrayTypeReference(parameter));
+ } else {
+ value = decode(dynamicElementData, declaredField);
+ }
+ return value;
+ }
+
+ private static T decodeDynamicParameterFromStructWithTypeReference(
+ final String input,
+ final int parameterOffset,
+ final int parameterLength,
+ final TypeReference parameterTypeReference)
+ throws ClassNotFoundException {
+ final String dynamicElementData =
+ input.substring(parameterOffset, parameterOffset + parameterLength);
+ final Class declaredField = parameterTypeReference.getClassType();
+
+ final T value;
+ if (DynamicStruct.class.isAssignableFrom(declaredField)) {
+ value = decodeDynamicStruct(dynamicElementData, 0, parameterTypeReference);
+ } else if (DynamicArray.class.isAssignableFrom(declaredField)) {
+ value = (T) decodeDynamicArray(dynamicElementData, 0, parameterTypeReference);
+ } else if (StaticArray.class.isAssignableFrom(declaredField)) {
+ int staticLength = extractStaticArrayLength(parameterTypeReference);
+ value = (T) decodeStaticArray(dynamicElementData, 0, parameterTypeReference, staticLength);
} else {
value = decode(dynamicElementData, declaredField);
}
@@ -573,13 +808,56 @@ private static T decodeDynamicParameterFromStruct(
}
private static int decodeDynamicStructDynamicParameterOffset(final String input) {
- return (decodeUintAsInt(input, 0) * 2) + 64;
+ return (decodeUintAsInt(input, 0) * 2);
}
static boolean isDynamic(Class parameter) {
return DynamicBytes.class.isAssignableFrom(parameter)
|| Utf8String.class.isAssignableFrom(parameter)
- || DynamicArray.class.isAssignableFrom(parameter);
+ || DynamicArray.class.isAssignableFrom(parameter)
+ || DynamicStruct.class.isAssignableFrom(parameter);
+ }
+
+ /**
+ * Recursive ABI-dynamic check: a type is ABI-dynamic if it is itself a dynamic class
+ * (per {@link #isDynamic(Class)}) OR it is a StaticArray whose element type is ABI-dynamic.
+ * E.g. {@code string[3]} is ABI-dynamic because {@code string} is dynamic, even though
+ * its outer Java class is StaticArray.
+ */
+ @SuppressWarnings("unchecked")
+ static boolean isDynamic(TypeReference> typeReference) {
+ try {
+ Class cls = (Class) typeReference.getClassType();
+ if (isDynamic(cls)) {
+ return true;
+ }
+ if (StaticArray.class.isAssignableFrom(cls)) {
+ TypeReference> subRef = typeReference.getSubTypeReference();
+ if (subRef != null) {
+ return isDynamic(subRef);
+ }
+ java.lang.reflect.Type type = typeReference.getType();
+ if (type instanceof ParameterizedType) {
+ final java.lang.reflect.Type elementType =
+ ((ParameterizedType) type).getActualTypeArguments()[0];
+ return isDynamic(new TypeReference() {
+ @Override
+ public java.lang.reflect.Type getType() {
+ return elementType;
+ }
+ });
+ }
+ try {
+ Class paramType = Utils.getParameterizedTypeFromArray(typeReference);
+ return isDynamic(paramType);
+ } catch (Exception e) {
+ return false;
+ }
+ }
+ return false;
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
}
static BigInteger asBigInteger(Object arg) {
@@ -612,8 +890,7 @@ static List arrayToList(Object array) {
}
@SuppressWarnings("unchecked")
- private static T instantiateStaticArray(
- TypeReference typeReference, List elements, int length) {
+ private static T instantiateStaticArray(List elements, int length) {
try {
Class extends StaticArray> arrayClass =
(Class extends StaticArray>)
@@ -631,33 +908,182 @@ private static T decodeArrayElements(
TypeReference typeReference,
int length,
BiFunction, String, T> consumer) {
-
try {
Class cls = Utils.getParameterizedTypeFromArray(typeReference);
- if (Array.class.isAssignableFrom(cls)) {
- throw new UnsupportedOperationException(
- "Arrays of arrays are not currently supported for external functions, see"
- + "http://solidity.readthedocs.io/en/develop/types.html#members");
+ List elements = new ArrayList<>(length);
+ if (StructType.class.isAssignableFrom(cls)) {
+ int currOffset = offset;
+ for (int i = 0; i < length; i++) {
+ T value;
+ if (DynamicStruct.class.isAssignableFrom(cls)) {
+ if (Optional.ofNullable(typeReference)
+ .map(x -> x.getSubTypeReference())
+ .map(x -> x.getInnerTypes())
+ .isPresent()) {
+ value =
+ TypeDecoder.decodeDynamicStruct(
+ input,
+ offset + getDataOffset(input, currOffset, typeReference),
+ (TypeReference) new TypeReference(
+ typeReference.isIndexed(),
+ typeReference.getSubTypeReference().getInnerTypes()) {});
+ currOffset +=
+ getSingleElementLength(input, currOffset, cls)
+ * MAX_BYTE_LENGTH_FOR_HEX_STRING;
+ } else {
+ value =
+ TypeDecoder.decodeDynamicStruct(
+ input,
+ offset
+ + getDataOffset(
+ input, currOffset, typeReference),
+ TypeReference.create(cls));
+ currOffset +=
+ getSingleElementLength(input, currOffset, cls)
+ * MAX_BYTE_LENGTH_FOR_HEX_STRING;
+ }
+ } else {
+ if (Optional.ofNullable(typeReference)
+ .map(x -> x.getSubTypeReference())
+ .map(x -> x.getInnerTypes())
+ .isPresent()) {
+ value = TypeDecoder.decodeStaticStruct(
+ input,
+ currOffset,
+ (TypeReference) typeReference.getSubTypeReference());
+ currOffset +=
+ (value.bytes32PaddedLength() / Type.MAX_BYTE_LENGTH)
+ * MAX_BYTE_LENGTH_FOR_HEX_STRING;
+ } else {
+ value =
+ TypeDecoder.decodeStaticStruct(
+ input, currOffset, TypeReference.create(cls));
+ currOffset +=
+ (value.bytes32PaddedLength() / Type.MAX_BYTE_LENGTH)
+ * MAX_BYTE_LENGTH_FOR_HEX_STRING;
+ }
+ }
+ elements.add(value);
+ }
+
+ String typeName = getSimpleTypeName(cls);
+
+ return consumer.apply(elements, typeName);
+ } else if (Array.class.isAssignableFrom(cls)) {
+ for (int i = 0, currOffset = offset; i < length; i++) {
+ T value;
+ if (DynamicArray.class.isAssignableFrom(cls)) {
+ TypeReference> dynamicTypeRef =
+ Utils.resolveDynamicArrayElementTypeReference(typeReference);
+ value =
+ (T)
+ TypeDecoder.decodeDynamicArray(
+ input,
+ offset
+ + getDataOffset(
+ input, currOffset, typeReference),
+ dynamicTypeRef);
+ currOffset +=
+ getSingleElementLength(input, currOffset, cls)
+ * MAX_BYTE_LENGTH_FOR_HEX_STRING;
+ } else {
+ String typeName = cls.getSimpleName();
+ String extractedLength =
+ typeName.substring(typeName.replaceAll("[0-9]+$", "").length());
+ int staticLength =
+ extractedLength.isEmpty() ? 0 : Integer.parseInt(extractedLength);
+ final TypeReference innerType =
+ Utils.resolveStaticArrayInnerTypeReference(typeReference);
+
+ TypeReference.StaticArrayTypeReference staticReference =
+ new TypeReference.StaticArrayTypeReference(
+ staticLength) {
+
+ @Override
+ public TypeReference getSubTypeReference() {
+ return innerType;
+ }
+
+ @Override
+ public boolean isIndexed() {
+ return false;
+ }
+
+ @Override
+ public java.lang.reflect.Type getType() {
+ return new ParameterizedType() {
+ @Override
+ public java.lang.reflect.Type[] getActualTypeArguments() {
+ return new java.lang.reflect.Type[] {
+ innerType.getType()
+ };
+ }
+
+ @Override
+ public java.lang.reflect.Type getRawType() {
+ return cls;
+ }
+
+ @Override
+ public java.lang.reflect.Type getOwnerType() {
+ return Class.class;
+ }
+ };
+ }
+ };
+ if (isDynamic(staticReference)) {
+ // StaticArray with dynamic elements (e.g. string[3]) is ABI-dynamic:
+ // outer container stores an offset pointer (1 slot); actual data
+ // is at offset + pointerValue, in head/tail form.
+ int hexStringDataOffset =
+ getDataOffset(input, currOffset, staticReference);
+ value =
+ (T)
+ TypeDecoder.decodeStaticArray(
+ input,
+ offset + hexStringDataOffset,
+ staticReference,
+ staticLength);
+ currOffset += MAX_BYTE_LENGTH_FOR_HEX_STRING;
+ } else {
+ // All-static StaticArray: inline, no length prefix.
+ value =
+ (T)
+ TypeDecoder.decodeStaticArray(
+ input, currOffset, staticReference, staticLength);
+ currOffset +=
+ (value.bytes32PaddedLength() / Type.MAX_BYTE_LENGTH)
+ * MAX_BYTE_LENGTH_FOR_HEX_STRING;
+ }
+ }
+ elements.add(value);
+ }
+ return consumer.apply(elements, cls.getName());
} else {
- List elements = new ArrayList<>(length);
-
- for (int i = 0, currOffset = offset;
- i < length;
- i++,
- currOffset +=
- getSingleElementLength(input, currOffset, cls)
- * MAX_BYTE_LENGTH_FOR_HEX_STRING) {
- T value = decode(input, currOffset, cls);
+ int currOffset = offset;
+ for (int i = 0; i < length; i++) {
+ T value;
+ if (isDynamic(cls)) {
+ int hexStringDataOffset = getDataOffset(input, currOffset, typeReference);
+ value = decode(input, offset + hexStringDataOffset, cls);
+ currOffset += MAX_BYTE_LENGTH_FOR_HEX_STRING;
+ } else {
+ value = decode(input, currOffset, cls);
+ currOffset +=
+ getSingleElementLength(input, currOffset, cls)
+ * MAX_BYTE_LENGTH_FOR_HEX_STRING;
+ }
elements.add(value);
}
- String typeName = Utils.getSimpleTypeName(cls);
+ String typeName = getSimpleTypeName(cls);
return consumer.apply(elements, typeName);
}
} catch (ClassNotFoundException e) {
throw new UnsupportedOperationException(
- "Unable to access parameterized type " + typeReference.getType().getTypeName(),
+ "Unable to access parameterized type "
+ + Utils.getTypeName(typeReference.getType()),
e);
}
}
diff --git a/abi/src/main/java/org/tron/trident/abi/TypeEncoder.java b/abi/src/main/java/org/tron/trident/abi/TypeEncoder.java
index 6ff0b67a..ac0fdcb5 100644
--- a/abi/src/main/java/org/tron/trident/abi/TypeEncoder.java
+++ b/abi/src/main/java/org/tron/trident/abi/TypeEncoder.java
@@ -20,6 +20,7 @@
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
+import java.util.stream.Collectors;
import org.tron.trident.abi.datatypes.Address;
import org.tron.trident.abi.datatypes.Array;
import org.tron.trident.abi.datatypes.Bool;
@@ -28,8 +29,11 @@
import org.tron.trident.abi.datatypes.DynamicArray;
import org.tron.trident.abi.datatypes.DynamicBytes;
import org.tron.trident.abi.datatypes.DynamicStruct;
+import org.tron.trident.abi.datatypes.Fixed;
+import org.tron.trident.abi.datatypes.FixedPointType;
import org.tron.trident.abi.datatypes.NumericType;
import org.tron.trident.abi.datatypes.StaticArray;
+import org.tron.trident.abi.datatypes.StaticStruct;
import org.tron.trident.abi.datatypes.Type;
import org.tron.trident.abi.datatypes.Ufixed;
import org.tron.trident.abi.datatypes.Uint;
@@ -46,10 +50,34 @@ public class TypeEncoder {
private TypeEncoder() {
}
+ /**
+ * Determines if a given ABI type is dynamic.
+ * According to the ABI specification, dynamic types require an offset pointer in the tuple header
+ * (occupying exactly 1 slot / 32 bytes) rather than being encoded inline. This method recursively
+ * checks StaticArrays to properly identify if they contain any dynamic elements.
+ *
+ * @param parameter The ABI type parameter to check.
+ * @return true if the type is dynamic, false otherwise.
+ */
static boolean isDynamic(Type parameter) {
- return parameter instanceof DynamicBytes
+ if (parameter instanceof DynamicBytes
|| parameter instanceof Utf8String
- || parameter instanceof DynamicArray;
+ || parameter instanceof DynamicArray
+ || parameter instanceof DynamicStruct) {
+ return true;
+ }
+ if (parameter instanceof StaticArray) {
+ StaticArray staticArray = (StaticArray) parameter;
+ if (!staticArray.getValue().isEmpty()) {
+ return isDynamic((Type) staticArray.getValue().get(0));
+ }
+ Class> componentType = staticArray.getComponentType();
+ return DynamicBytes.class.isAssignableFrom(componentType)
+ || Utf8String.class.isAssignableFrom(componentType)
+ || DynamicArray.class.isAssignableFrom(componentType)
+ || DynamicStruct.class.isAssignableFrom(componentType);
+ }
+ return false;
}
@SuppressWarnings("unchecked")
@@ -67,7 +95,11 @@ public static String encode(Type parameter) {
} else if (parameter instanceof Utf8String) {
return encodeString((Utf8String) parameter);
} else if (parameter instanceof StaticArray) {
- return encodeArrayValues((StaticArray) parameter);
+ if (isDynamic(parameter)) {
+ return encodeStaticArrayWithDynamicStruct((StaticArray) parameter);
+ } else {
+ return encodeArrayValues((StaticArray) parameter);
+ }
} else if (parameter instanceof DynamicStruct) {
return encodeDynamicStruct((DynamicStruct) parameter);
} else if (parameter instanceof DynamicArray) {
@@ -80,6 +112,87 @@ public static String encode(Type parameter) {
}
}
+ /**
+ * Returns abi.encodePacked hex value for the supported types. First the value is encoded and
+ * after the padding or length, in arrays cases, is removed resulting the packed encode hex
+ * value
+ *
+ * @param parameter Value to be encoded
+ * @return
+ */
+ public static String encodePacked(Type parameter) {
+ if (parameter instanceof Utf8String) {
+ // removePadding can also be used, but is not necessary
+ return Numeric.toHexStringNoPrefix(
+ ((Utf8String) parameter).getValue().getBytes(StandardCharsets.UTF_8));
+ } else if (parameter instanceof DynamicBytes) {
+ // removePadding can also be used, but is not necessary
+ return Numeric.toHexStringNoPrefix(((DynamicBytes) parameter).getValue());
+ } else if (parameter instanceof DynamicArray) {
+ return arrayEncodePacked((DynamicArray) parameter);
+ } else if (parameter instanceof StaticArray) {
+ return arrayEncodePacked((StaticArray) parameter);
+ } else if (parameter instanceof PrimitiveType) {
+ return encodePacked(((PrimitiveType) parameter).toSolidityType());
+ } else {
+ return removePadding(encode(parameter), parameter);
+ }
+ }
+
+ /**
+ * Remove padding from the static types and {@link Utf8String} after the encode was applied
+ *
+ * @param encodedValue Encoded value of the parameter
+ * @param parameter Value which was encoded
+ * @return The encoded value without padding
+ */
+ static String removePadding(String encodedValue, Type parameter) {
+ if (parameter instanceof NumericType) {
+ if (parameter instanceof Ufixed || parameter instanceof Fixed) {
+ return encodedValue;
+ }
+ return encodedValue.substring(64 - ((NumericType) parameter).getBitSize() / 4, 64);
+ } else if (parameter instanceof Address) {
+ return encodedValue.substring(64 - ((Address) parameter).toUint().getBitSize() / 4, 64);
+ } else if (parameter instanceof Bool) {
+ return encodedValue.substring(62, 64);
+ }
+ if (parameter instanceof Bytes) {
+ return encodedValue.substring(0, ((BytesType) parameter).getValue().length * 2);
+ }
+ if (parameter instanceof Utf8String) {
+ int length =
+ ((Utf8String) parameter).getValue().getBytes(StandardCharsets.UTF_8).length;
+ return encodedValue.substring(64, 64 + length * 2);
+ }
+ if (parameter instanceof DynamicBytes) {
+ return encodedValue.substring(
+ 64, 64 + ((DynamicBytes) parameter).getValue().length * 2);
+ } else {
+ throw new UnsupportedOperationException(
+ "Type cannot be encoded: " + parameter.getClass());
+ }
+ }
+
+ /**
+ * Encodes a static array containing a dynamic struct type. In this case, the array items are
+ * decoded as dynamic values and have their offsets at the beginning of the encoding. Example:
+ * For the following static array containing three elements: StaticArray3
+ * enc([struct1, struct2, struct2]) = offset(enc(struct1)) offset(enc(struct2))
+ * offset(enc(struct3)) enc(struct1) enc(struct2) enc(struct3)
+ *
+ **/
+ private static String encodeStaticArrayWithDynamicStruct(Array value) {
+ String valuesOffsets = encodeDynamicsTypesArraysOffsets(value);
+ String encodedValues = encodeArrayValues(value);
+
+ StringBuilder result = new StringBuilder();
+ result.append(valuesOffsets);
+ result.append(encodedValues);
+ return result.toString();
+ }
+
+
static String encodeAddress(Address address) {
return encodeNumeric(address.toUint());
}
@@ -199,8 +312,9 @@ private static String encodeDynamicStructValues(final DynamicStruct value) {
Numeric.toBytesPadded(
new BigInteger(Long.toString(dynamicOffset)),
MAX_BYTE_LENGTH)));
- dynamicValues.add(encode(type));
- dynamicOffset += type.bytes32PaddedLength();
+ String encodedValue = encode(type);
+ dynamicValues.add(encodedValue);
+ dynamicOffset += encodedValue.length() >> 1;
} else {
offsetsAndStaticValues.add(encode(value.getValue().get(i)));
}
@@ -224,22 +338,48 @@ static String encodeDynamicArray(DynamicArray value) {
return result.toString();
}
+ /**
+ * Encodes the array values offsets of the to be encrypted dynamic array, which are in our case
+ * the heads of the encryption. Refer to
+ *
+ * @see encoding
+ * formal specification
+ * Dynamic structs array encryption
+ * An array of dynamic structs (ie, structs containing dynamic datatypes) is encoded in
+ * the following way: Considering X = [struct1, struct2] for example enc(X) = head(struct1)
+ * head(struct2) tail(struct1) tail(struct2) with: - tail(struct1) = enc(struct1) -
+ * tail(struct2) = enc(struct2) - head(struct1) = enc(len( head(struct1) head(struct2))) =
+ * enc(64), because the heads are 256bits - head(struct2) = enc(len( head(struct1)
+ * head(struct2) tail(struct1)))
+ */
+
private static String encodeArrayValuesOffsets(DynamicArray value) {
StringBuilder result = new StringBuilder();
boolean arrayOfBytes =
!value.getValue().isEmpty() && value.getValue().get(0) instanceof DynamicBytes;
boolean arrayOfString =
!value.getValue().isEmpty() && value.getValue().get(0) instanceof Utf8String;
+ boolean arrayOfDynamicStructs =
+ !value.getValue().isEmpty() && value.getValue().get(0) instanceof DynamicStruct;
+ boolean arrayOfDynamicArrays =
+ !value.getValue().isEmpty() && value.getValue().get(0) instanceof DynamicArray;
+ boolean arrayOfDynamicStaticArrays =
+ !value.getValue().isEmpty()
+ && value.getValue().get(0) instanceof StaticArray
+ && isDynamic(value.getValue().get(0));
if (arrayOfBytes || arrayOfString) {
long offset = 0;
for (int i = 0; i < value.getValue().size(); i++) {
if (i == 0) {
- offset = value.getValue().size() * MAX_BYTE_LENGTH;
+ offset = (long) value.getValue().size() * MAX_BYTE_LENGTH;
} else {
int bytesLength =
arrayOfBytes
? ((byte[]) value.getValue().get(i - 1).getValue()).length
- : ((String) value.getValue().get(i - 1).getValue()).length();
+ : ((String) value.getValue().get(i - 1).getValue())
+ .getBytes(StandardCharsets.UTF_8)
+ .length;
int numberOfWords = (bytesLength + MAX_BYTE_LENGTH - 1) / MAX_BYTE_LENGTH;
int totalBytesLength = numberOfWords * MAX_BYTE_LENGTH;
offset += totalBytesLength + MAX_BYTE_LENGTH;
@@ -249,7 +389,70 @@ private static String encodeArrayValuesOffsets(DynamicArray
Numeric.toBytesPadded(
new BigInteger(Long.toString(offset)), MAX_BYTE_LENGTH)));
}
+ } else if (arrayOfDynamicArrays || arrayOfDynamicStructs || arrayOfDynamicStaticArrays) {
+ result.append(encodeDynamicsTypesArraysOffsets(value));
}
return result.toString();
}
+
+ /**
+ * Encodes arrays of structs or dynamic arrays elements offsets. To be used when encoding a
+ * dynamic arrays or a static array containing dynamic structs,
+ *
+ * @param value DynamicArray or StaticArray containing dynamic structs
+ * @return encoded array offset
+ */
+ private static String encodeDynamicsTypesArraysOffsets(Array value) {
+ StringBuilder result = new StringBuilder();
+ long offset = value.getValue().size();
+ List tailsEncoding =
+ value.getValue().stream().map(TypeEncoder::encode).collect(Collectors.toList());
+ for (int i = 0; i < value.getValue().size(); i++) {
+ if (i == 0) {
+ offset = offset * MAX_BYTE_LENGTH;
+ } else {
+ offset += tailsEncoding.get(i - 1).length() / 2;
+ }
+ result.append(
+ Numeric.toHexStringNoPrefix(
+ Numeric.toBytesPadded(
+ new BigInteger(Long.toString(offset)), MAX_BYTE_LENGTH)));
+ }
+ return result.toString();
+ }
+
+ /**
+ * Checks if the received array doesn't contain any element that can make the array unsupported
+ * for abi.encodePacked
+ *
+ * @param value Array to which the abi.encodePacked should be applied
+ * @param Types of elements from the array
+ * @return if the encodePacked is supported for the given array
+ */
+ private static boolean isSupportingEncodedPacked(Array value) {
+ if (Utf8String.class.isAssignableFrom(value.getComponentType())
+ || DynamicStruct.class.isAssignableFrom(value.getComponentType())
+ || DynamicArray.class.isAssignableFrom(value.getComponentType())
+ || StaticStruct.class.isAssignableFrom(value.getComponentType())
+ || FixedPointType.class.isAssignableFrom(value.getComponentType())
+ || DynamicBytes.class.isAssignableFrom(value.getComponentType())) {
+ return false;
+ }
+ return true;
+ }
+
+ private static String arrayEncodePacked(Array values) {
+ 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());
+ }
}
diff --git a/abi/src/main/java/org/tron/trident/abi/TypeReference.java b/abi/src/main/java/org/tron/trident/abi/TypeReference.java
index bd7631d6..549ed3da 100644
--- a/abi/src/main/java/org/tron/trident/abi/TypeReference.java
+++ b/abi/src/main/java/org/tron/trident/abi/TypeReference.java
@@ -15,6 +15,7 @@
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
+import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.tron.trident.abi.datatypes.AbiTypes;
@@ -41,10 +42,17 @@ public abstract class TypeReference> innerTypes;
+
protected TypeReference() {
this(false);
}
+ protected TypeReference(boolean indexed, List> innerTypesIn) {
+ this(indexed);
+ this.innerTypes = innerTypesIn;
+ }
+
protected TypeReference(boolean indexed) {
Type superclass = getClass().getGenericSuperclass();
if (superclass instanceof Class) {
@@ -65,6 +73,11 @@ TypeReference getSubTypeReference() {
return null;
}
+ public List> getInnerTypes() {
+ return this.innerTypes;
+ }
+
+
public int compareTo(TypeReference o) {
// taken from the blog post comments - this results in an errror if the
// type parameter is left out.
@@ -93,7 +106,7 @@ public Class getClassType() throws ClassNotFoundException {
if (getType() instanceof ParameterizedType) {
return (Class) ((ParameterizedType) clsType).getRawType();
} else {
- return (Class) Class.forName(clsType.getTypeName());
+ return Utils.safeLoadTypeClass(Utils.getTypeName(clsType));
}
}
@@ -178,7 +191,7 @@ public static TypeReference makeTypeReference(
arrayWrappedType =
new TypeReference(indexed) {
@Override
- TypeReference getSubTypeReference() {
+ public TypeReference getSubTypeReference() {
return baseTr;
}
@@ -216,7 +229,7 @@ public java.lang.reflect.Type getOwnerType() {
new TypeReference.StaticArrayTypeReference(arraySizeInt) {
@Override
- TypeReference getSubTypeReference() {
+ public TypeReference getSubTypeReference() {
return baseTr;
}
diff --git a/abi/src/main/java/org/tron/trident/abi/Utils.java b/abi/src/main/java/org/tron/trident/abi/Utils.java
index 74d51d14..08c0f978 100644
--- a/abi/src/main/java/org/tron/trident/abi/Utils.java
+++ b/abi/src/main/java/org/tron/trident/abi/Utils.java
@@ -13,21 +13,35 @@
package org.tron.trident.abi;
+import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
+import java.util.ArrayDeque;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.IdentityHashMap;
import java.util.List;
+import java.util.Set;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.tron.trident.abi.datatypes.DynamicArray;
import org.tron.trident.abi.datatypes.DynamicBytes;
import org.tron.trident.abi.datatypes.Fixed;
import org.tron.trident.abi.datatypes.Int;
import org.tron.trident.abi.datatypes.StaticArray;
+import org.tron.trident.abi.datatypes.StaticStruct;
+import org.tron.trident.abi.datatypes.StructType;
import org.tron.trident.abi.datatypes.Type;
import org.tron.trident.abi.datatypes.Ufixed;
import org.tron.trident.abi.datatypes.Uint;
import org.tron.trident.abi.datatypes.Utf8String;
+import org.tron.trident.abi.datatypes.reflection.Parameterized;
/**
* Utility functions.
@@ -37,6 +51,27 @@ public class Utils {
private Utils() {
}
+ /**
+ * Reflectively loads a class by FQN and verifies it is an ABI {@link Type}
+ * subtype. Uses {@code initialize=false} so loading never triggers the
+ * target class's static initializer — defense-in-depth against side
+ * effects from unexpected class names sneaking into a TypeReference graph.
+ *
+ * @throws ClassNotFoundException if the named class cannot be located
+ * @throws UnsupportedOperationException if the named class is not a Type subtype
+ */
+ @SuppressWarnings("unchecked")
+ static Class safeLoadTypeClass(String fqcn)
+ throws ClassNotFoundException {
+ Class> loaded = Class.forName(fqcn, false, Utils.class.getClassLoader());
+ if (!Type.class.isAssignableFrom(loaded)) {
+ throw new UnsupportedOperationException(
+ "Resolved class is not a subtype of " + Type.class.getName()
+ + ": " + fqcn);
+ }
+ return (Class) loaded;
+ }
+
static String getTypeName(TypeReference typeReference) {
try {
java.lang.reflect.Type reflectedType = typeReference.getType();
@@ -45,8 +80,13 @@ static String getTypeName(TypeReference typeReference) {
if (reflectedType instanceof ParameterizedType) {
type = (Class>) ((ParameterizedType) reflectedType).getRawType();
return getParameterizedTypeName(typeReference, type);
+ } else if (typeReference.getSubTypeReference() != null) {
+ return getParameterizedTypeName(typeReference, typeReference.getClassType());
} else {
- type = Class.forName(reflectedType.getTypeName());
+ type = safeLoadTypeClass(getTypeName(reflectedType));
+ if (StructType.class.isAssignableFrom(type)) {
+ return getStructType(type);
+ }
return getSimpleTypeName(type);
}
} catch (ClassNotFoundException e) {
@@ -54,6 +94,115 @@ static String getTypeName(TypeReference typeReference) {
}
}
+ /** 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);
+ }
+ }
+
+ public static String getStructType(Class type) {
+ final StringBuilder sb = new StringBuilder("(");
+ Constructor constructor = findStructConstructor(type);
+ Class[] itemTypes = constructor.getParameterTypes();
+ for (int i = 0; i < itemTypes.length; ++i) {
+ final Class cls = itemTypes[i];
+ if (StructType.class.isAssignableFrom(cls)) {
+ sb.append(getStructType(cls));
+ } else {
+ Class parameterAnnotation =
+ extractParameterFromAnnotation(constructor.getParameterAnnotations()[i]);
+ if (parameterAnnotation != null) {
+ try {
+ TypeReference typeRef = getTypeReferenceForParameterizedField(cls, parameterAnnotation);
+ sb.append(getTypeName(typeRef));
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(
+ "Failed to build TypeReference for @Parameterized field of type "
+ + cls.getName() + " with element type "
+ + parameterAnnotation.getName(), e);
+ }
+ } else {
+ sb.append(getTypeName(TypeReference.create(cls)));
+ }
+ }
+ if (i < itemTypes.length - 1) {
+ sb.append(",");
+ }
+ }
+ sb.append(")");
+ return sb.toString();
+ }
+
+ public static TypeReference getDynamicArrayTypeReference(Class parameter) {
+ return new TypeReference() {
+ @Override
+ public TypeReference getSubTypeReference() {
+ return TypeReference.create(parameter);
+ }
+ };
+ }
+
+ public static TypeReference getTypeReferenceForParameterizedField(
+ Class fieldType, Class elementType) throws ClassNotFoundException {
+ if (StaticArray.class.isAssignableFrom(fieldType)) {
+ int size = extractStaticArraySize(fieldType);
+ String elementTypeName = getSimpleTypeName(elementType);
+ String solidityType = elementTypeName + "[" + size + "]";
+ return TypeReference.makeTypeReference(solidityType);
+ } else {
+ return getDynamicArrayTypeReference(elementType);
+ }
+ }
+
+ /**
+ * Extracts the array size from a generated {@code StaticArrayN} subclass.
+ * The bare {@link StaticArray} base class carries no size information and is rejected.
+ */
+ private static int extractStaticArraySize(Class> staticArrayClass) {
+ String className = staticArrayClass.getSimpleName();
+ String prefix = "StaticArray";
+ if (!className.startsWith(prefix) || className.length() == prefix.length()) {
+ throw new IllegalArgumentException(
+ "Cannot determine static array size from class " + staticArrayClass.getName()
+ + "; @Parameterized fields must use a generated StaticArrayN subclass.");
+ }
+ String sizeStr = className.substring(prefix.length());
+ try {
+ return Integer.parseInt(sizeStr);
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException(
+ "Invalid static array size suffix in class " + staticArrayClass.getName(), e);
+ }
+ }
+
+ public static Class extractParameterFromAnnotation(
+ Annotation[] parameterAnnotation) {
+ for (Annotation a : parameterAnnotation) {
+ if (Parameterized.class.isInstance(a)) {
+ return (Class) ((Parameterized) a).type();
+ }
+ }
+ return null;
+ }
+
+ public static Constructor findStructConstructor(Class classType) {
+ return Arrays.stream(classType.getDeclaredConstructors())
+ .filter(
+ declaredConstructor ->
+ Arrays.stream(declaredConstructor.getParameterTypes())
+ .allMatch(Type.class::isAssignableFrom))
+ .findAny()
+ .orElseThrow(
+ () ->
+ new RuntimeException(
+ "TypeReferenced struct must contain "
+ + "a constructor with types that extend Type"));
+ }
+
+
static String getSimpleTypeName(Class> type) {
String simpleName = type.getSimpleName().toLowerCase();
@@ -66,6 +215,8 @@ static String getSimpleTypeName(Class> type) {
return "string";
} else if (type.equals(DynamicBytes.class)) {
return "bytes";
+ } else if (StructType.class.isAssignableFrom(type)) {
+ return type.getName();
} else {
return simpleName;
}
@@ -76,16 +227,18 @@ static String getParameterizedTypeName(
try {
if (type.equals(DynamicArray.class)) {
- Class parameterizedType = getParameterizedTypeFromArray(typeReference);
- String parameterizedTypeName = getSimpleTypeName(parameterizedType);
+ String parameterizedTypeName = getArrayElementTypeName(typeReference);
return parameterizedTypeName + "[]";
- } else if (type.equals(StaticArray.class)) {
- Class parameterizedType = getParameterizedTypeFromArray(typeReference);
- String parameterizedTypeName = getSimpleTypeName(parameterizedType);
- return parameterizedTypeName
- + "["
- + ((TypeReference.StaticArrayTypeReference) typeReference).getSize()
- + "]";
+ } else if (StaticArray.class.isAssignableFrom(type)) {
+ String parameterizedTypeName = getArrayElementTypeName(typeReference);
+ final int length;
+ if (TypeReference.StaticArrayTypeReference.class.isAssignableFrom(
+ typeReference.getClass())) {
+ length = ((TypeReference.StaticArrayTypeReference) typeReference).getSize();
+ } else {
+ length = Integer.parseInt(type.getSimpleName().replaceAll("\\D+", ""));
+ }
+ return parameterizedTypeName + "[" + length + "]";
} else {
throw new UnsupportedOperationException("Invalid type provided " + type.getName());
}
@@ -94,16 +247,150 @@ static String getParameterizedTypeName(
}
}
+ @SuppressWarnings("unchecked")
+ private static String getArrayElementTypeName(TypeReference> typeReference)
+ throws ClassNotFoundException {
+ TypeReference> subTypeReference = typeReference.getSubTypeReference();
+ if (subTypeReference != null) {
+ return getTypeName((TypeReference) subTypeReference);
+ }
+
+ java.lang.reflect.Type reflectedType = typeReference.getType();
+ if (!(reflectedType instanceof ParameterizedType)) {
+ throw new UnsupportedOperationException("Invalid array type provided " + reflectedType);
+ }
+
+ java.lang.reflect.Type elementType =
+ ((ParameterizedType) reflectedType).getActualTypeArguments()[0];
+ if (elementType instanceof ParameterizedType) {
+ final java.lang.reflect.Type parameterizedElementType = elementType;
+ return getTypeName(
+ new TypeReference() {
+ @Override
+ public java.lang.reflect.Type getType() {
+ return parameterizedElementType;
+ }
+ });
+ }
+
+ Class> elementClass;
+ if (elementType instanceof Class) {
+ elementClass = (Class>) elementType;
+ } else {
+ elementClass = safeLoadTypeClass(elementType.getTypeName());
+ }
+ if (!Type.class.isAssignableFrom(elementClass)) {
+ throw new UnsupportedOperationException(
+ "Resolved array element is not a subtype of " + Type.class.getName()
+ + ": " + elementType);
+ }
+ return simpleNameOrStruct((Class extends Type>) elementClass);
+ }
+
+ private static String simpleNameOrStruct(Class parameterizedType) {
+ if (StructType.class.isAssignableFrom(parameterizedType)) {
+ return getStructType(parameterizedType);
+ }
+ return getSimpleTypeName(parameterizedType);
+ }
+
+
+ /**
+ * Resolves the element TypeReference for a DynamicArray during decoding.
+ * Prefers the explicit {@code subTypeReference} (set by ABI-JSON path); falls back to
+ * reflecting on {@code getType()}; last resort synthesizes via
+ * {@link #getFullParameterizedTypeFromArray}.
+ */
+ @SuppressWarnings("unchecked")
+ static TypeReference> resolveDynamicArrayElementTypeReference(TypeReference> outerRef)
+ throws ClassNotFoundException {
+ TypeReference> sub = outerRef.getSubTypeReference();
+ if (sub != null) {
+ return sub;
+ }
+ final java.lang.reflect.Type elementType =
+ ((ParameterizedType) outerRef.getType()).getActualTypeArguments()[0];
+ if (elementType instanceof ParameterizedType) {
+ return new TypeReference() {
+ @Override
+ public java.lang.reflect.Type getType() {
+ return elementType;
+ }
+ };
+ }
+ return getDynamicArrayTypeReference(getFullParameterizedTypeFromArray(outerRef));
+ }
+
+ /**
+ * Resolves the inner element TypeReference for a StaticArray during decoding,
+ * drilling two levels (outer-of-static-array, then element-of-static-array).
+ */
+ @SuppressWarnings("unchecked")
+ static TypeReference> resolveStaticArrayInnerTypeReference(TypeReference> outerRef) {
+ TypeReference> sub = outerRef.getSubTypeReference();
+ if (sub != null && sub.getSubTypeReference() != null) {
+ return sub.getSubTypeReference();
+ }
+ final java.lang.reflect.Type elementType =
+ ((ParameterizedType) outerRef.getType()).getActualTypeArguments()[0];
+ final java.lang.reflect.Type innerReflectType =
+ ((ParameterizedType) elementType).getActualTypeArguments()[0];
+ if (innerReflectType instanceof ParameterizedType) {
+ return new TypeReference() {
+ @Override
+ public java.lang.reflect.Type getType() {
+ return innerReflectType;
+ }
+ };
+ }
+ return TypeReference.create((Class) innerReflectType);
+ }
+
@SuppressWarnings("unchecked")
static Class getParameterizedTypeFromArray(TypeReference typeReference)
throws ClassNotFoundException {
+ if (typeReference.getSubTypeReference() != null) {
+ return typeReference.getSubTypeReference().getClassType();
+ }
+
+
java.lang.reflect.Type type = typeReference.getType();
java.lang.reflect.Type[] typeArguments =
((ParameterizedType) type).getActualTypeArguments();
- String parameterizedTypeName = typeArguments[0].getTypeName();
- return (Class) Class.forName(parameterizedTypeName);
+ if (typeArguments[0] instanceof ParameterizedType) {
+ return safeLoadTypeClass(
+ getTypeName(((ParameterizedType) typeArguments[0]).getRawType()));
+ }
+
+ return safeLoadTypeClass(typeArguments[0].getTypeName());
+ }
+
+ static Class getFullParameterizedTypeFromArray(TypeReference typeReference)
+ throws ClassNotFoundException {
+
+ TypeReference> subRef = typeReference.getSubTypeReference();
+ if (subRef != null && subRef.getSubTypeReference() != null) {
+ return subRef.getSubTypeReference().getClassType();
+ }
+
+ java.lang.reflect.Type type = typeReference.getType();
+
+ java.lang.reflect.Type typeArgument =
+ ((ParameterizedType) type).getActualTypeArguments()[0];
+
+ java.lang.reflect.Type innerType =
+ ((ParameterizedType) typeArgument).getActualTypeArguments()[0];
+
+ // For 3D+ arrays, innerType may be a ParameterizedType (e.g. DynamicArray).
+ // Use getRawType() to extract only the class name without generic parameters,
+ // since Class.forName() cannot parse parameterized type strings.
+ if (innerType instanceof ParameterizedType) {
+ return safeLoadTypeClass(
+ getTypeName(((ParameterizedType) innerType).getRawType()));
+ }
+ return safeLoadTypeClass(innerType.getTypeName());
}
@SuppressWarnings("unchecked")
@@ -156,4 +443,130 @@ public static > List typeMap(List input, Class des
}
return result;
}
+
+ /**
+ * Returns flat list of canonical fields in a static struct. Example: struct Baz { Struct Bar {
+ * int a, int b }, int c } will return {a, b, c}.
+ *
+ * @param classType Static struct type
+ * @return Flat list of canonical fields in a nested struct
+ */
+ public static List staticStructNestedPublicFieldsFlatList(Class classType) {
+ return staticStructsNestedFieldsFlatList(classType).stream()
+ .filter(field -> Modifier.isPublic(field.getModifiers()))
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Goes over a static structs and enumerates all of its fields and nested structs fields
+ * recursively.
+ *
+ * @param classType Static struct type
+ * @return Flat list of all the fields nested in the struct
+ */
+ @SuppressWarnings("unchecked")
+ public static List staticStructsNestedFieldsFlatList(Class classType) {
+ List canonicalFields =
+ Arrays.stream(classType.getDeclaredFields())
+ .filter(field -> !StaticStruct.class.isAssignableFrom(field.getType()))
+ .collect(Collectors.toList());
+ List nestedFields =
+ Arrays.stream(classType.getDeclaredFields())
+ .filter(field -> StaticStruct.class.isAssignableFrom(field.getType()))
+ .map(
+ field ->
+ staticStructsNestedFieldsFlatList(
+ (Class) field.getType()))
+ .flatMap(Collection::stream)
+ .collect(Collectors.toList());
+ return Stream.concat(canonicalFields.stream(), nestedFields.stream())
+ .collect(Collectors.toList());
+ }
+
+ /** Support java version < 8 Copied from {@link Class#getTypeName()}. */
+ private static String getClassName(Class type) {
+ if (type.isArray()) {
+ try {
+ Class> cl = type;
+ int dimensions = 0;
+ while (cl.isArray()) {
+ dimensions++;
+ cl = cl.getComponentType();
+ }
+ StringBuilder sb = new StringBuilder();
+ sb.append(cl.getName());
+ for (int i = 0; i < dimensions; i++) {
+ sb.append("[]");
+ }
+ return sb.toString();
+ } catch (Throwable e) {
+ /*FALLTHRU*/
+ }
+ }
+
+ return type.getName();
+ }
+
+ /**
+ * Upper bound on TypeReference graph depth accepted by the decoder.
+ * Real ABIs rarely exceed 6-8 levels of nesting (e.g. DeFi aggregator structs);
+ * 10 covers realistic use with a small margin while rejecting obviously
+ * malicious or malformed inputs quickly. Bump if a legitimate case trips this.
+ */
+ private static final int MAX_TYPEREF_DEPTH = 10;
+
+ /**
+ * Validates a TypeReference graph before decoding: rejects cycles on the
+ * current traversal path via identity comparison and caps the maximum depth at
+ * {@link #MAX_TYPEREF_DEPTH}. Walked
+ * iteratively (explicit stack) so this method itself cannot blow the JVM stack
+ * on malicious input.
+ *
+ * Called once at the decoder entry; bounds every downstream recursive
+ * traversal of subTypeReference / innerTypes (in {@code TypeDecoder.isDynamic},
+ * {@code buildTypeStringFromTypeReference}, {@code decodeStaticStruct},
+ * {@code decodeDynamicStruct}, etc.). All local state, no shared mutable
+ * fields — safe for concurrent invocation on the same TypeReference.
+ */
+ public static void validateTypeReferenceDepth(TypeReference> root) {
+ if (root == null) {
+ return;
+ }
+ Deque