diff --git a/abi/build.gradle b/abi/build.gradle index 3541772c..c9ac2bd3 100644 --- a/abi/build.gradle +++ b/abi/build.gradle @@ -6,4 +6,5 @@ description 'TRON Application Binary Interface (ABI) for working with smart cont dependencies { implementation project(':utils') + testImplementation "com.fasterxml.jackson.core:jackson-databind:2.18.6" } diff --git a/abi/src/main/java/org/tron/trident/abi/CustomErrorEncoder.java b/abi/src/main/java/org/tron/trident/abi/CustomErrorEncoder.java new file mode 100644 index 00000000..315d31e3 --- /dev/null +++ b/abi/src/main/java/org/tron/trident/abi/CustomErrorEncoder.java @@ -0,0 +1,43 @@ +package org.tron.trident.abi; + +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.stream.Collectors; +import org.tron.trident.abi.datatypes.CustomError; +import org.tron.trident.abi.datatypes.Type; +import org.tron.trident.crypto.Hash; +import org.tron.trident.utils.Numeric; + +/** + * Ethereum custom error encoding. Further limited details are available here. + */ +public class CustomErrorEncoder { + + private CustomErrorEncoder() { + } + + public static String encode(CustomError error) { + return calculateSignatureHash( + buildErrorSignature(error.getName(), error.getParameters())); + } + + static String buildErrorSignature( + String errorName, List> parameters) { + + StringBuilder result = new StringBuilder(); + result.append(errorName); + result.append("("); + String params = + parameters.stream().map(Utils::getTypeName).collect(Collectors.joining(",")); + result.append(params); + result.append(")"); + return result.toString(); + } + + public static String calculateSignatureHash(String errorSignature) { + byte[] input = errorSignature.getBytes(StandardCharsets.UTF_8); + byte[] hash = Hash.sha3(input); + return Numeric.toHexString(hash).substring(2); + } +} diff --git a/abi/src/main/java/org/tron/trident/abi/DefaultFunctionEncoder.java b/abi/src/main/java/org/tron/trident/abi/DefaultFunctionEncoder.java index cb79fd8d..cfe4069f 100644 --- a/abi/src/main/java/org/tron/trident/abi/DefaultFunctionEncoder.java +++ b/abi/src/main/java/org/tron/trident/abi/DefaultFunctionEncoder.java @@ -17,6 +17,7 @@ import java.util.List; import org.tron.trident.abi.datatypes.Function; 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.Uint; @@ -64,11 +65,53 @@ private static String encodeParameters( return result.toString(); } + /** + * Encodes a function call including its method signature (selector) and its parameters. + * + * @param methodId the 4-byte selector (8 hex chars) + * @param parameters the list of parameters to encode + * @return the complete ABI-encoded hex string representing the function call + */ + public String encodeWithSelector(String methodId, List parameters) { + final StringBuilder result = new StringBuilder(methodId); + + return encodeParameters(parameters, result); + } + + /** + * Encodes parameters using tight packing (abi.encodePacked). + * This is a non-standard ABI encoding used primarily for computing hashes, + * where padding is omitted and dynamic types are concatenated without length prefixes. + * + * @param parameters the list of parameters to pack + * @return the packed ABI-encoded hex string + */ + @Override + protected String encodePackedParameters(List parameters) { + final StringBuilder result = new StringBuilder(); + for (Type parameter : parameters) { + result.append(TypeEncoder.encodePacked(parameter)); + } + return result.toString(); + } + + /** + * Calculates the length of the tuple head (in 32-byte slots) required for the given parameters. + * Crucially, all dynamic types (including StaticArrays containing dynamic elements) always occupy exactly + * 1 slot in the head (for the offset pointer). Pure static arrays and structs are flattened to calculate + * their total inline slot requirement. + * + * @param parameters the list of types to be encoded. + * @return the total number of 32-byte slots required for the tuple head. + */ + @SuppressWarnings("unchecked") private static int getLength(final List parameters) { int count = 0; for (final Type type : parameters) { - if (type instanceof StaticArray) { - count += ((StaticArray) type).getValue().size(); + if (TypeEncoder.isDynamic(type)) { + count++; + } else if (type instanceof StaticArray || type instanceof StaticStruct) { + count += type.bytes32PaddedLength() / Type.MAX_BYTE_LENGTH; } else { count++; } diff --git a/abi/src/main/java/org/tron/trident/abi/DefaultFunctionReturnDecoder.java b/abi/src/main/java/org/tron/trident/abi/DefaultFunctionReturnDecoder.java index f2a03bcf..94e0181b 100644 --- a/abi/src/main/java/org/tron/trident/abi/DefaultFunctionReturnDecoder.java +++ b/abi/src/main/java/org/tron/trident/abi/DefaultFunctionReturnDecoder.java @@ -13,6 +13,9 @@ package org.tron.trident.abi; +import static org.tron.trident.abi.TypeDecoder.MAX_BYTE_LENGTH_FOR_HEX_STRING; +import static org.tron.trident.abi.TypeDecoder.isDynamic; + import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -20,7 +23,6 @@ import org.tron.trident.abi.datatypes.Bytes; import org.tron.trident.abi.datatypes.BytesType; 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.StaticArray; import org.tron.trident.abi.datatypes.StaticStruct; @@ -31,7 +33,7 @@ import org.tron.trident.utils.Strings; /** - * Ethereum Contract Application Binary Interface (ABI) encoding for functions. Further details are + * Ethereum Contract Application Binary Interface (ABI) decoding for functions. Further details are * available here. */ public class DefaultFunctionReturnDecoder extends FunctionReturnDecoder { @@ -73,60 +75,63 @@ public Type decodeEventParameter( } private static List build(String input, List> outputParameters) { + // Reject cyclic or excessively nested TypeReference graphs up front; any + // downstream recursion through subTypeReference / innerTypes is then bounded + // by what passed validation here. + for (TypeReference typeReference : outputParameters) { + Utils.validateTypeReferenceDepth(typeReference); + } + List results = new ArrayList<>(outputParameters.size()); int offset = 0; for (TypeReference typeReference : outputParameters) { try { + int hexStringDataOffset = getDataOffset(input, offset, typeReference); + @SuppressWarnings("unchecked") Class classType = (Class) typeReference.getClassType(); - int hexStringDataOffset = getDataOffset(input, offset, classType); - Type result; if (DynamicStruct.class.isAssignableFrom(classType)) { - if (outputParameters.size() != 1) { - throw new UnsupportedOperationException( - "Multiple return objects containing a struct is not supported"); - } result = - TypeDecoder.decodeDynamicStruct( - input, hexStringDataOffset, typeReference); - offset += TypeDecoder.MAX_BYTE_LENGTH_FOR_HEX_STRING; + TypeDecoder.decodeDynamicStruct( + input, hexStringDataOffset, typeReference); + offset += MAX_BYTE_LENGTH_FOR_HEX_STRING; } else if (DynamicArray.class.isAssignableFrom(classType)) { result = - TypeDecoder.decodeDynamicArray( - input, hexStringDataOffset, typeReference); - offset += TypeDecoder.MAX_BYTE_LENGTH_FOR_HEX_STRING; - - } else if (typeReference instanceof TypeReference.StaticArrayTypeReference) { - int length = ((TypeReference.StaticArrayTypeReference) typeReference).getSize(); - result = - TypeDecoder.decodeStaticArray( - input, hexStringDataOffset, typeReference, length); - offset += length * TypeDecoder.MAX_BYTE_LENGTH_FOR_HEX_STRING; + TypeDecoder.decodeDynamicArray( + input, hexStringDataOffset, typeReference); + offset += MAX_BYTE_LENGTH_FOR_HEX_STRING; } else if (StaticStruct.class.isAssignableFrom(classType)) { result = - TypeDecoder.decodeStaticStruct( - input, hexStringDataOffset, typeReference); - offset += - classType.getDeclaredFields().length * TypeDecoder.MAX_BYTE_LENGTH_FOR_HEX_STRING; + TypeDecoder.decodeStaticStruct( + input, hexStringDataOffset, typeReference); + offset += (result.bytes32PaddedLength() / Type.MAX_BYTE_LENGTH) + * MAX_BYTE_LENGTH_FOR_HEX_STRING; + } else if (StaticArray.class.isAssignableFrom(classType)) { - int length = - Integer.parseInt( - classType - .getSimpleName() - .substring(StaticArray.class.getSimpleName().length())); + int length; + if (typeReference instanceof TypeReference.StaticArrayTypeReference) { + length = ((TypeReference.StaticArrayTypeReference) typeReference).getSize(); + } else { + length = Utils.extractStaticArraySize(classType); + } result = - TypeDecoder.decodeStaticArray( - input, hexStringDataOffset, typeReference, length); - offset += length * TypeDecoder.MAX_BYTE_LENGTH_FOR_HEX_STRING; - + TypeDecoder.decodeStaticArray( + input, hexStringDataOffset, typeReference, length); + if (isDynamic(typeReference)) { + offset += MAX_BYTE_LENGTH_FOR_HEX_STRING; + } else { + offset += + (result.bytes32PaddedLength() / Type.MAX_BYTE_LENGTH) + * MAX_BYTE_LENGTH_FOR_HEX_STRING; + } } else { result = TypeDecoder.decode(input, hexStringDataOffset, classType); - offset += TypeDecoder.MAX_BYTE_LENGTH_FOR_HEX_STRING; + offset += MAX_BYTE_LENGTH_FOR_HEX_STRING; } results.add(result); @@ -137,10 +142,10 @@ private static List build(String input, List> outputPa return results; } - private static int getDataOffset(String input, int offset, Class type) { - if (DynamicBytes.class.isAssignableFrom(type) - || Utf8String.class.isAssignableFrom(type) - || DynamicArray.class.isAssignableFrom(type)) { + public static int getDataOffset( + String input, int offset, TypeReference typeReference) + throws ClassNotFoundException { + if (isDynamic(typeReference)) { return TypeDecoder.decodeUintAsInt(input, offset) << 1; } else { return offset; diff --git a/abi/src/main/java/org/tron/trident/abi/FunctionEncoder.java b/abi/src/main/java/org/tron/trident/abi/FunctionEncoder.java index 4e326d13..2ff2fdbf 100644 --- a/abi/src/main/java/org/tron/trident/abi/FunctionEncoder.java +++ b/abi/src/main/java/org/tron/trident/abi/FunctionEncoder.java @@ -13,6 +13,9 @@ package org.tron.trident.abi; +import static org.tron.trident.abi.TypeDecoder.instantiateType; +import static org.tron.trident.abi.TypeReference.makeTypeReference; + import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Iterator; @@ -32,19 +35,35 @@ * @see DefaultFunctionEncoder * @see FunctionEncoderProvider */ + public abstract class FunctionEncoder { - private static FunctionEncoder DEFAULT_ENCODER; + private static final FunctionEncoder FUNCTION_ENCODER; - private static final ServiceLoader loader = - ServiceLoader.load(FunctionEncoderProvider.class); + static { + ServiceLoader loader = + ServiceLoader.load(FunctionEncoderProvider.class); + final Iterator iterator = loader.iterator(); + + FUNCTION_ENCODER = + iterator.hasNext() ? iterator.next().get() : new DefaultFunctionEncoder(); + } public static String encode(final Function function) { - return encoder().encodeFunction(function); + return FUNCTION_ENCODER.encodeFunction(function); + } + + /** Encode function when we know function method Id / Selector. */ + public static String encode(final String methodId, final List parameters) { + return FUNCTION_ENCODER.encodeWithSelector(methodId, parameters); } public static String encodeConstructor(final List parameters) { - return encoder().encodeParameters(parameters); + return FUNCTION_ENCODER.encodeParameters(parameters); + } + + public static String encodeConstructorPacked(final List parameters) { + return FUNCTION_ENCODER.encodePackedParameters(parameters); } public static Function makeFunction( @@ -52,16 +71,19 @@ public static Function makeFunction( List solidityInputTypes, List arguments, List solidityOutputTypes) - throws ClassNotFoundException, NoSuchMethodException, InstantiationException, - IllegalAccessException, InvocationTargetException { + throws ClassNotFoundException, + NoSuchMethodException, + InstantiationException, + IllegalAccessException, + InvocationTargetException { List encodedInput = new ArrayList<>(); Iterator argit = arguments.iterator(); for (String st : solidityInputTypes) { - encodedInput.add(TypeDecoder.instantiateType(st, argit.next())); + encodedInput.add(instantiateType(st, argit.next())); } List> encodedOutput = new ArrayList<>(); for (String st : solidityOutputTypes) { - encodedOutput.add(TypeReference.makeTypeReference(st)); + encodedOutput.add(makeTypeReference(st)); } return new Function(fnname, encodedInput, encodedOutput); } @@ -70,6 +92,32 @@ public static Function makeFunction( protected abstract String encodeParameters(List parameters); + /** + * Encodes parameters prefixed with the given selector. Not part of the original + * subclass contract: implementations predating it inherit this throwing default, + * so the pre-existing entry points keep working and only the newer + * {@link #encode(String, List)} fails, with a clear message. + * + * @param methodId Callback selector / Abi method Id (Hex format) + */ + protected String encodeWithSelector( + final String methodId, final List parameters) { + throw new UnsupportedOperationException( + "encodeWithSelector is not implemented by " + getClass().getName() + + "; override it to support FunctionEncoder.encode(methodId, parameters)"); + } + + /** + * Encodes parameters using tight packing (abi.encodePacked). Not part of the + * original subclass contract; see {@link #encodeWithSelector(String, List)}. + */ + protected String encodePackedParameters(List parameters) { + throw new UnsupportedOperationException( + "encodePackedParameters is not implemented by " + getClass().getName() + + "; override it to support " + + "FunctionEncoder.encodeConstructorPacked(parameters)"); + } + protected static String buildMethodSignature( final String methodName, final List parameters) { @@ -88,16 +136,4 @@ protected static String buildMethodId(final String methodSignature) { final byte[] hash = Hash.sha3(input); return Numeric.toHexString(hash).substring(2, 10); } - - private static FunctionEncoder encoder() { - final Iterator iterator = loader.iterator(); - return iterator.hasNext() ? iterator.next().get() : defaultEncoder(); - } - - private static FunctionEncoder defaultEncoder() { - if (DEFAULT_ENCODER == null) { - DEFAULT_ENCODER = new DefaultFunctionEncoder(); - } - return DEFAULT_ENCODER; - } } diff --git a/abi/src/main/java/org/tron/trident/abi/FunctionReturnDecoder.java b/abi/src/main/java/org/tron/trident/abi/FunctionReturnDecoder.java index e07bd048..9f764842 100644 --- a/abi/src/main/java/org/tron/trident/abi/FunctionReturnDecoder.java +++ b/abi/src/main/java/org/tron/trident/abi/FunctionReturnDecoder.java @@ -13,10 +13,13 @@ package org.tron.trident.abi; +import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.ServiceLoader; +import org.tron.trident.abi.datatypes.Address; +import org.tron.trident.abi.datatypes.DynamicBytes; import org.tron.trident.abi.datatypes.Type; import org.tron.trident.abi.spi.FunctionReturnDecoderProvider; @@ -32,10 +35,15 @@ */ public abstract class FunctionReturnDecoder { - private static FunctionReturnDecoder DEFAULT_DECODER; + private static final FunctionReturnDecoder decoder; - private static final ServiceLoader loader = - ServiceLoader.load(FunctionReturnDecoderProvider.class); + static { + ServiceLoader loader = + ServiceLoader.load(FunctionReturnDecoderProvider.class); + final Iterator iterator = loader.iterator(); + + decoder = iterator.hasNext() ? iterator.next().get() : new DefaultFunctionReturnDecoder(); + } /** * Decode ABI encoded return values from smart contract function call. @@ -46,9 +54,35 @@ public abstract class FunctionReturnDecoder { * invalid response */ public static List decode(String rawInput, List> outputParameters) { - return decoder().decodeFunctionResult(rawInput, outputParameters); + return decoder.decodeFunctionResult(rawInput, outputParameters); + } + + /** + * Decode ABI encoded return value DynamicBytes from smart contract function call. + * + * @param rawInput ABI encoded input + * @return the raw {@code byte[]} value of the decoded {@link DynamicBytes}, null if invalid + * response + */ + public static byte[] decodeDynamicBytes(String rawInput) { + List outputParameters = new ArrayList>(); + outputParameters.add(new TypeReference() {}); + + List typeList = decoder.decodeFunctionResult(rawInput, outputParameters); + + return typeList.isEmpty() ? null : ((DynamicBytes) typeList.get(0)).getValue(); + } + + public static String decodeAddress(String rawInput) { + List outputParameters = new ArrayList>(); + outputParameters.add(new TypeReference
() {}); + + List typeList = decoder.decodeFunctionResult(rawInput, outputParameters); + + return typeList.isEmpty() ? null : ((Address) typeList.get(0)).getValue(); } + /** * Decodes an indexed parameter associated with an event. Indexed parameters are individually * encoded, unlike non-indexed parameters which are encoded as per ABI-encoded function @@ -73,7 +107,7 @@ public static List decode(String rawInput, List> outpu */ public static Type decodeIndexedValue( String rawInput, TypeReference typeReference) { - return decoder().decodeEventParameter(rawInput, typeReference); + return decoder.decodeEventParameter(rawInput, typeReference); } protected abstract List decodeFunctionResult( @@ -82,15 +116,4 @@ protected abstract List decodeFunctionResult( protected abstract Type decodeEventParameter( String rawInput, TypeReference typeReference); - private static FunctionReturnDecoder decoder() { - final Iterator 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..43c5a147 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); @@ -75,6 +93,16 @@ public static Type instantiateType(TypeReference ref, Object value) return instantiateAtomicType(rc, value); } + /** + * Broken as inherited from web3j — every call throws. {@code getRawType().getClass()} + * below always yields {@code java.lang.Class} (the class of the Class object, not the + * raw type itself), so both {@code isAssignableFrom} branches are unreachable and any + * input falls through to {@code UnsupportedOperationException}. Nothing in this SDK + * calls this overload; use {@link #decodeStaticArray} / {@link #decodeDynamicArray} + * directly instead. Kept unfixed to stay aligned with upstream; the fix would be + * {@code (Class) ...getRawType()} plus end-to-end verification of the StaticArray + * branch (note its hardcoded length argument of 1). + */ public static T decode( String input, int offset, TypeReference typeReference) { Class cls = ((ParameterizedType) typeReference.getType()).getRawType().getClass(); @@ -93,9 +121,14 @@ public static T decode( @SuppressWarnings("unchecked") static T decode(String input, int offset, Class type) { if (NumericType.class.isAssignableFrom(type)) { - return (T) decodeNumeric(input.substring(offset), (Class) type); + return (T) decodeNumeric( + input.substring( + offset, Math.min(input.length(), offset + MAX_BYTE_LENGTH_FOR_HEX_STRING)), + (Class) type); } else if (Address.class.isAssignableFrom(type)) { - return (T) decodeAddress(input.substring(offset)); + return (T) decodeAddress( + input.substring( + offset, Math.min(input.length(), offset + MAX_BYTE_LENGTH_FOR_HEX_STRING))); } else if (Bool.class.isAssignableFrom(type)) { return (T) decodeBool(input, offset); } else if (Bytes.class.isAssignableFrom(type)) { @@ -116,6 +149,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 +162,22 @@ 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); + if (inputByteArray.length < Type.MAX_BYTE_LENGTH) { + throw new IndexOutOfBoundsException( + "Input is too short to decode " + type.getSimpleName() + ": needs " + + Type.MAX_BYTE_LENGTH + " bytes, found " + + inputByteArray.length); + } + byte[] slice = + Arrays.copyOfRange(inputByteArray, valueOffset, Type.MAX_BYTE_LENGTH); - 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 +215,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,14 +229,14 @@ 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 = ref instanceof TypeReference.StaticArrayTypeReference ? ((TypeReference.StaticArrayTypeReference) ref).getSize() : -1; - if (arraySize <= 0) { + if (arraySize < 0) { listcons = DynamicArray.class.getConstructor(Class.class, List.class); } else { Class arrayClass = @@ -207,8 +253,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 +299,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 +307,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 +383,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 +402,66 @@ 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 { + return Utils.extractStaticArraySize(typeReference.getClassType()); + } 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,38 +469,21 @@ 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 - .getDeclaredFields()[i] - .getType() - .getConstructors()[0] - .getParameters() - .length - * 64; - value = - decodeStaticStruct( - input.substring(currOffset, currOffset + nestedStructLength), - 0, - TypeReference.create(declaredField)); - currOffset += nestedStructLength; + value = decodeStaticStruct(input, currOffset, TypeReference.create(declaredField)); + currOffset += value.bytes32PaddedLength() * 2; + } else if (StaticArray.class.isAssignableFrom(declaredField)) { + value = decodeStaticArrayStructField( + input, currOffset, classType, constructor, i, declaredField); + currOffset += value.bytes32PaddedLength() * 2; } else { value = decode(input.substring(currOffset, currOffset + 64), 0, declaredField); currOffset += 64; @@ -399,31 +491,68 @@ 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); } } + /** + * Decodes a {@code StaticArrayN}-typed struct field on the constructor-reflection + * path. The element type comes from the {@code @Parameterized} annotation on the + * constructor parameter — the same contract signature generation + * ({@code Utils.getStructType}) relies on. + */ + @SuppressWarnings("unchecked") + private static T decodeStaticArrayStructField( + final String input, + final int offset, + final Class structClass, + final Constructor constructor, + final int parameterIndex, + final Class declaredField) throws ClassNotFoundException { + final Class elementType = + Utils.extractParameterFromAnnotation( + constructor.getParameterAnnotations()[parameterIndex]); + if (elementType == null) { + throw new UnsupportedOperationException( + "Array-typed struct field " + declaredField.getSimpleName() + + " of " + structClass.getName() + + " requires the @Parameterized annotation on the constructor parameter" + + " to declare its element type"); + } + if (isDynamic(elementType)) { + throw new UnsupportedOperationException( + "Field " + declaredField.getSimpleName() + " of " + structClass.getName() + + " is a static array of dynamic elements, which makes the struct ABI-dynamic;" + + " the struct class must extend DynamicStruct, not StaticStruct"); + } + TypeReference arrayRef = + (TypeReference) + Utils.getTypeReferenceForParameterizedField(declaredField, elementType); + int size = ((TypeReference.StaticArrayTypeReference) arrayRef).getSize(); + return decodeStaticArray(input, offset, arrayRef, size); + } + + @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 +574,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, beginIndex, 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, beginIndex, 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 +716,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; @@ -484,35 +725,38 @@ private static T decodeDynamicStructElements( final Class declaredField = (Class) constructor.getParameterTypes()[i]; final T value; final int beginIndex = offset + staticOffset; - if (isDynamic(declaredField)) { - final boolean isOnlyParameterInStruct = length == 1; + if (isDynamicStructField(constructor, i)) { final int parameterOffset = - isOnlyParameterInStruct - ? offset - : decodeDynamicStructDynamicParameterOffset( - input.substring(beginIndex, beginIndex + 64)); + decodeDynamicStructDynamicParameterOffset( + input.substring(beginIndex, beginIndex + 64)) + + offset; parameterOffsets.add(parameterOffset); staticOffset += 64; } else { if (StaticStruct.class.isAssignableFrom(declaredField)) { value = decodeStaticStruct( - input.substring(beginIndex), - 0, + input, + beginIndex, TypeReference.create(declaredField)); + staticOffset += value.bytes32PaddedLength() * 2; + } else if (StaticArray.class.isAssignableFrom(declaredField)) { + value = decodeStaticArrayStructField( + input, beginIndex, classType, constructor, i, declaredField); + staticOffset += value.bytes32PaddedLength() * 2; } else { - value = decode(input.substring(beginIndex), 0, declaredField); + value = decode(input, beginIndex, declaredField); + staticOffset += value.bytes32PaddedLength() * 2; } parameters.put(i, value); - staticOffset += value.bytes32PaddedLength() * 2; } } int dynamicParametersProcessed = 0; int dynamicParametersToProcess = - getDynamicStructDynamicParametersCount(constructor.getParameterTypes()); + getDynamicStructDynamicParametersCount(constructor); for (int i = 0; i < length; ++i) { final Class declaredField = (Class) constructor.getParameterTypes()[i]; - if (isDynamic(declaredField)) { + if (isDynamicStructField(constructor, i)) { final boolean isLastParameterInStruct = dynamicParametersProcessed == (dynamicParametersToProcess - 1); final int parameterLength = @@ -521,18 +765,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,30 +790,107 @@ 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); } } + private static int getDynamicStructDynamicParametersCount(final Constructor constructor) { + int count = 0; + for (int i = 0; i < constructor.getParameterCount(); i++) { + if (isDynamicStructField(constructor, i)) { + count++; + } + } + return count; + } + + /** + * ABI-dynamic check for a constructor-reflected struct field. A field is dynamic if its + * declared class is dynamic, or if it is a {@code StaticArrayN} whose {@code @Parameterized} + * element type is dynamic (e.g. {@code string[2]}) — such arrays are encoded with a head + * offset and tail data, exactly like any other dynamic field. + */ @SuppressWarnings("unchecked") - private static int getDynamicStructDynamicParametersCount( - final Class[] cls) { - return (int) Arrays.stream(cls).filter(c -> isDynamic((Class) c)).count(); + private static boolean isDynamicStructField( + final Constructor constructor, final int parameterIndex) { + final Class declaredField = + (Class) constructor.getParameterTypes()[parameterIndex]; + if (isDynamic(declaredField)) { + return true; + } + // Struct classes extend StaticArray for encoding purposes but are not arrays; + // a StaticStruct field is static by definition. + if (StaticArray.class.isAssignableFrom(declaredField) + && !StructType.class.isAssignableFrom(declaredField)) { + final Class elementType = + Utils.extractParameterFromAnnotation( + constructor.getParameterAnnotations()[parameterIndex]); + return elementType != null && isDynamic(elementType); + } + return false; } 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, + // Same shape as getDynamicArrayTypeReference for a DynamicArray field, + // but routed through the nested-array rejection guard. + Utils.getTypeReferenceForParameterizedField(declaredField, parameter)); + } else if (StaticArray.class.isAssignableFrom(declaredField)) { + // StaticArrayN of dynamic elements (e.g. string[2]): ABI-dynamic, decoded from its + // tail data. isDynamicStructField only routes here when @Parameterized is present. + final TypeReference arrayRef = + (TypeReference) + Utils.getTypeReferenceForParameterizedField(declaredField, parameter); + final int size = ((TypeReference.StaticArrayTypeReference) arrayRef).getSize(); + value = decodeStaticArray(dynamicElementData, 0, arrayRef, size); + } 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 +898,66 @@ private static T decodeDynamicParameterFromStruct( } private static int decodeDynamicStructDynamicParameterOffset(final String input) { - return (decodeUintAsInt(input, 0) * 2) + 64; + return (decodeUintAsInt(input, 0) * 2); } + /** + * Class-level dynamic check: whether the class ITSELF is one of the four dynamic types. + * Only valid for leaf/element classes — for struct fields or type references, where a + * {@code StaticArrayN} class may hide dynamic elements, use + * {@link #isDynamicStructField(Constructor, int)} (constructor-reflection path) or + * {@link #isDynamic(TypeReference)} (innerTypes path) instead. + */ 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) throws ClassNotFoundException { + Class cls = (Class) typeReference.getClassType(); + if (isDynamic(cls)) { + return true; + } + // Struct classes extend StaticArray/DynamicArray for encoding purposes but are + // not arrays: a StaticStruct is static by definition (dynamic structs are already + // caught by isDynamic(cls) above), so keep them out of the element-type probe. + if (StaticArray.class.isAssignableFrom(cls) + && !StructType.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) { + throw new UnsupportedOperationException( + "Unable to determine element type of static array " + + Utils.getTypeName(typeReference.getType()), + e); + } + } + return false; } static BigInteger asBigInteger(Object arg) { @@ -612,14 +990,15 @@ 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 arrayClass = (Class) Class.forName("org.tron.trident.abi.datatypes.generated.StaticArray" + length); - return (T) arrayClass.getConstructor(List.class).newInstance(elements); + return (T) arrayClass + .getConstructor(Class.class, List.class) + .newInstance(elements.get(0).getClass(), elements); } catch (ReflectiveOperationException e) { throw new UnsupportedOperationException(e); } @@ -631,33 +1010,185 @@ 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 { + // Prefer the size carried by the element's StaticArrayTypeReference: for + // sizes with no generated class (e.g. uint256[33]) cls is the bare + // StaticArray, whose name parses to nothing. + TypeReference elementRef = typeReference.getSubTypeReference(); + int staticLength = + elementRef instanceof TypeReference.StaticArrayTypeReference + ? ((TypeReference.StaticArrayTypeReference) elementRef).getSize() + : Utils.extractStaticArraySize(cls); + 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..ace1f63a 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,12 @@ 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.StructType; import org.tron.trident.abi.datatypes.Type; import org.tron.trident.abi.datatypes.Ufixed; import org.tron.trident.abi.datatypes.Uint; @@ -46,10 +51,24 @@ public class TypeEncoder { private TypeEncoder() { } + /** + * Returns whether the given value is ABI-dynamic, probing StaticArray elements recursively. + */ 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(staticArray.getValue().get(0)); + } + return TypeDecoder.isDynamic(staticArray.getComponentType()); + } + return false; } @SuppressWarnings("unchecked") @@ -67,7 +86,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 +103,105 @@ 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) { + // Structs are not supported by abi.encodePacked: "structs as well as nested + // arrays are not supported" — see + // https://docs.soliditylang.org/en/latest/abi-spec.html#non-standard-packed-mode + // (solc rejects them at compile time with "Type not supported in packed mode."). + // They must be rejected BEFORE the array dispatch below: DynamicStruct + // extends DynamicArray and StaticStruct extends StaticArray, so they would + // otherwise fall into arrayEncodePacked, whose component-type check cannot see + // them (a struct's componentType is Type.class) — silently producing corrupt + // packed bytes instead of an error. + if (parameter instanceof StructType) { + throw new UnsupportedOperationException( + "Type cannot be packed encoded: " + parameter.getClass()); + } + 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) { + // Slices by the stored Uint's bit size, not a fixed 160: addresses built via + // Address(BigInteger)/Address(String) are 160-bit-normalized and yield the canonical + // 20 packed bytes, but Address(Uint) (256-bit by default) yields 32 and + // Address(int bitSize, ...) yields bitSize/8. Kept as-is; the fix would be a fixed + // Address.DEFAULT_LENGTH / 4 slice. See the Address(Uint) constructor Javadoc. + 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, struct3]) = 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 +321,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 +347,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 +398,87 @@ 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(); } + + /** + * Types abi.encodePacked cannot represent as array elements: dynamic types + * (string/bytes), fixed-point types, structs and nested arrays. + */ + private static boolean isPackedForbidden(Class cls) { + return Utf8String.class.isAssignableFrom(cls) + || DynamicStruct.class.isAssignableFrom(cls) + || DynamicArray.class.isAssignableFrom(cls) + || StaticStruct.class.isAssignableFrom(cls) + || StaticArray.class.isAssignableFrom(cls) + || FixedPointType.class.isAssignableFrom(cls) + || DynamicBytes.class.isAssignableFrom(cls); + } + + /** + * 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 (isPackedForbidden(value.getComponentType())) { + return false; + } + // The declared componentType may be a supertype of the elements (e.g. an + // array built with Type.class), which the check above cannot see through; + // validate the actual element types as well. + for (T element : value.getValue()) { + if (isPackedForbidden(element.getClass())) { + 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..2e8d6c4f 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) { @@ -61,10 +69,15 @@ protected TypeReference(boolean indexed) { * * @return the type wrapped by this Array TypeReference, or null if not Array */ - TypeReference getSubTypeReference() { + public 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..0084c860 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,32 @@ 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.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.List; +import java.util.Locale; 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.TrcToken; 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 +48,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 +77,18 @@ 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 if (typeReference.getInnerTypes() != null) { + List> innerTypes = typeReference.getInnerTypes(); + return convert(innerTypes).stream() + .map(Utils::getTypeName) + .collect(Collectors.joining(",", "(", ")")); } 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,8 +96,194 @@ 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 { + // An array-typed field without @Parameterized cannot recover its element + // type by reflection; the generic fallback below would lowercase the class + // simple name into an invalid signature token (e.g. "dynamicarray"), + // silently yielding a wrong function/event selector. + if (StaticArray.class.isAssignableFrom(cls) + || DynamicArray.class.isAssignableFrom(cls)) { + throw new UnsupportedOperationException( + "Missing @Parameterized annotation: constructor parameter " + i + + " of struct " + type.getName() + " is the array type " + + cls.getSimpleName() + ", whose element type cannot be recovered" + + " reflectively. Annotate it with @Parameterized(type =" + + " .class) so a valid ABI signature can be built."); + } + 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); + } + }; + } + + /** + * Builds the element TypeReference for an array-typed struct field declared via + * {@code @Parameterized} on a constructor parameter. + * + *

Limitation: this annotation-based constructor-reflection path represents + * only one element-type level. Nested array fields (e.g. {@code uint256[2][2]} declared + * as {@code StaticArray2>}) are therefore not supported by the + * current implementation and are rejected explicitly. Nested arrays remain supported + * through runtime type references, for example by placing + * {@code TypeReference.makeTypeReference("uint256[2][2]")} in the {@code innerTypes} + * list of a runtime struct reference. + */ + public static TypeReference getTypeReferenceForParameterizedField( + Class fieldType, Class elementType) throws ClassNotFoundException { + // @Parameterized recovers the erased element type of an array field; on a + // non-array field it is meaningless. + if (!StaticArray.class.isAssignableFrom(fieldType) + && !DynamicArray.class.isAssignableFrom(fieldType)) { + throw new UnsupportedOperationException( + "@Parameterized is only applicable to array-typed struct fields: " + + fieldType.getSimpleName() + " is not a StaticArray/DynamicArray" + + " subtype. Remove the annotation from this field."); + } + // Struct classes extend StaticArray/DynamicArray for encoding purposes but are + // legitimate element types; only genuine array-of-array nesting is rejected. + if ((StaticArray.class.isAssignableFrom(elementType) + || DynamicArray.class.isAssignableFrom(elementType)) + && !StructType.class.isAssignableFrom(elementType)) { + throw new UnsupportedOperationException( + "Nested arrays are not supported in constructor-reflected struct classes: " + + "@Parameterized element type " + elementType.getSimpleName() + + " on a " + fieldType.getSimpleName() + " field exceeds the single" + + " element-type level represented by the current @Parameterized-based path." + + " Describe the struct with runtime innerTypes instead, placing e.g." + + " TypeReference.makeTypeReference(\"uint256[2][2]\") in the field list."); + } + if (StaticArray.class.isAssignableFrom(fieldType)) { + int size = extractStaticArraySize(fieldType); + TypeReference elementRef = TypeReference.create(elementType); + // Built directly from the classes at hand, mirroring the reference shape + // makeTypeReference produces. Round-tripping through a Solidity name string + // is lossy for element types whose simple name is not a valid ABI token + // (Fixed/Ufixed, struct classes). + return new TypeReference.StaticArrayTypeReference(size) { + @Override + public TypeReference getSubTypeReference() { + return elementRef; + } + + @Override + public java.lang.reflect.Type getType() { + return new ParameterizedType() { + @Override + public java.lang.reflect.Type[] getActualTypeArguments() { + return new java.lang.reflect.Type[] {elementType}; + } + + @Override + public java.lang.reflect.Type getRawType() { + return fieldType; + } + + @Override + public java.lang.reflect.Type getOwnerType() { + return Class.class; + } + }; + } + }; + } 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. + * Single source of truth for StaticArrayN size parsing — decoder paths that hold a + * {@code StaticArrayTypeReference} should prefer its {@code getSize()} and fall back here. + */ + 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() + + "; expected a generated StaticArrayN subclass (e.g. StaticArray2)."); + } + 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(); + String simpleName = type.getSimpleName().toLowerCase(Locale.ROOT); if (type.equals(Uint.class) || type.equals(Int.class) @@ -66,6 +294,12 @@ static String getSimpleTypeName(Class type) { return "string"; } else if (type.equals(DynamicBytes.class)) { return "bytes"; + } else if (type.equals(TrcToken.class)) { + // TVM extension type; the ABI token is camelCase and AbiTypes' lookup is + // case-sensitive, so the generic toLowerCase() would corrupt it. + return "trcToken"; + } else if (StructType.class.isAssignableFrom(type)) { + return type.getName(); } else { return simpleName; } @@ -76,16 +310,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 = extractStaticArraySize(type); + } + return parameterizedTypeName + "[" + length + "]"; } else { throw new UnsupportedOperationException("Invalid type provided " + type.getName()); } @@ -94,16 +330,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) 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 +526,112 @@ 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 by capping its depth at + * {@link #MAX_TYPEREF_DEPTH}. A cycle keeps increasing the depth along the cyclic + * path, so cycles are rejected by the same check; shared (diamond) nodes at legal + * depths pass. Recursion is bounded by the cap itself, so this method cannot blow + * the JVM stack on malicious input. + * + *

Called from {@code DefaultFunctionReturnDecoder.build} for each output + * parameter (direct {@code TypeDecoder} entry points are not guarded); bounds + * every downstream recursive + * traversal of subTypeReference / innerTypes (in {@code TypeDecoder.isDynamic}, + * {@code Utils.getTypeName}, {@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) { + validateTypeReferenceDepth(root, 0); + } + + private static void validateTypeReferenceDepth(TypeReference node, int depth) { + if (node == null) { + return; + } + if (depth > MAX_TYPEREF_DEPTH) { + throw new UnsupportedOperationException( + "TypeReference depth exceeds " + MAX_TYPEREF_DEPTH + + " — possible cycle or excessively nested type"); + } + validateTypeReferenceDepth(node.getSubTypeReference(), depth + 1); + List> inners = node.getInnerTypes(); + if (inners != null) { + for (TypeReference inner : inners) { + validateTypeReferenceDepth(inner, depth + 1); + } + } + } } diff --git a/abi/src/main/java/org/tron/trident/abi/datatypes/AbiTypes.java b/abi/src/main/java/org/tron/trident/abi/datatypes/AbiTypes.java index a06aa715..8f401141 100644 --- a/abi/src/main/java/org/tron/trident/abi/datatypes/AbiTypes.java +++ b/abi/src/main/java/org/tron/trident/abi/datatypes/AbiTypes.java @@ -13,6 +13,7 @@ package org.tron.trident.abi.datatypes; +import java.util.Locale; import org.tron.trident.abi.datatypes.generated.Bytes1; import org.tron.trident.abi.datatypes.generated.Bytes10; import org.tron.trident.abi.datatypes.generated.Bytes11; @@ -367,18 +368,48 @@ public static Class getType(String type, boolean primitives) { return Bytes32.class; case "trcToken": return TrcToken.class; - default: - throw new UnsupportedOperationException("Unsupported type encountered: " + type); + default: { + try { + // initialize=false: do not trigger static initializers of arbitrary + // classes during reflective lookup. Explicit ClassLoader keeps + // resolution independent of the calling stack. + Class loaded = + Class.forName(type, false, AbiTypes.class.getClassLoader()); + if (!Type.class.isAssignableFrom(loaded)) { + throw new UnsupportedOperationException( + "Unsupported type encountered: " + type + + " (not a subtype of " + Type.class.getName() + ")"); + } + @SuppressWarnings("unchecked") + Class typed = (Class) loaded; + return typed; + } catch (ClassNotFoundException e) { + throw new UnsupportedOperationException( + "Unsupported type encountered: " + type); + } + } } } + /** + * Returns the provided class type as a string. In case of a struct, it will return the struct + * name. For the tuple notation of a struct, example ((string,uint256)), think of taking an + * instance of the struct and calling the instance.getTypeAsString() method. + */ public static String getTypeAString(Class type) { if (Utf8String.class.equals(type)) { return "string"; } else if (DynamicBytes.class.equals(type)) { return "bytes"; + } else if (TrcToken.class.equals(type)) { + return "trcToken"; + } else if (Uint.class.equals(type) + || org.tron.trident.abi.datatypes.Int.class.equals(type) + || Ufixed.class.equals(type) + || Fixed.class.equals(type)) { + return type.getSimpleName().toLowerCase(Locale.ROOT) + "256"; } else { - return type.getSimpleName().toLowerCase(); + return type.getSimpleName().toLowerCase(Locale.ROOT); } } } diff --git a/abi/src/main/java/org/tron/trident/abi/datatypes/Address.java b/abi/src/main/java/org/tron/trident/abi/datatypes/Address.java index ebc4f00e..d51f8994 100644 --- a/abi/src/main/java/org/tron/trident/abi/datatypes/Address.java +++ b/abi/src/main/java/org/tron/trident/abi/datatypes/Address.java @@ -29,6 +29,15 @@ public class Address implements Type { private final Uint value; + /** + * Stores the given {@link Uint} as-is, without normalizing its bit size to 160. + * {@link org.tron.trident.abi.TypeEncoder#encodePacked} slices by the stored bit + * size, so a 256-bit Uint packs to 32 bytes instead of the canonical 20. + * + * @deprecated use {@link #Address(BigInteger)} or {@link #Address(String)}, + * which normalize to 160 bits. + */ + @Deprecated public Address(Uint value) { this.value = value; } diff --git a/abi/src/main/java/org/tron/trident/abi/datatypes/Array.java b/abi/src/main/java/org/tron/trident/abi/datatypes/Array.java index fc451f25..12878018 100644 --- a/abi/src/main/java/org/tron/trident/abi/datatypes/Array.java +++ b/abi/src/main/java/org/tron/trident/abi/datatypes/Array.java @@ -17,6 +17,7 @@ import java.util.Arrays; import java.util.List; import java.util.Objects; +import org.tron.trident.abi.Utils; /** * Fixed size array. @@ -58,8 +59,8 @@ public abstract class Array implements Type> { @Override public int bytes32PaddedLength() { int length = 0; - for (int i = 0; i < value.size(); i++) { - int valueLength = value.get(i).bytes32PaddedLength(); + for (T t : value) { + int valueLength = t.bytes32PaddedLength(); length += valueLength; } return length; @@ -77,11 +78,56 @@ public Class getComponentType() { @Override public abstract String getTypeAsString(); + /** + * Computes the Solidity type string of this array's elements; the dynamic and static + * subclasses append their respective {@code []} / {@code [N]} suffix to it. + */ + String getElementTypeAsString() { + if (value.isEmpty()) { + Class componentType = getComponentType(); + boolean genericStruct = + componentType == DynamicStruct.class || componentType == StaticStruct.class; + boolean rawArrayType = + componentType != null + && Array.class.isAssignableFrom(componentType) + && !StructType.class.isAssignableFrom(componentType); + if (componentType == null || genericStruct || rawArrayType) { + throw new UnsupportedOperationException( + "Cannot determine type string for empty array of generic struct " + + "or nested array type. Either construct the array with a " + + "concrete element type, or compute the type string externally " + + "from a TypeReference."); + } + if (StructType.class.isAssignableFrom(componentType)) { + return Utils.getStructType(componentType); + } + return AbiTypes.getTypeAString(componentType); + } + if (StructType.class.isAssignableFrom(value.get(0).getClass()) + || Array.class.isAssignableFrom(value.get(0).getClass())) { + return value.get(0).getTypeAsString(); + } + return AbiTypes.getTypeAString(getComponentType()); + } + private void checkValid(Class type, List values) { Objects.requireNonNull(type); Objects.requireNonNull(values); } + /** + * Infers the component type from the first array element, for the deprecated subclass + * constructors that take values without an explicit element class. + */ + @SuppressWarnings("unchecked") + static Class inferComponentType(T firstValue) { + if (StructType.class.isAssignableFrom(firstValue.getClass()) + || Array.class.isAssignableFrom(firstValue.getClass())) { + return (Class) firstValue.getClass(); + } + return (Class) AbiTypes.getType(firstValue.getTypeAsString()); + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/abi/src/main/java/org/tron/trident/abi/datatypes/BytesType.java b/abi/src/main/java/org/tron/trident/abi/datatypes/BytesType.java index 172ed35b..82184ca0 100644 --- a/abi/src/main/java/org/tron/trident/abi/datatypes/BytesType.java +++ b/abi/src/main/java/org/tron/trident/abi/datatypes/BytesType.java @@ -30,7 +30,12 @@ public BytesType(byte[] src, String type) { @Override public int bytes32PaddedLength() { - return value.length * 32; + 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; } @Override diff --git a/abi/src/main/java/org/tron/trident/abi/datatypes/CustomError.java b/abi/src/main/java/org/tron/trident/abi/datatypes/CustomError.java new file mode 100644 index 00000000..ee87494e --- /dev/null +++ b/abi/src/main/java/org/tron/trident/abi/datatypes/CustomError.java @@ -0,0 +1,38 @@ +/* + * Copyright 2019 Web3 Labs Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package org.tron.trident.abi.datatypes; + +import static org.tron.trident.abi.Utils.convert; + +import java.util.List; +import org.tron.trident.abi.TypeReference; + +/** CustomError wrapper type. */ +public class CustomError { + private String name; + private List> parameters; + + public CustomError(String name, List> parameters) { + this.name = name; + this.parameters = convert(parameters); + } + + public String getName() { + return name; + } + + public List> getParameters() { + return parameters; + } +} diff --git a/abi/src/main/java/org/tron/trident/abi/datatypes/DynamicArray.java b/abi/src/main/java/org/tron/trident/abi/datatypes/DynamicArray.java index 06e966bf..12ad4734 100644 --- a/abi/src/main/java/org/tron/trident/abi/datatypes/DynamicArray.java +++ b/abi/src/main/java/org/tron/trident/abi/datatypes/DynamicArray.java @@ -20,17 +20,23 @@ */ public class DynamicArray extends Array { + /** + * @deprecated Infers the element type from {@code values[0]}, so passing an empty array + * throws {@link ArrayIndexOutOfBoundsException}. Use {@code DynamicArray(Class, T...)}. + */ @Deprecated @SafeVarargs - @SuppressWarnings({"unchecked"}) public DynamicArray(T... values) { - super((Class) AbiTypes.getType(values[0].getTypeAsString()), values); + super(inferComponentType(values[0]), values); } + /** + * @deprecated Infers the element type from {@code values.get(0)}, so passing an empty list + * throws {@link IndexOutOfBoundsException}. Use {@code DynamicArray(Class, List)}. + */ @Deprecated - @SuppressWarnings("unchecked") public DynamicArray(List values) { - super((Class) AbiTypes.getType(values.get(0).getTypeAsString()), values); + super(inferComponentType(values.get(0)), values); } @Deprecated @@ -60,6 +66,6 @@ public DynamicArray(Class type, T... values) { @Override public String getTypeAsString() { - return AbiTypes.getTypeAString(getComponentType()) + "[]"; + return getElementTypeAsString() + "[]"; } } diff --git a/abi/src/main/java/org/tron/trident/abi/datatypes/DynamicStruct.java b/abi/src/main/java/org/tron/trident/abi/datatypes/DynamicStruct.java index 17803f4b..aa72c5fa 100644 --- a/abi/src/main/java/org/tron/trident/abi/datatypes/DynamicStruct.java +++ b/abi/src/main/java/org/tron/trident/abi/datatypes/DynamicStruct.java @@ -25,6 +25,7 @@ public DynamicStruct(List values) { this(Type.class, values); } + @SuppressWarnings("unchecked") private DynamicStruct(Class type, List values) { super(type, values); for (Type value : values) { @@ -51,7 +52,7 @@ public String getTypeAsString() { final StringBuilder type = new StringBuilder("("); for (int i = 0; i < itemTypes.size(); ++i) { final Class cls = itemTypes.get(i); - if (StructType.class.isAssignableFrom(cls)) { + if (StructType.class.isAssignableFrom(cls) || Array.class.isAssignableFrom(cls)) { type.append(getValue().get(i).getTypeAsString()); } else { type.append(AbiTypes.getTypeAString(cls)); diff --git a/abi/src/main/java/org/tron/trident/abi/datatypes/Event.java b/abi/src/main/java/org/tron/trident/abi/datatypes/Event.java index 84539853..6a1c793f 100644 --- a/abi/src/main/java/org/tron/trident/abi/datatypes/Event.java +++ b/abi/src/main/java/org/tron/trident/abi/datatypes/Event.java @@ -13,10 +13,11 @@ package org.tron.trident.abi.datatypes; +import static org.tron.trident.abi.Utils.convert; + import java.util.List; import java.util.stream.Collectors; import org.tron.trident.abi.TypeReference; -import org.tron.trident.abi.Utils; /** * Event wrapper type. @@ -28,7 +29,7 @@ public class Event { public Event(String name, List> parameters) { this.name = name; - this.parameters = Utils.convert(parameters); + this.parameters = convert(parameters); } public String getName() { diff --git a/abi/src/main/java/org/tron/trident/abi/datatypes/Function.java b/abi/src/main/java/org/tron/trident/abi/datatypes/Function.java index ebb81aca..a018b92a 100644 --- a/abi/src/main/java/org/tron/trident/abi/datatypes/Function.java +++ b/abi/src/main/java/org/tron/trident/abi/datatypes/Function.java @@ -13,13 +13,15 @@ package org.tron.trident.abi.datatypes; +import static org.tron.trident.abi.Utils.convert; + import java.util.List; import org.tron.trident.abi.TypeReference; -import org.tron.trident.abi.Utils; /** * Function type. */ + public class Function { private String name; @@ -30,7 +32,7 @@ public Function( String name, List inputParameters, List> outputParameters) { this.name = name; this.inputParameters = inputParameters; - this.outputParameters = Utils.convert(outputParameters); + this.outputParameters = convert(outputParameters); } public String getName() { diff --git a/abi/src/main/java/org/tron/trident/abi/datatypes/StaticArray.java b/abi/src/main/java/org/tron/trident/abi/datatypes/StaticArray.java index 6f782a3f..a74c7676 100644 --- a/abi/src/main/java/org/tron/trident/abi/datatypes/StaticArray.java +++ b/abi/src/main/java/org/tron/trident/abi/datatypes/StaticArray.java @@ -45,10 +45,15 @@ public StaticArray(List values) { this(values.size(), values); } + /** + * @deprecated Infers the element type from {@code values.get(0)}, so passing an empty list + * throws {@link IndexOutOfBoundsException}. Notably this makes the deprecated + * constructors of {@code StaticArray0} unusable. Use + * {@code StaticArray(Class, int, List)} instead. + */ @Deprecated - @SuppressWarnings("unchecked") public StaticArray(int expectedSize, List values) { - super((Class) AbiTypes.getType(values.get(0).getTypeAsString()), values); + super(inferComponentType(values.get(0)), values); checkValid(expectedSize); } @@ -79,7 +84,7 @@ public List getValue() { @Override public String getTypeAsString() { - return AbiTypes.getTypeAString(getComponentType()) + "[" + value.size() + "]"; + return getElementTypeAsString() + "[" + value.size() + "]"; } private void checkValid(int expectedSize) { diff --git a/abi/src/main/java/org/tron/trident/abi/datatypes/StaticStruct.java b/abi/src/main/java/org/tron/trident/abi/datatypes/StaticStruct.java index 7992d791..5c4e1583 100644 --- a/abi/src/main/java/org/tron/trident/abi/datatypes/StaticStruct.java +++ b/abi/src/main/java/org/tron/trident/abi/datatypes/StaticStruct.java @@ -21,6 +21,7 @@ public class StaticStruct extends StaticArray implements StructType { private final List> itemTypes = new ArrayList<>(); + @SuppressWarnings("unchecked") public StaticStruct(List values) { super(Type.class, values.size(), values); for (Type value : values) { @@ -38,7 +39,7 @@ public String getTypeAsString() { final StringBuilder type = new StringBuilder("("); for (int i = 0; i < itemTypes.size(); ++i) { final Class cls = itemTypes.get(i); - if (StructType.class.isAssignableFrom(cls)) { + if (StructType.class.isAssignableFrom(cls) || Array.class.isAssignableFrom(cls)) { type.append(getValue().get(i).getTypeAsString()); } else { type.append(AbiTypes.getTypeAString(cls)); diff --git a/abi/src/main/java/org/tron/trident/abi/datatypes/TrcToken.java b/abi/src/main/java/org/tron/trident/abi/datatypes/TrcToken.java index 298d58db..b5475428 100644 --- a/abi/src/main/java/org/tron/trident/abi/datatypes/TrcToken.java +++ b/abi/src/main/java/org/tron/trident/abi/datatypes/TrcToken.java @@ -18,13 +18,13 @@ /** * Unsigned integer type. */ -public class TrcToken extends IntType { +public class TrcToken extends Uint { public static final String TYPE_NAME = "trcToken"; public static final Uint DEFAULT = new Uint(BigInteger.ZERO); protected TrcToken(int bitSize, BigInteger value) { - super(TYPE_NAME, bitSize, value); + super(bitSize, value); } public TrcToken(int value) { @@ -37,7 +37,9 @@ public TrcToken(BigInteger value) { } @Override - protected boolean valid() { - return super.valid() && 0 <= value.signum(); + public String getTypeAsString() { + // Uint stores "uint256" (prefix + bit size); the canonical TVM ABI + // token is "trcToken", same as Address over Uint160. + return TYPE_NAME; } } diff --git a/abi/src/main/java/org/tron/trident/abi/datatypes/Utf8String.java b/abi/src/main/java/org/tron/trident/abi/datatypes/Utf8String.java index 6b0462f5..da23e973 100644 --- a/abi/src/main/java/org/tron/trident/abi/datatypes/Utf8String.java +++ b/abi/src/main/java/org/tron/trident/abi/datatypes/Utf8String.java @@ -13,6 +13,8 @@ package org.tron.trident.abi.datatypes; +import java.nio.charset.StandardCharsets; + /** * UTF-8 encoded string type. */ @@ -27,9 +29,15 @@ public Utf8String(String value) { this.value = value; } + /** + * 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; + int length = value == null ? 0 : value.getBytes(StandardCharsets.UTF_8).length; + int count = (int) Math.ceil((double) length / MAX_BYTE_LENGTH); + return Math.addExact(MAX_BYTE_LENGTH, Math.multiplyExact(count, MAX_BYTE_LENGTH)); } @Override diff --git a/abi/src/main/java/org/tron/trident/abi/datatypes/generated/StaticArray0.java b/abi/src/main/java/org/tron/trident/abi/datatypes/generated/StaticArray0.java new file mode 100644 index 00000000..b5959777 --- /dev/null +++ b/abi/src/main/java/org/tron/trident/abi/datatypes/generated/StaticArray0.java @@ -0,0 +1,33 @@ +package org.tron.trident.abi.datatypes.generated; + +import java.util.List; +import org.tron.trident.abi.datatypes.StaticArray; +import org.tron.trident.abi.datatypes.Type; + +/** + * Auto generated code. + *

Do not modifiy! + *

Please use org.web3j.codegen.AbiTypesGenerator in the + * codegen module to update. + */ +public class StaticArray0 extends StaticArray { + @Deprecated + public StaticArray0(List values) { + super(0); + } + + @Deprecated + @SafeVarargs + public StaticArray0(T... values) { + super(0, values); + } + + public StaticArray0(Class type, List values) { + super(type, 0, values); + } + + @SafeVarargs + public StaticArray0(Class type, T... values) { + super(type, 0, values); + } +} diff --git a/abi/src/main/java/org/tron/trident/abi/datatypes/primitive/Int.java b/abi/src/main/java/org/tron/trident/abi/datatypes/primitive/Int.java index 482894cd..0198ca20 100644 --- a/abi/src/main/java/org/tron/trident/abi/datatypes/primitive/Int.java +++ b/abi/src/main/java/org/tron/trident/abi/datatypes/primitive/Int.java @@ -16,7 +16,7 @@ import org.tron.trident.abi.datatypes.NumericType; import org.tron.trident.abi.datatypes.generated.Int32; -public final class Int extends Number { +public final class Int extends Number { public Int(int value) { super(value); diff --git a/abi/src/main/java/org/tron/trident/abi/datatypes/primitive/PrimitiveType.java b/abi/src/main/java/org/tron/trident/abi/datatypes/primitive/PrimitiveType.java index 7f227952..622d5cd3 100644 --- a/abi/src/main/java/org/tron/trident/abi/datatypes/primitive/PrimitiveType.java +++ b/abi/src/main/java/org/tron/trident/abi/datatypes/primitive/PrimitiveType.java @@ -14,6 +14,7 @@ package org.tron.trident.abi.datatypes.primitive; import java.io.Serializable; +import java.util.Locale; import java.util.Objects; import org.tron.trident.abi.datatypes.Type; @@ -23,7 +24,7 @@ public abstract class PrimitiveType> impl private final T value; PrimitiveType(final T value) { - this.type = getClass().getSimpleName().toLowerCase(); + this.type = getClass().getSimpleName().toLowerCase(Locale.ROOT); this.value = value; } diff --git a/abi/src/main/java/org/tron/trident/abi/datatypes/reflection/Parameterized.java b/abi/src/main/java/org/tron/trident/abi/datatypes/reflection/Parameterized.java new file mode 100644 index 00000000..2a96b3bd --- /dev/null +++ b/abi/src/main/java/org/tron/trident/abi/datatypes/reflection/Parameterized.java @@ -0,0 +1,25 @@ +/* + * Copyright 2022 Web3 Labs Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package org.tron.trident.abi.datatypes.reflection; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +public @interface Parameterized { + Class type(); +} \ No newline at end of file diff --git a/abi/src/test/java/org/tron/trident/abi/AbiV2TestFixture.java b/abi/src/test/java/org/tron/trident/abi/AbiV2TestFixture.java index 3a51dcea..2d4747fa 100644 --- a/abi/src/test/java/org/tron/trident/abi/AbiV2TestFixture.java +++ b/abi/src/test/java/org/tron/trident/abi/AbiV2TestFixture.java @@ -14,14 +14,26 @@ package org.tron.trident.abi; import java.math.BigInteger; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import org.tron.trident.abi.datatypes.Address; +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.Event; import org.tron.trident.abi.datatypes.Function; import org.tron.trident.abi.datatypes.StaticStruct; import org.tron.trident.abi.datatypes.Type; import org.tron.trident.abi.datatypes.Utf8String; +import org.tron.trident.abi.datatypes.generated.StaticArray1; +import org.tron.trident.abi.datatypes.generated.StaticArray2; +import org.tron.trident.abi.datatypes.generated.StaticArray3; import org.tron.trident.abi.datatypes.generated.Uint256; +import org.tron.trident.abi.datatypes.generated.Uint32; +import org.tron.trident.abi.datatypes.reflection.Parameterized; public class AbiV2TestFixture { @@ -31,12 +43,60 @@ public class AbiV2TestFixture { public static final String FUNC_GETFOOBAR = "getFooBar"; + public static final String FUNC_GETFOOBARBAR = "getFooBarBar"; + + public static final String FUNC_GETFOOFOOBARBAR = "getFooFooBarBar"; + + public static final String FUNC_GETNARBARBARFUZZFOONARFUZZNUUFOOFUZZFUNCTION = + "getNarBarBarFuzzFooNarFuzzNuuFooFuzz"; + public static final String FUNC_GETFOOUINT = "getFooUint"; + public static final String FUNC_GETFOOSTATICARRAY1 = "getFooStaticArray1"; + + public static final String FUNC_GETFOOSTATICARRAY2 = "getFooStaticArray2"; + + public static final String FUNC_GETFOOSTATICARRAY3 = "getFooStaticArray3"; + + public static final String FUNC_GETFOODYNAMICARRAY = "getFooDynamicArray"; + + public static final String FUNC_GETNARBARFOONARFOODYNAMICARRAY = "getNarBarFooNarFooDynamicArrays"; + + public static final String FUNC_IDNARBARFOONARFOODYNAMICARRAY = "idNarBarFooNarFooDynamicArrays"; + + public static final String FUNC_GETBARDYNAMICARRAY = "getBarDynamicArray"; + + public static final String FUNC_GETBARSTATICARRAY = "getBarStaticArray"; + + public static final String FUNC_SETBARSTATICARRAY = "setBarStaticArray"; + + public static final String FUNC_SETBARDYNAMICARRAY = "setBarDynamicArray"; + + public static final String FUNC_GETNARDYNAMICARRAY = "getNarDynamicArray"; + + public static final String FUNC_GETNARSTATICARRAY = "getNarStaticArray"; + + public static final String FUNC_SETFOODYNAMICARRAY = "setFooDynamicArray"; + + public static final String FUNC_GETFOOMULTIPLESTATICARRAY = "getFooMultipleStaticArray"; + + public static final String FUNC_GETFOOMULTIPLEDYNAMICARRAY = "getFooMultipleDynamicArray"; + + public static final String FUNC_IDNARBARFOONARFOOARRAYS = "idNarBarFooNarFooArrays"; + + public static final String FUNC_IDBARNARFOONARFOOARRAYS = "idBarNarFooNarFooArrays"; + + public static final String FUNC_GETFOOMULTIPLEDYNAMICSTATICARRAY = + "getFooMultipleDynamicStaticArray"; + public static final String FUNC_GETFUZZ = "getFuzz"; + public static final String FUNC_GETFUZZFUZZ = "getFuzzFuzz"; + public static final String FUNC_GETNAZ = "getNaz"; + public static final String FUNC_GETNAR = "getNar"; + public static final String FUNC_SETBAR = "setBar"; public static final String FUNC_SETBAZ = "setBaz"; @@ -53,8 +113,23 @@ public class AbiV2TestFixture { public static final String FUNC_SETWIZ = "setWiz"; - public static class Foo extends DynamicStruct { + public static final String FUNC_SETQUX = "setQux"; + + public static final String FUNC_GETQUX = "getQux"; + + public static final String FUNC_addDynamicBytesArray = "addDynamicBytesArray"; + public static final String FUNC_setArrayOfStructWithArraysFunction = + "setArrayOfStructWithArraysFunction"; + + public static final String FUNC_SETGETMULTIDIMSTATICARRAY = "setGetMultiDimStaticArray"; + + public static final String FUNC_SETGETMULTIDIMDYNAMICARRAY = "setGetMultiDimDynamicArray"; + + public static final String FUNC_setMultiDimStaticArrayWithUtf8StringSubType + = "setMultiDimStaticArrayWithUtf8StringSubType"; + + public static class Foo extends DynamicStruct { public String id; public String name; @@ -72,32 +147,330 @@ public Foo(Utf8String id, Utf8String name) { this.id = id.getValue(); this.name = name.getValue(); } + + @Override + public String toString() { + return "Foo{" + "id='" + id + '\'' + ", name='" + name + '\'' + '}'; + } } public static final Function setFooFunction = new Function( FUNC_SETFOO, Arrays.asList(new Foo("id", "name")), - Collections.>emptyList()); + Collections.emptyList()); public static final Function getFooFunction = new Function( - FUNC_GETFOO, - Arrays.asList(), - Arrays.>asList(new TypeReference() { - })); + FUNC_GETFOO, Arrays.asList(), Arrays.asList(new TypeReference() {})); public static final Function getFooUintFunction = new Function( FUNC_GETFOOUINT, Arrays.asList(), + Arrays.asList(new TypeReference() {}, new TypeReference() {})); + + public static final Function getFooStaticArray1Function = + new Function( + FUNC_GETFOOSTATICARRAY1, + Arrays.asList(), + Arrays.asList(new TypeReference>() {})); + + public static final Function getFooStaticArray2Function = + new Function( + FUNC_GETFOOSTATICARRAY2, + Arrays.asList(), + Arrays.asList(new TypeReference>() {})); + + public static final Function getFooStaticArray3Function = + new Function( + FUNC_GETFOOSTATICARRAY3, + Arrays.asList(), + Arrays.asList(new TypeReference>() {})); + + public static final Function getFooDynamicArrayFunction = + new Function( + FUNC_GETFOODYNAMICARRAY, + Arrays.asList(), + Arrays.asList(new TypeReference>() {})); + + public static final Function getNarBarFooNarFooDynamicArrayFunction = + new Function( + FUNC_GETNARBARFOONARFOODYNAMICARRAY, + Arrays.asList(), + Arrays.asList( + new TypeReference>() {}, + new TypeReference>() {}, + new TypeReference>() {}, + new TypeReference>() {}, + new TypeReference>() {})); + + public static final Function idNarBarFooNarFooDynamicArrayFunction = + new Function( + FUNC_IDNARBARFOONARFOODYNAMICARRAY, + Arrays.asList( + new StaticArray3<>( + AbiV2TestFixture.Nar.class, + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo"))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("", ""))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo")))), + new StaticArray3<>( + AbiV2TestFixture.Bar.class, + new AbiV2TestFixture.Bar(BigInteger.ZERO, BigInteger.ZERO), + new AbiV2TestFixture.Bar( + BigInteger.valueOf(123), BigInteger.valueOf(123)), + new AbiV2TestFixture.Bar(BigInteger.ZERO, BigInteger.ZERO)), + new DynamicArray<>( + AbiV2TestFixture.Foo.class, + new AbiV2TestFixture.Foo("id", "name")), + new DynamicArray<>( + AbiV2TestFixture.Nar.class, + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo"))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo"))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("", "")))), + new StaticArray3<>( + AbiV2TestFixture.Foo.class, + new AbiV2TestFixture.Foo("id", "name"), + new AbiV2TestFixture.Foo("id", "name"), + new AbiV2TestFixture.Foo("id", "name"))), + Arrays.asList( + new TypeReference>() {}, + new TypeReference>() {}, + new TypeReference>() {}, + new TypeReference>() {}, + new TypeReference>() {})); + + public static final Function getBarDynamicArrayFunction = + new Function( + FUNC_GETBARDYNAMICARRAY, + Arrays.asList(), + Arrays.asList(new TypeReference>() {})); + + public static final Function getBarStaticArrayFunction = + new Function( + FUNC_GETBARSTATICARRAY, + Arrays.asList(), + Arrays.asList(new TypeReference>() {})); + + @SuppressWarnings("unchecked") + public static final Function setBarStaticArrayFunction = + new Function( + FUNC_SETBARSTATICARRAY, + Arrays.asList( + new StaticArray3( + AbiV2TestFixture.Bar.class, + new AbiV2TestFixture.Bar( + BigInteger.valueOf(0), BigInteger.valueOf(0)), + new AbiV2TestFixture.Bar( + BigInteger.valueOf(123), BigInteger.valueOf(123)), + new AbiV2TestFixture.Bar( + BigInteger.valueOf(0), BigInteger.valueOf(0)))), + Arrays.asList()); + + @SuppressWarnings("unchecked") + public static final Function setBarDynamicArrayFunction = + new Function( + FUNC_SETBARDYNAMICARRAY, + Arrays.asList( + new DynamicArray( + AbiV2TestFixture.Bar.class, + new AbiV2TestFixture.Bar( + BigInteger.valueOf(0), BigInteger.valueOf(0)), + new AbiV2TestFixture.Bar( + BigInteger.valueOf(123), BigInteger.valueOf(123)), + new AbiV2TestFixture.Bar( + BigInteger.valueOf(0), BigInteger.valueOf(0)))), + Arrays.asList()); + + public static final Function getNarDynamicArrayFunction = + new Function( + FUNC_GETNARDYNAMICARRAY, + Arrays.asList(), + Arrays.asList(new TypeReference>() {})); + + public static final Function getNarStaticArrayFunction = + new Function( + FUNC_GETNARSTATICARRAY, + Arrays.asList(), + Arrays.asList(new TypeReference>() {})); + + public static final Function getFooMultipleStaticArrayFunction = + new Function( + FUNC_GETFOOMULTIPLESTATICARRAY, + Arrays.asList(), + Arrays.asList( + new TypeReference>() {}, + new TypeReference>() {})); + + public static final Function getFooMultipleDynamicArrayFunction = + new Function( + FUNC_GETFOOMULTIPLEDYNAMICARRAY, + Arrays.asList(), + Arrays.asList( + new TypeReference>() {}, + new TypeReference>() {})); + + public static final Function idNarBarFooNarFooArraysFunction = + new Function( + FUNC_IDNARBARFOONARFOOARRAYS, + Arrays.asList( + new DynamicArray<>( + AbiV2TestFixture.Nar.class, + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo"))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo"))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("", "")))), + new StaticArray3<>( + AbiV2TestFixture.Bar.class, + new AbiV2TestFixture.Bar(BigInteger.ZERO, BigInteger.ZERO), + new AbiV2TestFixture.Bar( + BigInteger.valueOf(123), BigInteger.valueOf(123)), + new AbiV2TestFixture.Bar(BigInteger.ZERO, BigInteger.ZERO)), + new DynamicArray<>( + AbiV2TestFixture.Foo.class, + new AbiV2TestFixture.Foo("id", "name")), + new DynamicArray<>( + AbiV2TestFixture.Nar.class, + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo"))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo"))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("", "")))), + new DynamicArray<>( + AbiV2TestFixture.Foo.class, + new AbiV2TestFixture.Foo("id", "name"))), Arrays.>asList( - new TypeReference() { - }, new TypeReference() { - })); + new TypeReference>() {}, + new TypeReference>() {}, + new TypeReference>() {}, + new TypeReference>() {}, + new TypeReference>() {})); - public static class Bar extends StaticStruct { + public static final Function idNarBarFooNarFooArraysFunction2 = + new Function( + FUNC_IDNARBARFOONARFOOARRAYS, + Arrays.asList( + new StaticArray3<>( + AbiV2TestFixture.Nar.class, + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo"))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("", ""))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo")))), + new DynamicArray<>( + AbiV2TestFixture.Bar.class, + new AbiV2TestFixture.Bar( + BigInteger.valueOf(123), BigInteger.valueOf(123)), + new AbiV2TestFixture.Bar( + BigInteger.valueOf(12), BigInteger.valueOf(33)), + new AbiV2TestFixture.Bar(BigInteger.ZERO, BigInteger.ZERO)), + new DynamicArray<>( + AbiV2TestFixture.Foo.class, + new AbiV2TestFixture.Foo("id", "name")), + new DynamicArray<>( + AbiV2TestFixture.Nar.class, + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo"))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo"))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("", "")))), + new StaticArray3<>( + AbiV2TestFixture.Foo.class, + new AbiV2TestFixture.Foo("id", "name"), + new AbiV2TestFixture.Foo("id", "name"), + new AbiV2TestFixture.Foo("id", "name"))), + Arrays.>asList( + new TypeReference>() {}, + new TypeReference>() {}, + new TypeReference>() {}, + new TypeReference>() {}, + new TypeReference>() {})); + public static final Function idBarNarFooNarFooArraysFunction = + new Function( + FUNC_IDBARNARFOONARFOOARRAYS, + Arrays.asList( + new StaticArray3<>( + AbiV2TestFixture.Bar.class, + new AbiV2TestFixture.Bar(BigInteger.ZERO, BigInteger.ZERO), + new AbiV2TestFixture.Bar( + BigInteger.valueOf(123), BigInteger.valueOf(123)), + new AbiV2TestFixture.Bar(BigInteger.ZERO, BigInteger.ZERO)), + new StaticArray3<>( + AbiV2TestFixture.Nar.class, + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo"))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("", ""))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo")))), + new DynamicArray<>( + AbiV2TestFixture.Foo.class, + new AbiV2TestFixture.Foo("id", "name")), + new DynamicArray<>( + AbiV2TestFixture.Nar.class, + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo"))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo"))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("", "")))), + new StaticArray3<>( + AbiV2TestFixture.Foo.class, + new AbiV2TestFixture.Foo("id", "name"), + new AbiV2TestFixture.Foo("id", "name"), + new AbiV2TestFixture.Foo("id", "name"))), + Arrays.>asList( + new TypeReference>() {}, + new TypeReference>() {}, + new TypeReference>() {}, + new TypeReference>() {}, + new TypeReference>() {})); + + public static final Function getFooMultipleDynamicStaticArrayFunction = + new Function( + FUNC_GETFOOMULTIPLEDYNAMICSTATICARRAY, + Arrays.asList(), + Arrays.>asList( + new TypeReference>() {}, + new TypeReference>() {})); + + public static class Bar extends StaticStruct { public BigInteger id; public BigInteger data; @@ -115,6 +488,11 @@ public Bar(Uint256 id, Uint256 data) { this.id = id.getValue(); this.data = data.getValue(); } + + @Override + public String toString() { + return "Bar{" + "id='" + id + '\'' + ", data='" + data + '\'' + '}'; + } } public static final Function setBarFunction = @@ -127,20 +505,52 @@ public Bar(Uint256 id, Uint256 data) { new Function( FUNC_GETBAR, Arrays.asList(), - Arrays.>asList(new TypeReference() { - })); + Arrays.>asList(new TypeReference() {})); public static final Function getFooBarFunction = new Function( FUNC_GETFOOBAR, Arrays.asList(), Arrays.>asList( - new TypeReference() { - }, new TypeReference() { - })); + new TypeReference() {}, new TypeReference() {})); - public static class Baz extends DynamicStruct { + public static final Function getFooBarBarFunction = + new Function( + FUNC_GETFOOBARBAR, + Arrays.asList(), + Arrays.asList( + new TypeReference() {}, + new TypeReference() {}, + new TypeReference() {})); + + public static final Function getFooFooBarBarFunction = + new Function( + FUNC_GETFOOFOOBARBAR, + Arrays.asList(), + Arrays.asList( + new TypeReference() {}, + new TypeReference() {}, + new TypeReference() {}, + new TypeReference() {})); + + public static final Function + getNarBarBarFuzzFooNarFuzzNuuFooFuzzFunction = + new Function( + FUNC_GETNARBARBARFUZZFOONARFUZZNUUFOOFUZZFUNCTION, + Arrays.asList(), + Arrays.asList( + new TypeReference() {}, + new TypeReference() {}, + new TypeReference() {}, + new TypeReference() {}, + new TypeReference() {}, + new TypeReference() {}, + new TypeReference() {}, + new TypeReference() {}, + new TypeReference() {}, + new TypeReference() {})); + public static class Baz extends DynamicStruct { public String id; public BigInteger data; @@ -167,7 +577,6 @@ public Baz(Utf8String id, Uint256 data) { Collections.>emptyList()); public static class Boz extends DynamicStruct { - public BigInteger data; public String id; @@ -197,11 +606,9 @@ public Boz(Uint256 data, Utf8String id) { new Function( FUNC_SETBOZ, Collections.emptyList(), - Arrays.>asList(new TypeReference() { - })); + Arrays.>asList(new TypeReference() {})); public static class Fuzz extends StaticStruct { - public Bar bar; public BigInteger data; @@ -219,22 +626,43 @@ public Fuzz(Bar bar, Uint256 data) { } } + public static final Function setFooDynamicArrayFunction = + new Function( + FUNC_SETFOODYNAMICARRAY, + Collections.singletonList( + new DynamicArray<>( + Foo.class, + new Foo("", ""), + new Foo("id", "name"), + new Foo("", ""))), + Collections.emptyList()); + + public static final Function setDoubleFooStaticArrayFunction = + new Function( + FUNC_SETFOODYNAMICARRAY, + Arrays.asList(new Foo("", ""), new Foo("id", "name"), new Foo("", "")), + Collections.emptyList()); + public static final Function setFuzzFunction = new Function( FUNC_SETFUZZ, Arrays.asList( new Fuzz(new Bar(BigInteger.ONE, BigInteger.TEN), BigInteger.ONE)), - Collections.>emptyList()); + Collections.emptyList()); public static final Function getFuzzFunction = new Function( FUNC_GETFUZZ, Arrays.asList(), - Arrays.>asList(new TypeReference() { - })); + Arrays.asList(new TypeReference() {})); - public static class Nuu extends DynamicStruct { + public static final Function getFuzzFuzzFunction = + new Function( + FUNC_GETFUZZFUZZ, + Arrays.asList(), + Arrays.asList(new TypeReference() {}, new TypeReference() {})); + public static class Nuu extends DynamicStruct { public Foo foo; public Nuu(Foo foo) { @@ -253,11 +681,9 @@ public Nuu(Foo foo) { new Function( FUNC_SETNUU, Collections.emptyList(), - Arrays.>asList(new TypeReference() { - })); + Arrays.>asList(new TypeReference() {})); public static class Nar extends DynamicStruct { - public Nuu nuu; public Nar(Nuu nuu) { @@ -267,7 +693,6 @@ public Nar(Nuu nuu) { } public static class Naz extends DynamicStruct { - public Nar nar; public BigInteger data; @@ -285,22 +710,96 @@ public Naz(Nar nar, Uint256 data) { } } + public static class Nazzy extends DynamicStruct { + public List foo; + + public Nazzy(List foo) { + super(new DynamicArray<>(Foo.class, foo)); + this.foo = foo; + } + + public Nazzy(@Parameterized(type = Foo.class) DynamicArray foo) { + super(foo); + this.foo = foo.getValue(); + } + + @Override + public String toString() { + return "Nazzy{" + "foo=" + foo + '}'; + } + } + + public static class Barr extends DynamicStruct { + public List bars; + + public BigInteger data; + + public Barr(List bars, BigInteger data) { + super( + new DynamicArray<>(Bar.class, bars), + new Uint256(data)); + this.bars = bars; + this.data = data; + } + + public Barr(@Parameterized(type = Bar.class) DynamicArray bars, Uint256 data) { + super(bars, data); + this.bars = bars.getValue(); + this.data = data.getValue(); + } + } + + public static class Nazz extends DynamicStruct { + public List nazzy; + + public BigInteger data; + + public Nazz(List nazzy, BigInteger data) { + super( + new DynamicArray<>(Nazzy.class, nazzy), + new Uint256(data)); + this.nazzy = nazzy; + this.data = data; + } + + public Nazz(@Parameterized(type = Nazzy.class) DynamicArray nazzy, Uint256 data) { + super(nazzy, data); + this.nazzy = nazzy.getValue(); + this.data = data.getValue(); + } + + @Override + public String toString() { + return "Nazz{" + "nazzy=" + nazzy + ", data=" + data + '}'; + } + } + public static final Function setNazFunction = new Function( FUNC_SETNAZ, Arrays.asList( new Naz(new Nar(new Nuu(new Foo("id", "name"))), BigInteger.ONE)), - Collections.>emptyList()); + Collections.emptyList()); - public static final Function getNazFunction = + public static final Function getNazzFunction = new Function( FUNC_GETNAZ, Arrays.asList(), - Arrays.>asList(new TypeReference() { - })); + Arrays.asList(new TypeReference() {})); - public static class Wiz extends DynamicStruct { + public static final Event nazzEvent = + new Event( + "nazzEvent", + Arrays.asList(new TypeReference() {}, new TypeReference() {})); + + public static final Event nazzEvent2 = + new Event("nazzEvent2", Arrays.asList(new TypeReference>() {})); + + public static final Function getNarFunction = + new Function( + FUNC_GETNAR, Arrays.asList(), Arrays.asList(new TypeReference() {})); + public static class Wiz extends DynamicStruct { public Foo foo; public String data; @@ -322,5 +821,178 @@ public Wiz(Foo foo, Utf8String data) { new Function( FUNC_SETWIZ, Arrays.asList(new Wiz(new Foo("id", "name"), "data")), + Collections.emptyList()); + + public static class Qux extends DynamicStruct { + public Bar bar; + + public String data; + + public Qux(Bar bar, String data) { + super(bar, new Utf8String(data)); + this.bar = bar; + this.data = data; + } + + public Qux(Bar bar, Utf8String data) { + super(bar, data); + this.bar = bar; + this.data = data.getValue(); + } + } + + public static final Function setQuxFunction = + new Function( + FUNC_SETQUX, + Arrays.asList(new Qux(new Bar(BigInteger.ONE, BigInteger.TEN), "data")), + Collections.emptyList()); + + public static final Function getQuxFunction = + new Function( + FUNC_GETQUX, Arrays.asList(), Arrays.asList(new TypeReference() {})); + + public static class BytesStruct extends DynamicStruct { + public byte[] pubkey; + + public BigInteger something; + + public byte[] metadata; + + public BytesStruct(byte[] pubkey, BigInteger something, byte[] metadata) { + super( + new DynamicBytes(pubkey), + new Uint32(something), + new DynamicBytes(metadata)); + this.pubkey = pubkey; + this.something = something; + this.metadata = metadata; + } + + public BytesStruct(DynamicBytes pubkey, Uint32 something, DynamicBytes metadata) { + super(pubkey, something, metadata); + this.pubkey = pubkey.getValue(); + this.something = something.getValue(); + this.metadata = metadata.getValue(); + } + } + + public static final Function addDynamicBytesArrayFunction = + new Function( + FUNC_addDynamicBytesArray, + Arrays.asList( + new BytesStruct( + "dynamic".getBytes(StandardCharsets.UTF_8), + BigInteger.ZERO, + "Bytes".getBytes(StandardCharsets.UTF_8))), Collections.>emptyList()); + + public static class ArrayStruct extends DynamicStruct { + public BigInteger id; + + public List addresses; + + public ArrayStruct(BigInteger id, List addresses) { + super( + new Uint256(id), + new DynamicArray

( + Address.class, + Utils.typeMap( + addresses, Address.class))); + this.id = id; + this.addresses = addresses; + } + + public ArrayStruct(Uint256 id, DynamicArray
addresses) { + super(id, addresses); + this.id = id.getValue(); + this.addresses = + addresses.getValue().stream() + .map(v -> v.getValue()) + .collect(Collectors.toList()); + } + } + + public static final Function setArrayOfStructWithArraysFunction = + new Function( + FUNC_setArrayOfStructWithArraysFunction, + Arrays.asList( + new DynamicArray( + ArrayStruct.class, + Arrays.asList( + new ArrayStruct( + BigInteger.ONE, + Arrays.asList( + "0x0000000000000000000000000000000000000000", + "0x1111111111111111111111111111111111111111")), + new ArrayStruct( + BigInteger.TEN, + Arrays.asList( + "0x2222222222222222222222222222222222222222", + "0x3333333333333333333333333333333333333333"))))), + Collections.>emptyList()); + + public static final Function setGetMultiDimStaticArrayFunction = + new Function(FUNC_SETGETMULTIDIMSTATICARRAY, + Collections.singletonList(new StaticArray3<>( + (Class)StaticArray2.class, + new StaticArray2<>(Uint256.class, new Uint256(1), new Uint256(2)), + new StaticArray2<>(Uint256.class, new Uint256(3), new Uint256(4)), + new StaticArray2<>(Uint256.class, new Uint256(5), new Uint256(6)))), + Collections.singletonList(new TypeReference>>() {}) + ); + + public static final Function setGetMultiDimDynamicArrayFunction = + new Function(FUNC_SETGETMULTIDIMDYNAMICARRAY, + Collections.singletonList(new DynamicArray<>( + (Class)DynamicArray.class, + new DynamicArray<>( + (Class)DynamicArray.class, + new DynamicArray<>( + Uint256.class, + new Uint256(1))), + new DynamicArray<>( + (Class)DynamicArray.class, + new DynamicArray<>( + Uint256.class, + new Uint256(2), new Uint256(3))), + new DynamicArray<>( + (Class)DynamicArray.class, + new DynamicArray<>( + Uint256.class, + new Uint256(4), new Uint256(5)), + new DynamicArray<>( + Uint256.class, + new Uint256(6), new Uint256(7), new Uint256(8))))), + Collections.singletonList(new TypeReference>>>() {}) + ); + + public static Function setMultiDimStaticArrayWithUtf8StringSubTypeFunction() { + //string[2][2] + StaticArray2 inner1 = new StaticArray2<>( + Utf8String.class, + new Utf8String("1234567890123456789012345678901234567890" + + "1234567890123456789012345678901234567890"), + new Utf8String("short1") + ); + + StaticArray2 inner2 = new StaticArray2<>( + Utf8String.class, + new Utf8String("short2"), + new Utf8String("short3") + ); + + @SuppressWarnings("unchecked") + StaticArray2> outer = new StaticArray2<>( + (Class>) (Class) StaticArray2.class, + inner1, + inner2 + ); + + return new Function( + FUNC_setMultiDimStaticArrayWithUtf8StringSubType, + Collections.singletonList(outer), + Collections.emptyList() + ); + } + } diff --git a/abi/src/test/java/org/tron/trident/abi/CustomErrorEncoderTest.java b/abi/src/test/java/org/tron/trident/abi/CustomErrorEncoderTest.java new file mode 100644 index 00000000..087a6baa --- /dev/null +++ b/abi/src/test/java/org/tron/trident/abi/CustomErrorEncoderTest.java @@ -0,0 +1,110 @@ +package org.tron.trident.abi; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.tron.trident.abi.Utils.convert; + +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.tron.trident.abi.AbiV2TestFixture.Nazz; +import org.tron.trident.abi.datatypes.Address; +import org.tron.trident.abi.datatypes.CustomError; +import org.tron.trident.abi.datatypes.DynamicArray; +import org.tron.trident.abi.datatypes.DynamicStruct; +import org.tron.trident.abi.datatypes.StaticStruct; +import org.tron.trident.abi.datatypes.Utf8String; +import org.tron.trident.abi.datatypes.generated.Uint256; + +public class CustomErrorEncoderTest { + @Test + public void testCalculateSignatureHash() { + assertEquals( + CustomErrorEncoder.calculateSignatureHash("InvalidAccess(address,string,uint256)"), + ("cb5157bf1b439b9573ea7a95f7c00cc33f832ed728345c2bd29146ce58bbab57")); + + assertEquals( + CustomErrorEncoder.calculateSignatureHash("RandomError(address[],bytes)"), + ("bf37b77ddf0fbbf29ee6a3ebda3d177c2d438123b10571806c57958230d9f905")); + } + + @Test + public void testEncode() { + CustomError error = + new CustomError( + "InvalidAccess", + Arrays.>asList( + new TypeReference
() {}, + new TypeReference() {}, + new TypeReference() {})); + + assertEquals( + CustomErrorEncoder.encode(error), + "cb5157bf1b439b9573ea7a95f7c00cc33f832ed728345c2bd29146ce58bbab57"); + } + + @Test + public void testBuildErrorSignature() { + List> parameters = + Arrays.>asList( + new TypeReference
() {}, + new TypeReference() {}, + new TypeReference() {}); + + assertEquals( + "InvalidAccess(address,string,uint256)", + CustomErrorEncoder.buildErrorSignature("InvalidAccess", convert(parameters))); + } + + @Test + void testBuildErrorSignatureWithDynamicStructs() { + List> parameters = + Arrays.asList( + new TypeReference() {}, + new TypeReference() {}); + + assertEquals( + "DynamicStructError((((string,string)[])[],uint256),(string,string))", + CustomErrorEncoder.buildErrorSignature("DynamicStructError", convert(parameters))); + } + + @Test + void testBuildErrorSignatureWithDynamicArrays() { + List> parameters = + Arrays.asList(new TypeReference>() {}); + + assertEquals( + "DynamicArrayError((((string,string)[])[],uint256)[])", + CustomErrorEncoder.buildErrorSignature("DynamicArrayError", convert(parameters))); + } + + @Test + void testBuildErrorSignatureWithInnerTypesTuple() throws ClassNotFoundException { + List> tupleFields = + Arrays.asList( + TypeReference.makeTypeReference("uint256"), + TypeReference.makeTypeReference("string")); + List> parameters = + Arrays.asList(new TypeReference(false, tupleFields) { + }); + + assertEquals( + "TupleError((uint256,string))", + CustomErrorEncoder.buildErrorSignature("TupleError", convert(parameters))); + } + + @Test + void testBuildErrorSignatureWithInnerTypesStaticTuple() throws ClassNotFoundException { + List> tupleFields = + Arrays.asList( + TypeReference.makeTypeReference("uint256"), + TypeReference.makeTypeReference("address")); + List> parameters = + Arrays.asList(new TypeReference(false, tupleFields) { + }); + + assertEquals( + "StaticTupleError((uint256,address))", + CustomErrorEncoder.buildErrorSignature("StaticTupleError", convert(parameters))); + } + +} diff --git a/abi/src/test/java/org/tron/trident/abi/DefaultFunctionEncoderTest.java b/abi/src/test/java/org/tron/trident/abi/DefaultFunctionEncoderTest.java index cd05613e..87690a59 100644 --- a/abi/src/test/java/org/tron/trident/abi/DefaultFunctionEncoderTest.java +++ b/abi/src/test/java/org/tron/trident/abi/DefaultFunctionEncoderTest.java @@ -20,6 +20,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.Iterator; +import java.util.List; import org.junit.jupiter.api.Test; import org.tron.trident.abi.datatypes.Address; import org.tron.trident.abi.datatypes.Bool; @@ -28,12 +29,14 @@ import org.tron.trident.abi.datatypes.DynamicStruct; import org.tron.trident.abi.datatypes.Function; import org.tron.trident.abi.datatypes.StaticStruct; +import org.tron.trident.abi.datatypes.TrcToken; import org.tron.trident.abi.datatypes.Type; import org.tron.trident.abi.datatypes.Uint; import org.tron.trident.abi.datatypes.Utf8String; import org.tron.trident.abi.datatypes.generated.Bytes10; import org.tron.trident.abi.datatypes.generated.Uint256; import org.tron.trident.abi.datatypes.generated.Uint32; +import org.tron.trident.utils.Numeric; public class DefaultFunctionEncoderTest { @@ -50,6 +53,19 @@ public void testBuildMessageSignature() { "baz", Arrays.asList(new Uint32(BigInteger.valueOf(69)), new Bool(true)))); } + @Test + public void testBuildMethodSignatureWithTrcToken() { + // Bare TrcToken parameters go through the instance getTypeAsString path; + // the canonical TVM token has no bit-size suffix. + assertEquals( + "transferToken(trcToken,uint256)", + DefaultFunctionEncoder.buildMethodSignature( + "transferToken", + Arrays.asList( + new TrcToken(BigInteger.valueOf(1000016)), + new Uint256(BigInteger.TEN)))); + } + @Test public void testBuildEmptyMethodSignature() { assertEquals( @@ -57,6 +73,53 @@ public void testBuildEmptyMethodSignature() { DefaultFunctionEncoder.buildMethodSignature("empty", Collections.emptyList())); } + @Test + public void testBuildMethodSignatureWithStructWithArray() { + assertEquals( + "structWithArray((uint256,address[]))", + DefaultFunctionEncoder.buildMethodSignature( + "structWithArray", + Arrays.asList( + new AbiV2TestFixture.ArrayStruct( + BigInteger.ONE, Collections.emptyList())))); + } + + @Test + public void testBuildMessageSignatureWithComplexTuple() { + AbiV2TestFixture.Nazz nazz = + new AbiV2TestFixture.Nazz( + Arrays.asList( + new AbiV2TestFixture.Nazzy( + Arrays.asList( + new AbiV2TestFixture.Foo("a", "b"), + new AbiV2TestFixture.Foo("c", "d"))), + new AbiV2TestFixture.Nazzy( + Arrays.asList( + new AbiV2TestFixture.Foo("e", "f"), + new AbiV2TestFixture.Foo("g", "key")))), + BigInteger.valueOf(100L)); + + assertEquals( + "someFunc((((string,string)[])[],uint256))", + FunctionEncoder.buildMethodSignature("someFunc", Arrays.asList(nazz))); + + // correct handling of empty list of dynamic struct + AbiV2TestFixture.Nazz nazz2 = + new AbiV2TestFixture.Nazz(Collections.emptyList(), BigInteger.ZERO); + + assertEquals( + "someFunc((((string,string)[])[],uint256))", + FunctionEncoder.buildMethodSignature("someFunc", Arrays.asList(nazz2))); + + // correct handling of empty list of static struct + AbiV2TestFixture.Barr barr = + new AbiV2TestFixture.Barr(Collections.emptyList(), BigInteger.ZERO); + + assertEquals( + "someFunc(((uint256,uint256)[],uint256))", + FunctionEncoder.buildMethodSignature("someFunc", Arrays.asList(barr))); + } + @Test public void testEncodeConstructorEmpty() { assertEquals("", FunctionEncoder.encodeConstructor(Collections.emptyList())); @@ -82,6 +145,42 @@ public void testEncodeConstructorUint() { new Uint(BigInteger.ONE), new Uint(BigInteger.valueOf(0x20))))); } + @Test + public void testEncodeConstructorPacked_multipleParameters() { + assertEquals( + "00000045014772656574696e677321", + FunctionEncoder.encodeConstructorPacked( + Arrays.asList( + new Uint32(BigInteger.valueOf(69)), + new Bool(true), + new Utf8String("Greetings!")))); + + assertEquals( + "783139457468657265756d205369676e6564204d6573736167653a" + + "663e27adc18d862da9a82f060310621d379e469a" + + "000000000000000000000000000000000000000000000000000000000000000a" + + "31323334353637383930", + FunctionEncoder.encodeConstructorPacked( + Arrays.asList( + new Utf8String("x19Ethereum Signed Message:"), + new Address("0x663e27AdC18d862dA9A82f060310621D379e469a"), + new Uint256(BigInteger.TEN), + new Bytes10("1234567890".getBytes())))); + assertEquals( + "0000004501000102030405", + FunctionEncoder.encodeConstructorPacked( + Arrays.asList( + new Uint32(BigInteger.valueOf(69)), + new Bool(true), + new DynamicBytes((new byte[] {0, 1, 2, 3, 4, 5}))))); + assertEquals( + "12000102030405", + FunctionEncoder.encodeConstructorPacked( + Arrays.asList( + new DynamicBytes(Numeric.hexStringToByteArray("0x12")), + new DynamicBytes((new byte[] {0, 1, 2, 3, 4, 5}))))); + } + @Test public void testFunctionSimpleEncode() { Function function = @@ -245,6 +344,393 @@ public void testDynamicStructNestedEncode2() { FunctionEncoder.encode(AbiV2TestFixture.setNazFunction)); } + @Test + public void testMultiReturnStaticDynamicArrayWithStaticDynamicStructs() { + String encodedInput = + "1f9218db" + + "0000000000000000000000000000000000000000000000000000000000000140" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "000000000000000000000000000000000000000000000000000000000000007b" + + "000000000000000000000000000000000000000000000000000000000000007b" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000460" + + "0000000000000000000000000000000000000000000000000000000000000560" + + "00000000000000000000000000000000000000000000000000000000000008a0" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000160" + + "0000000000000000000000000000000000000000000000000000000000000220" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000003" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000160" + + "0000000000000000000000000000000000000000000000000000000000000260" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000120" + + "00000000000000000000000000000000000000000000000000000000000001e0" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000"; + assertEquals( + encodedInput, + FunctionEncoder.encode(AbiV2TestFixture.idNarBarFooNarFooDynamicArrayFunction)); + } + + @Test + public void testEncodeStructMultipleDynamicStaticArray() { + String expected = + "7fcdab08" + + "0000000000000000000000000000000000000000000000000000000000000140" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "000000000000000000000000000000000000000000000000000000000000007b" + + "000000000000000000000000000000000000000000000000000000000000007b" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000480" + + "0000000000000000000000000000000000000000000000000000000000000580" + + "00000000000000000000000000000000000000000000000000000000000008c0" + + "0000000000000000000000000000000000000000000000000000000000000003" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000160" + + "0000000000000000000000000000000000000000000000000000000000000260" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000003" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000160" + + "0000000000000000000000000000000000000000000000000000000000000260" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000"; + + assertEquals( + expected, FunctionEncoder.encode(AbiV2TestFixture.idNarBarFooNarFooArraysFunction)); + } + + @Test + public void testEncodeStructMultipleDynamicStaticArray2() { + String expected = + "8a62c214" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "000000000000000000000000000000000000000000000000000000000000007b" + + "000000000000000000000000000000000000000000000000000000000000007b" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000140" + + "0000000000000000000000000000000000000000000000000000000000000460" + + "0000000000000000000000000000000000000000000000000000000000000560" + + "00000000000000000000000000000000000000000000000000000000000008a0" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000160" + + "0000000000000000000000000000000000000000000000000000000000000220" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000003" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000160" + + "0000000000000000000000000000000000000000000000000000000000000260" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000120" + + "00000000000000000000000000000000000000000000000000000000000001e0" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000"; + + assertEquals( + expected, FunctionEncoder.encode(AbiV2TestFixture.idBarNarFooNarFooArraysFunction)); + } + + @Test + public void testEncodeStructMultipleDynamicStaticArray3() { + String expected = + "1b9faea1" + + "00000000000000000000000000000000000000000000000000000000000000a0" + + "00000000000000000000000000000000000000000000000000000000000003c0" + + "00000000000000000000000000000000000000000000000000000000000004a0" + + "00000000000000000000000000000000000000000000000000000000000005a0" + + "00000000000000000000000000000000000000000000000000000000000008e0" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000160" + + "0000000000000000000000000000000000000000000000000000000000000220" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000003" + + "000000000000000000000000000000000000000000000000000000000000007b" + + "000000000000000000000000000000000000000000000000000000000000007b" + + "000000000000000000000000000000000000000000000000000000000000000c" + + "0000000000000000000000000000000000000000000000000000000000000021" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000003" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000160" + + "0000000000000000000000000000000000000000000000000000000000000260" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000120" + + "00000000000000000000000000000000000000000000000000000000000001e0" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000"; + + assertEquals( + expected, + FunctionEncoder.encode(AbiV2TestFixture.idNarBarFooNarFooArraysFunction2)); + } + @Test public void testDynamicStructNestedEncode3() { assertEquals( @@ -304,6 +790,32 @@ public Struct2(BigInteger id, BigInteger name) { new Struct2(BigInteger.valueOf(100), BigInteger.ONE)))); } + @Test + public void testABIv2DynamicArrayEncode() { + assertEquals( + "8907a08c" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000003" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "00000000000000000000000000000000000000000000000000000000000000e0" + + "00000000000000000000000000000000000000000000000000000000000001a0" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000", + FunctionEncoder.encode(AbiV2TestFixture.setFooDynamicArrayFunction)); + } + @Test public void testMakeFunction() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, @@ -336,4 +848,208 @@ public void testMakeFunction() assertEquals(actualOutput.getType(), expectedOutput.next().getType()); } } + + @Test + public void testEncodeStaticStructStaticArray() { + String expected = + "8efa5af7" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "000000000000000000000000000000000000000000000000000000000000007b" + + "000000000000000000000000000000000000000000000000000000000000007b" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000"; + + assertEquals(expected, FunctionEncoder.encode(AbiV2TestFixture.setBarStaticArrayFunction)); + } + + @Test + public void testEncodeStaticStructDynamicArray() { + String expected = + "e1f84cb5" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000003" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "000000000000000000000000000000000000000000000000000000000000007b" + + "000000000000000000000000000000000000000000000000000000000000007b" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000"; + + assertEquals(expected, FunctionEncoder.encode(AbiV2TestFixture.setBarDynamicArrayFunction)); + } + + @Test + public void testEncodeArrayOfStructWithArrays() { + String expected = + "fc3fdffb" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "00000000000000000000000000000000000000000000000000000000000000e0" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000001111111111111111111111111111111111111111" + + "000000000000000000000000000000000000000000000000000000000000000a" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000002222222222222222222222222222222222222222" + + "0000000000000000000000003333333333333333333333333333333333333333"; + + assertEquals( + expected, + FunctionEncoder.encode(AbiV2TestFixture.setArrayOfStructWithArraysFunction)); + } + + @Test + public void testDynamicStructWithStaticStruct() { + String expected = + "3db2b680" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "000000000000000000000000000000000000000000000000000000000000000a" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6461746100000000000000000000000000000000000000000000000000000000"; + + assertEquals(expected, FunctionEncoder.encode(AbiV2TestFixture.setQuxFunction)); + } + + @Test + public void testBuildMethodSignatureWithDynamicStruct_dynamicArray() { + List bytes = new java.util.ArrayList<>(); + + bytes.add(new DynamicBytes("1234".getBytes())); + bytes.add(new DynamicBytes("5678".getBytes())); + + List dynamicArray = Arrays.asList( + new Uint256(10), + new DynamicArray<>(DynamicBytes.class, bytes) + ); + + DynamicStruct dynamicStruct = + new DynamicStruct(dynamicArray); + + String functionDynamicArray = FunctionEncoder.buildMethodSignature( + "testBytes", dynamicArray); + + String functionDynamicStruct = FunctionEncoder.buildMethodSignature( + "testBytes", Collections.singletonList(dynamicStruct)); + + assertEquals("testBytes(uint256,bytes[])", functionDynamicArray); + + assertEquals("testBytes((uint256,bytes[]))", functionDynamicStruct); + + assertEquals("880e82a8", FunctionEncoder.buildMethodId(functionDynamicArray)); + + assertEquals("256b906f", FunctionEncoder.buildMethodId(functionDynamicStruct)); + + } + + @Test + public void testBuildMethodSignatureWithDynamicStruct_Uint() { + List uintArrayList = new java.util.ArrayList<>(); + + uintArrayList.add(new Uint256(new BigInteger("1234"))); + uintArrayList.add(new Uint256(new BigInteger("2345"))); + + List dynamicArray = Arrays.asList( + new Uint256(new BigInteger(String.valueOf(10))), + new DynamicArray<>(Uint256.class, uintArrayList) + ); + + DynamicStruct dynamicStruct = + new DynamicStruct(dynamicArray); + + String functionDynamicArray = FunctionEncoder.buildMethodSignature( + "testBytes", dynamicArray); + + String functionDynamicStruct = FunctionEncoder.buildMethodSignature( + "testBytes", Collections.singletonList(dynamicStruct)); + + assertEquals("testBytes(uint256,uint256[])", functionDynamicArray); + assertEquals("testBytes((uint256,uint256[]))", functionDynamicStruct); + + } + + @Test + public void testEncodeMultiDimStaticArray() { + //setGetMultiDimStaticArray(uint256[2][3]) + String expected = + "3cb0b894" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000003" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "0000000000000000000000000000000000000000000000000000000000000005" + + "0000000000000000000000000000000000000000000000000000000000000006"; + + assertEquals(expected, FunctionEncoder.encode(AbiV2TestFixture.setGetMultiDimStaticArrayFunction)); + } + + @Test + public void testEncodeMultiDimDynamicArrayFunction() { + // setGetMultiDimDynamicArrayFunction(uint256[][][]) + // [[[1]],[[2,3]],[[4,5],[6,7,8]]] + String expected = + "bec12ef4" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000003" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "00000000000000000000000000000000000000000000000000000000000000e0" + + "0000000000000000000000000000000000000000000000000000000000000180" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000003" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "00000000000000000000000000000000000000000000000000000000000000a0" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "0000000000000000000000000000000000000000000000000000000000000005" + + "0000000000000000000000000000000000000000000000000000000000000003" + + "0000000000000000000000000000000000000000000000000000000000000006" + + "0000000000000000000000000000000000000000000000000000000000000007" + + "0000000000000000000000000000000000000000000000000000000000000008"; + + assertEquals(expected, FunctionEncoder.encode(AbiV2TestFixture.setGetMultiDimDynamicArrayFunction)); + } + + @Test + void testEncodeMultiDimStaticArrayFunction() { + // testNestedLongString(string[2][2]) + // [["12345...","short1"],["short2","short3"]] + Function function = AbiV2TestFixture.setMultiDimStaticArrayWithUtf8StringSubTypeFunction(); + + String encoded = FunctionEncoder.encode(function); + String expected = + "a30bf9b2" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000140" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "00000000000000000000000000000000000000000000000000000000000000c0" + + "0000000000000000000000000000000000000000000000000000000000000050" + + "3132333435363738393031323334353637383930313233343536373839303132" + + "3334353637383930313233343536373839303132333435363738393031323334" + + "3536373839303132333435363738393000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000006" + + "73686f7274310000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000006" + + "73686f7274320000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000006" + + "73686f7274330000000000000000000000000000000000000000000000000000"; + assertEquals(expected, encoded); + } + } diff --git a/abi/src/test/java/org/tron/trident/abi/EventEncoderTest.java b/abi/src/test/java/org/tron/trident/abi/EventEncoderTest.java index b31351c0..7f46f98f 100644 --- a/abi/src/test/java/org/tron/trident/abi/EventEncoderTest.java +++ b/abi/src/test/java/org/tron/trident/abi/EventEncoderTest.java @@ -18,7 +18,10 @@ import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.Test; +import org.tron.trident.abi.datatypes.DynamicArray; +import org.tron.trident.abi.datatypes.DynamicStruct; import org.tron.trident.abi.datatypes.Event; +import org.tron.trident.abi.datatypes.generated.StaticArray2; import org.tron.trident.abi.datatypes.generated.Uint256; public class EventEncoderTest { @@ -61,4 +64,111 @@ public void testBuildMethodSignature() { EventEncoder.buildMethodSignature("Notify", Utils.convert(parameters)), "Notify(uint256,uint256)"); } + + @Test + void testBuildMethodSignatureWithDynamicStructs() { + assertEquals( + "nazzEvent((((string,string)[])[],uint256),(string,string))", + EventEncoder.buildMethodSignature( + AbiV2TestFixture.nazzEvent.getName(), + AbiV2TestFixture.nazzEvent.getParameters())); + } + + @Test + void testBuildMethodSignatureWithDynamicArrays() { + assertEquals( + "nazzEvent2((((string,string)[])[],uint256)[])", + EventEncoder.buildMethodSignature( + AbiV2TestFixture.nazzEvent2.getName(), + AbiV2TestFixture.nazzEvent2.getParameters())); + } + + @Test + void testBuildMethodSignatureWithNestedStaticArray() { + List> parameters = + Arrays.asList(new TypeReference>>() { + }); + + assertEquals( + "Nested(uint256[2][])", + EventEncoder.buildMethodSignature("Nested", Utils.convert(parameters))); + } + + @Test + void testBuildMethodSignatureWithInnerTypesTuple() throws ClassNotFoundException { + List> tupleFields = + Arrays.asList( + TypeReference.makeTypeReference("uint256"), + TypeReference.makeTypeReference("string")); + List> parameters = + Arrays.asList(new TypeReference(false, tupleFields) { + }); + + assertEquals( + "E((uint256,string))", + EventEncoder.buildMethodSignature("E", Utils.convert(parameters))); + } + + @Test + void testBuildMethodSignatureWithNestedInnerTypesTuple() throws ClassNotFoundException { + List> innerFields = + Arrays.asList( + TypeReference.makeTypeReference("uint256"), + TypeReference.makeTypeReference("string")); + List> outerFields = + Arrays.asList( + new TypeReference(false, innerFields) { + }, + TypeReference.makeTypeReference("bool")); + List> parameters = + Arrays.asList(new TypeReference(false, outerFields) { + }); + + assertEquals( + "E(((uint256,string),bool))", + EventEncoder.buildMethodSignature("E", Utils.convert(parameters))); + } + + @Test + void testBuildMethodSignatureWithArrayOfInnerTypesTuple() throws ClassNotFoundException { + List> tupleFields = + Arrays.asList( + TypeReference.makeTypeReference("uint256"), + TypeReference.makeTypeReference("string")); + final TypeReference tupleRef = + new TypeReference(false, tupleFields) { + }; + List> parameters = + Arrays.asList( + new TypeReference() { + @Override + public TypeReference getSubTypeReference() { + return tupleRef; + } + }); + + assertEquals( + "E((uint256,string)[])", + EventEncoder.buildMethodSignature("E", Utils.convert(parameters))); + } + + @Test + void testInnerTypesTupleSignatureMatchesGeneratedClass() throws ClassNotFoundException { + // AbiV2TestFixture.Foo is a generated-class style struct of (string,string); + // the runtime innerTypes style must produce the identical signature. + List> fooFields = + Arrays.asList( + TypeReference.makeTypeReference("string"), + TypeReference.makeTypeReference("string")); + List> runtimeStyle = + Arrays.asList(new TypeReference(false, fooFields) { + }); + List> generatedStyle = + Arrays.asList(new TypeReference() { + }); + + assertEquals( + EventEncoder.buildMethodSignature("E", Utils.convert(generatedStyle)), + EventEncoder.buildMethodSignature("E", Utils.convert(runtimeStyle))); + } } diff --git a/abi/src/test/java/org/tron/trident/abi/FunctionEncoderLegacySubclassTest.java b/abi/src/test/java/org/tron/trident/abi/FunctionEncoderLegacySubclassTest.java new file mode 100644 index 00000000..4cc0ada6 --- /dev/null +++ b/abi/src/test/java/org/tron/trident/abi/FunctionEncoderLegacySubclassTest.java @@ -0,0 +1,61 @@ +package org.tron.trident.abi; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Collections; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.tron.trident.abi.datatypes.Function; +import org.tron.trident.abi.datatypes.Type; + +/** + * A FunctionEncoder subclass implementing only encodeFunction and + * encodeParameters keeps serving those entry points; the selector/packed entry + * points throw UnsupportedOperationException instead of AbstractMethodError. + */ +public class FunctionEncoderLegacySubclassTest { + + static class LegacyEncoder extends FunctionEncoder { + @Override + protected String encodeFunction(Function function) { + return "legacy-function"; + } + + @Override + protected String encodeParameters(List parameters) { + return "legacy-parameters"; + } + } + + @Test + public void testOriginalContractStillServed() { + LegacyEncoder encoder = new LegacyEncoder(); + + assertEquals( + "legacy-function", + encoder.encodeFunction( + new Function("f", Collections.emptyList(), Collections.emptyList()))); + assertEquals("legacy-parameters", encoder.encodeParameters(Collections.emptyList())); + } + + @Test + public void testNewEntryPointsThrowWithClearMessage() { + LegacyEncoder encoder = new LegacyEncoder(); + + UnsupportedOperationException selectorEx = assertThrows( + UnsupportedOperationException.class, + () -> encoder.encodeWithSelector("12345678", Collections.emptyList())); + assertTrue(selectorEx.getMessage().contains("encodeWithSelector"), + "expected method name in message, got: " + selectorEx.getMessage()); + assertTrue(selectorEx.getMessage().contains(LegacyEncoder.class.getName()), + "expected subclass name in message, got: " + selectorEx.getMessage()); + + UnsupportedOperationException packedEx = assertThrows( + UnsupportedOperationException.class, + () -> encoder.encodePackedParameters(Collections.emptyList())); + assertTrue(packedEx.getMessage().contains("encodePackedParameters"), + "expected method name in message, got: " + packedEx.getMessage()); + } +} diff --git a/abi/src/test/java/org/tron/trident/abi/FunctionReturnDecoderTest.java b/abi/src/test/java/org/tron/trident/abi/FunctionReturnDecoderTest.java index 635b17f5..d635c961 100644 --- a/abi/src/test/java/org/tron/trident/abi/FunctionReturnDecoderTest.java +++ b/abi/src/test/java/org/tron/trident/abi/FunctionReturnDecoderTest.java @@ -13,8 +13,9 @@ package org.tron.trident.abi; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertNull; import java.math.BigInteger; import java.util.ArrayList; @@ -25,14 +26,18 @@ import org.junit.jupiter.api.Test; 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.Function; 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.Uint; import org.tron.trident.abi.datatypes.Utf8String; import org.tron.trident.abi.datatypes.generated.Bytes16; import org.tron.trident.abi.datatypes.generated.Bytes32; import org.tron.trident.abi.datatypes.generated.StaticArray2; +import org.tron.trident.abi.datatypes.generated.StaticArray3; +import org.tron.trident.abi.datatypes.generated.StaticArray4; import org.tron.trident.abi.datatypes.generated.Uint256; import org.tron.trident.crypto.Hash; import org.tron.trident.utils.Numeric; @@ -50,7 +55,7 @@ public void testSimpleFunctionDecode() { assertEquals( FunctionReturnDecoder.decode( - "0x0000000000000000000000000000000000000000000000000000000000000037", + "0000000000000000000000000000000000000000000000000000000000000037", function.getOutputParameters()), (Collections.singletonList(new Uint(BigInteger.valueOf(55))))); } @@ -66,7 +71,7 @@ public void testSimpleFunctionStringResultDecode() { List utf8Strings = FunctionReturnDecoder.decode( - "0x0000000000000000000000000000000000000000000000000000000000000020" + "0000000000000000000000000000000000000000000000000000000000000020" + "000000000000000000000000000000000000000000000000000000000000000d" + "6f6e65206d6f72652074696d6500000000000000000000000000000000000000", function.getOutputParameters()); @@ -85,7 +90,7 @@ public void testFunctionEmptyStringResultDecode() { List utf8Strings = FunctionReturnDecoder.decode( - "0x0000000000000000000000000000000000000000000000000000000000000020" + "0000000000000000000000000000000000000000000000000000000000000020" + "0000000000000000000000000000000000000000000000000000000000000000", function.getOutputParameters()); @@ -104,7 +109,7 @@ public void testMultipleResultFunctionDecode() { assertEquals( FunctionReturnDecoder.decode( - "0x0000000000000000000000000000000000000000000000000000000000000037" + "0000000000000000000000000000000000000000000000000000000000000037" + "0000000000000000000000000000000000000000000000000000000000000007", function.getOutputParameters()), (Arrays.asList(new Uint(BigInteger.valueOf(55)), new Uint(BigInteger.valueOf(7))))); @@ -128,7 +133,7 @@ public void testDecodeMultipleStringValues() { assertEquals( FunctionReturnDecoder.decode( - "0x0000000000000000000000000000000000000000000000000000000000000080" + "0000000000000000000000000000000000000000000000000000000000000080" + "00000000000000000000000000000000000000000000000000000000000000c0" + "0000000000000000000000000000000000000000000000000000000000000100" + "0000000000000000000000000000000000000000000000000000000000000140" @@ -159,7 +164,7 @@ public void testDecodeStaticArrayValue() { List decoded = FunctionReturnDecoder.decode( - "0x0000000000000000000000000000000000000000000000000000000000000037" + "0000000000000000000000000000000000000000000000000000000000000037" + "0000000000000000000000000000000000000000000000000000000000000001" + "000000000000000000000000000000000000000000000000000000000000000a", outputParameters); @@ -220,7 +225,7 @@ public void testDecodeIndexedStringValue() { @Test public void testDecodeIndexedBytes32Value() { - String rawInput = "0x1234567890123456789012345678901234567890123456789012345678901234"; + String rawInput = "1234567890123456789012345678901234567890123456789012345678901234"; byte[] rawInputBytes = Numeric.hexStringToByteArray(rawInput); Assertions.assertEquals( @@ -271,7 +276,7 @@ public void testDecodeIndexedDynamicArrayValue() { @Test public void testDecodeStaticStruct() { String rawInput = - "0x0000000000000000000000000000000000000000000000000000000000000001" + "0000000000000000000000000000000000000000000000000000000000000001" + "0000000000000000000000000000000000000000000000000000000000000064"; assertEquals( @@ -284,7 +289,7 @@ public void testDecodeStaticStruct() { @Test public void testDecodeDynamicStruct() { String rawInput = - "0x0000000000000000000000000000000000000000000000000000000000000020" + "0000000000000000000000000000000000000000000000000000000000000020" + "0000000000000000000000000000000000000000000000000000000000000040" + "0000000000000000000000000000000000000000000000000000000000000080" + "0000000000000000000000000000000000000000000000000000000000000002" @@ -298,10 +303,28 @@ public void testDecodeDynamicStruct() { Collections.singletonList(new AbiV2TestFixture.Foo("id", "name"))); } + @Test + public void testDecodeDynamicStruct3() { + AbiV2TestFixture.Nazz nazz = + new AbiV2TestFixture.Nazz( + Collections.singletonList( + new AbiV2TestFixture.Nazzy( + Arrays.asList( + new AbiV2TestFixture.Foo("a", "b"), + new AbiV2TestFixture.Foo("c", "d")))), + new BigInteger("100")); + String rawInput = FunctionEncoder.encodeConstructor(Collections.singletonList(nazz)); + + List decoded = + FunctionReturnDecoder.decode( + rawInput, AbiV2TestFixture.getNazzFunction.getOutputParameters()); + assertEquals(Collections.singletonList(nazz).get(0).toString(), decoded.get(0).toString()); + } + @Test public void testDecodeDynamicStruct2() { String rawInput = - "0x0000000000000000000000000000000000000000000000000000000000000020" + "0000000000000000000000000000000000000000000000000000000000000020" + "0000000000000000000000000000000000000000000000000000000000000001" + "0000000000000000000000000000000000000000000000000000000000000040" + "0000000000000000000000000000000000000000000000000000000000000002" @@ -316,7 +339,7 @@ public void testDecodeDynamicStruct2() { @Test public void testDecodeStaticStructNested() { String rawInput = - "0x0000000000000000000000000000000000000000000000000000000000000001" + "0000000000000000000000000000000000000000000000000000000000000001" + "000000000000000000000000000000000000000000000000000000000000000a" + "0000000000000000000000000000000000000000000000000000000000000001"; @@ -332,7 +355,7 @@ public void testDecodeStaticStructNested() { @Test public void testDynamicStructNestedEncode() { String rawInput = - "0x0000000000000000000000000000000000000000000000000000000000000020" + "0000000000000000000000000000000000000000000000000000000000000020" + "0000000000000000000000000000000000000000000000000000000000000020" + "0000000000000000000000000000000000000000000000000000000000000040" + "0000000000000000000000000000000000000000000000000000000000000080" @@ -351,9 +374,421 @@ public void testDynamicStructNestedEncode() { @Test public void testDecodeTupleDynamicStructNested() { String rawInput = - "0x0000000000000000000000000000000000000000000000000000000000000060" + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "000000000000000000000000000000000000000000000000000000000000000a" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000"; + + assertEquals(FunctionReturnDecoder.decode( + rawInput, + AbiV2TestFixture.getFooBarFunction.getOutputParameters()), + Arrays.asList( + new AbiV2TestFixture.Foo("id", "name"), + new AbiV2TestFixture.Bar(BigInteger.ONE, BigInteger.TEN))); + } + + @Test + public void testDecodeMultipleDynamicStruct() { + String rawInput = + "00000000000000000000000000000000000000000000000000000000000000a0" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "000000000000000000000000000000000000000000000000000000000000000a" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "000000000000000000000000000000000000000000000000000000000000000b" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000"; + + assertEquals( + FunctionReturnDecoder.decode( + rawInput, AbiV2TestFixture.getFooBarBarFunction.getOutputParameters()), + Arrays.asList( + new AbiV2TestFixture.Foo("id", "name"), + new AbiV2TestFixture.Bar(BigInteger.ONE, BigInteger.TEN), + new AbiV2TestFixture.Bar(BigInteger.valueOf(2), BigInteger.valueOf(11)))); + } + + @Test + public void testDecodeMultipleDynamicStruct2() { + String rawInput = + "00000000000000000000000000000000000000000000000000000000000000c0" + + "0000000000000000000000000000000000000000000000000000000000000180" + "0000000000000000000000000000000000000000000000000000000000000001" + "000000000000000000000000000000000000000000000000000000000000000a" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "000000000000000000000000000000000000000000000000000000000000000b" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000"; + + assertEquals( + FunctionReturnDecoder.decode( + rawInput, AbiV2TestFixture.getFooFooBarBarFunction.getOutputParameters()), + Arrays.asList( + new AbiV2TestFixture.Foo("id", "name"), + new AbiV2TestFixture.Foo("id", "name"), + new AbiV2TestFixture.Bar(BigInteger.ONE, BigInteger.TEN), + new AbiV2TestFixture.Bar(BigInteger.valueOf(2), BigInteger.valueOf(11)))); + } + + @Test + public void testDecodeDynamicNested3() { + String rawInput = + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000"; + + assertEquals( + FunctionReturnDecoder.decode( + rawInput, AbiV2TestFixture.getNarFunction.getOutputParameters()), + Arrays.asList( + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo"))))); + } + + @Test + public void testDecodeMultipleDynamicStaticNestedStructs() { + String rawInput = + "0000000000000000000000000000000000000000000000000000000000000240" + + "000000000000000000000000000000000000000000000000000000000000007b" + + "000000000000000000000000000000000000000000000000000000000000007b" + + "000000000000000000000000000000000000000000000000000000000000007b" + + "000000000000000000000000000000000000000000000000000000000000007b" + + "00000000000000000000000000000000000000000000000000000000000004d1" + + "0000000000000000000000000000000000000000000000000000000000000079" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000340" + + "0000000000000000000000000000000000000000000000000000000000000400" + + "00000000000000000000000000000000000000000000000000000000000004d1" + + "0000000000000000000000000000000000000000000000000000000000000079" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000500" + + "00000000000000000000000000000000000000000000000000000000000005a0" + + "00000000000000000000000000000000000000000000000000000000000004d1" + + "0000000000000000000000000000000000000000000000000000000000000079" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6861686100000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "686f686f00000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6861686100000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "686f686f00000000000000000000000000000000000000000000000000000000"; + + assertEquals( + FunctionReturnDecoder.decode( + rawInput, + AbiV2TestFixture.getNarBarBarFuzzFooNarFuzzNuuFooFuzzFunction + .getOutputParameters()), + Arrays.asList( + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo"))), + new AbiV2TestFixture.Bar(BigInteger.valueOf(123), BigInteger.valueOf(123)), + new AbiV2TestFixture.Bar(BigInteger.valueOf(123), BigInteger.valueOf(123)), + new AbiV2TestFixture.Fuzz( + new AbiV2TestFixture.Bar( + BigInteger.valueOf(1233), BigInteger.valueOf(121)), + BigInteger.valueOf(2)), + new AbiV2TestFixture.Foo("haha", "hoho"), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo"))), + new AbiV2TestFixture.Fuzz( + new AbiV2TestFixture.Bar( + BigInteger.valueOf(1233), BigInteger.valueOf(121)), + BigInteger.valueOf(2)), + new AbiV2TestFixture.Nuu(new AbiV2TestFixture.Foo("", "")), + new AbiV2TestFixture.Foo("haha", "hoho"), + new AbiV2TestFixture.Fuzz( + new AbiV2TestFixture.Bar( + BigInteger.valueOf(1233), BigInteger.valueOf(121)), + BigInteger.valueOf(2)))); + } + + @Test + @SuppressWarnings("unchecked") + public void testDecodeDynamicStructDynamicArray() { + String rawInput = + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000"; + + assertEquals( + FunctionReturnDecoder.decode( + rawInput, + AbiV2TestFixture.getFooDynamicArrayFunction.getOutputParameters()), + Arrays.asList( + new DynamicArray( + AbiV2TestFixture.Foo.class, + new AbiV2TestFixture.Foo("id", "name")))); + } + + @Test + @SuppressWarnings("unchecked") + public void testDecodeStaticStructStaticArray() { + String rawInput = + "0x0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "000000000000000000000000000000000000000000000000000000000000007b" + + "000000000000000000000000000000000000000000000000000000000000007b" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000"; + + assertEquals( + FunctionReturnDecoder.decode( + rawInput, AbiV2TestFixture.getBarStaticArrayFunction.getOutputParameters()), + Arrays.asList( + new StaticArray3( + AbiV2TestFixture.Bar.class, + new AbiV2TestFixture.Bar( + BigInteger.valueOf(0), BigInteger.valueOf(0)), + new AbiV2TestFixture.Bar( + BigInteger.valueOf(123), BigInteger.valueOf(123)), + new AbiV2TestFixture.Bar( + BigInteger.valueOf(0), BigInteger.valueOf(0))))); + } + + @Test + @SuppressWarnings("unchecked") + public void testDecodeDynamicStructStaticArray() { + String rawInput = + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000160" + + "0000000000000000000000000000000000000000000000000000000000000220" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000"; + + assertEquals( + FunctionReturnDecoder.decode( + rawInput, AbiV2TestFixture.getNarStaticArrayFunction.getOutputParameters()), + Arrays.asList( + new StaticArray3( + AbiV2TestFixture.Nar.class, + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo"))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu(new AbiV2TestFixture.Foo("", ""))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo")))))); + } + + @Test + @SuppressWarnings("unchecked") + public void testDecodeDynamicStructDynamicArray2() { + String rawInput = + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000003" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000160" + + "0000000000000000000000000000000000000000000000000000000000000260" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000"; + + assertEquals( + FunctionReturnDecoder.decode( + rawInput, + AbiV2TestFixture.getNarDynamicArrayFunction.getOutputParameters()), + Arrays.asList( + new DynamicArray( + AbiV2TestFixture.Nar.class, + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo"))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo"))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("", "")))))); + } + + @Test + public void testDecodeMultipleDynamicStructStaticDynamicArrays() { + String rawInput = + "0000000000000000000000000000000000000000000000000000000000000140" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "000000000000000000000000000000000000000000000000000000000000007b" + + "000000000000000000000000000000000000000000000000000000000000007b" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000460" + + "0000000000000000000000000000000000000000000000000000000000000560" + + "00000000000000000000000000000000000000000000000000000000000008a0" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000160" + + "0000000000000000000000000000000000000000000000000000000000000220" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000003" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000160" + + "0000000000000000000000000000000000000000000000000000000000000260" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000120" + + "00000000000000000000000000000000000000000000000000000000000001e0" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000040" + "0000000000000000000000000000000000000000000000000000000000000080" + "0000000000000000000000000000000000000000000000000000000000000002" @@ -361,19 +796,954 @@ public void testDecodeTupleDynamicStructNested() { + "0000000000000000000000000000000000000000000000000000000000000004" + "6e616d6500000000000000000000000000000000000000000000000000000000"; - assertThrows( + assertEquals( + FunctionReturnDecoder.decode( + rawInput, + AbiV2TestFixture.getNarBarFooNarFooDynamicArrayFunction + .getOutputParameters()), + Arrays.asList( + new StaticArray3<>( + AbiV2TestFixture.Nar.class, + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo"))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu(new AbiV2TestFixture.Foo("", ""))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo")))), + new StaticArray3<>( + AbiV2TestFixture.Bar.class, + new AbiV2TestFixture.Bar(BigInteger.ZERO, BigInteger.ZERO), + new AbiV2TestFixture.Bar( + BigInteger.valueOf(123), BigInteger.valueOf(123)), + new AbiV2TestFixture.Bar(BigInteger.ZERO, BigInteger.ZERO)), + new DynamicArray<>( + AbiV2TestFixture.Foo.class, new AbiV2TestFixture.Foo("id", "name")), + new DynamicArray<>( + AbiV2TestFixture.Nar.class, + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo"))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo"))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("", "")))), + new StaticArray3<>( + AbiV2TestFixture.Foo.class, + new AbiV2TestFixture.Foo("id", "name"), + new AbiV2TestFixture.Foo("id", "name"), + new AbiV2TestFixture.Foo("id", "name")))); + } + + @Test + public void testDecodeStructMultipleDynamicStaticArray() { + String rawInput = + "0000000000000000000000000000000000000000000000000000000000000140" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "000000000000000000000000000000000000000000000000000000000000007b" + + "000000000000000000000000000000000000000000000000000000000000007b" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000480" + + "0000000000000000000000000000000000000000000000000000000000000580" + + "00000000000000000000000000000000000000000000000000000000000008c0" + + "0000000000000000000000000000000000000000000000000000000000000003" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000160" + + "0000000000000000000000000000000000000000000000000000000000000260" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000003" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000160" + + "0000000000000000000000000000000000000000000000000000000000000260" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000"; + + assertEquals( + FunctionReturnDecoder.decode( + rawInput, + AbiV2TestFixture.idNarBarFooNarFooArraysFunction.getOutputParameters()), + Arrays.asList( + new DynamicArray<>( + AbiV2TestFixture.Nar.class, + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo"))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo"))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("", "")))), + new StaticArray3<>( + AbiV2TestFixture.Bar.class, + new AbiV2TestFixture.Bar(BigInteger.ZERO, BigInteger.ZERO), + new AbiV2TestFixture.Bar( + BigInteger.valueOf(123), BigInteger.valueOf(123)), + new AbiV2TestFixture.Bar(BigInteger.ZERO, BigInteger.ZERO)), + new DynamicArray<>( + AbiV2TestFixture.Foo.class, new AbiV2TestFixture.Foo("id", "name")), + new DynamicArray<>( + AbiV2TestFixture.Nar.class, + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo"))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo"))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("", "")))), + new DynamicArray<>( + AbiV2TestFixture.Foo.class, + new AbiV2TestFixture.Foo("id", "name")))); + } + + @Test + public void testDecodeStructMultipleDynamicStaticArray2() { + String rawInput = + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "000000000000000000000000000000000000000000000000000000000000007b" + + "000000000000000000000000000000000000000000000000000000000000007b" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000140" + + "0000000000000000000000000000000000000000000000000000000000000460" + + "0000000000000000000000000000000000000000000000000000000000000560" + + "00000000000000000000000000000000000000000000000000000000000008a0" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000160" + + "0000000000000000000000000000000000000000000000000000000000000220" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000003" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000160" + + "0000000000000000000000000000000000000000000000000000000000000260" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000120" + + "00000000000000000000000000000000000000000000000000000000000001e0" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000"; + + assertEquals( + FunctionReturnDecoder.decode( + rawInput, + AbiV2TestFixture.idBarNarFooNarFooArraysFunction.getOutputParameters()), + Arrays.asList( + new StaticArray3<>( + AbiV2TestFixture.Bar.class, + new AbiV2TestFixture.Bar(BigInteger.ZERO, BigInteger.ZERO), + new AbiV2TestFixture.Bar( + BigInteger.valueOf(123), BigInteger.valueOf(123)), + new AbiV2TestFixture.Bar(BigInteger.ZERO, BigInteger.ZERO)), + new StaticArray3<>( + AbiV2TestFixture.Nar.class, + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo"))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu(new AbiV2TestFixture.Foo("", ""))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo")))), + new DynamicArray<>( + AbiV2TestFixture.Foo.class, new AbiV2TestFixture.Foo("id", "name")), + new DynamicArray<>( + AbiV2TestFixture.Nar.class, + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo"))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo"))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("", "")))), + new StaticArray3<>( + AbiV2TestFixture.Foo.class, + new AbiV2TestFixture.Foo("id", "name"), + new AbiV2TestFixture.Foo("id", "name"), + new AbiV2TestFixture.Foo("id", "name")))); + } + + @Test + public void testDecodeStructMultipleDynamicStaticArray3() { + String rawInput = + "00000000000000000000000000000000000000000000000000000000000000a0" + + "00000000000000000000000000000000000000000000000000000000000003c0" + + "00000000000000000000000000000000000000000000000000000000000004a0" + + "00000000000000000000000000000000000000000000000000000000000005a0" + + "00000000000000000000000000000000000000000000000000000000000008e0" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000160" + + "0000000000000000000000000000000000000000000000000000000000000220" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000003" + + "000000000000000000000000000000000000000000000000000000000000007b" + + "000000000000000000000000000000000000000000000000000000000000007b" + + "000000000000000000000000000000000000000000000000000000000000000c" + + "0000000000000000000000000000000000000000000000000000000000000021" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000003" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000160" + + "0000000000000000000000000000000000000000000000000000000000000260" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "3400000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "6e6573746564466f6f0000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000120" + + "00000000000000000000000000000000000000000000000000000000000001e0" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000"; + + assertEquals( + FunctionReturnDecoder.decode( + rawInput, + AbiV2TestFixture.idNarBarFooNarFooArraysFunction2.getOutputParameters()), + Arrays.asList( + new StaticArray3<>( + AbiV2TestFixture.Nar.class, + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo"))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu(new AbiV2TestFixture.Foo("", ""))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo")))), + new DynamicArray<>( + AbiV2TestFixture.Bar.class, + new AbiV2TestFixture.Bar( + BigInteger.valueOf(123), BigInteger.valueOf(123)), + new AbiV2TestFixture.Bar( + BigInteger.valueOf(12), BigInteger.valueOf(33)), + new AbiV2TestFixture.Bar(BigInteger.ZERO, BigInteger.ZERO)), + new DynamicArray<>( + AbiV2TestFixture.Foo.class, new AbiV2TestFixture.Foo("id", "name")), + new DynamicArray<>( + AbiV2TestFixture.Nar.class, + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo"))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("4", "nestedFoo"))), + new AbiV2TestFixture.Nar( + new AbiV2TestFixture.Nuu( + new AbiV2TestFixture.Foo("", "")))), + new StaticArray3<>( + AbiV2TestFixture.Foo.class, + new AbiV2TestFixture.Foo("id", "name"), + new AbiV2TestFixture.Foo("id", "name"), + new AbiV2TestFixture.Foo("id", "name")))); + } + + @Test + @SuppressWarnings("unchecked") + public void testDecodeStaticStructDynamicArray() { + String rawInput = + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "000000000000000000000000000000000000000000000000000000000000007b" + + "000000000000000000000000000000000000000000000000000000000000007b" + + "000000000000000000000000000000000000000000000000000000000000007b" + + "000000000000000000000000000000000000000000000000000000000000007b"; + + assertEquals( + FunctionReturnDecoder.decode( + rawInput, + AbiV2TestFixture.getBarDynamicArrayFunction.getOutputParameters()), + Arrays.asList( + new DynamicArray( + AbiV2TestFixture.Bar.class, + new AbiV2TestFixture.Bar( + BigInteger.valueOf(123), BigInteger.valueOf(123)), + new AbiV2TestFixture.Bar( + BigInteger.valueOf(123), BigInteger.valueOf(123))))); + } + + @Test + public void testBuildDynamicArrayOfStaticStruct() throws ClassNotFoundException { + // This is a version of testDecodeStaticStructDynamicArray() that builds + // the decoding TypeReferences using inner types. + String rawInput = + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "000000000000000000000000000000000000000000000000000000000000007b" + + "000000000000000000000000000000000000000000000000000000000000007b" + + "000000000000000000000000000000000000000000000000000000000000007b" + + "000000000000000000000000000000000000000000000000000000000000007b"; + + // (uint256, uint256) static struct. + TypeReference staticStructTr = + new TypeReference( + false, + Arrays.asList( + TypeReference.makeTypeReference("uint256"), + TypeReference.makeTypeReference("uint256"))) { + }; + + // (uint256, uint256)[] dynamic array of static struct. + TypeReference dynamicArray = + new TypeReference(false) { + @Override + public TypeReference getSubTypeReference() { + return staticStructTr; + } + + @Override + public java.lang.reflect.Type getType() { + return new java.lang.reflect.ParameterizedType() { + @Override + public java.lang.reflect.Type[] getActualTypeArguments() { + return new java.lang.reflect.Type[] {staticStructTr.getType()}; + } + + @Override + public java.lang.reflect.Type getRawType() { + return DynamicArray.class; + } + + @Override + public java.lang.reflect.Type getOwnerType() { + return Class.class; + } + }; + } + }; + + List decodedData = + FunctionReturnDecoder.decode(rawInput, Utils.convert(Arrays.asList(dynamicArray))); + + List decodedArray = ((DynamicArray) decodedData.get(0)).getValue(); + + List decodedStaticStruct0 = ((StaticStruct) decodedArray.get(0)).getValue(); + assertEquals(decodedStaticStruct0.get(0).getValue(), BigInteger.valueOf(123)); + assertEquals(decodedStaticStruct0.get(1).getValue(), BigInteger.valueOf(123)); + + List decodedStaticStruct1 = ((StaticStruct) decodedArray.get(1)).getValue(); + assertEquals(decodedStaticStruct1.get(0).getValue(), BigInteger.valueOf(123)); + assertEquals(decodedStaticStruct1.get(1).getValue(), BigInteger.valueOf(123)); + } + + @Test + public void testDecodeTupleOfStaticArrays() { + List outputParameters = new ArrayList>(); + outputParameters.addAll( + Arrays.asList( + new TypeReference>() { + }, + new TypeReference>() { + })); + + // tuple of (strings string[4]{"", "", "", ""}, ints int[4]{0, 0, 0, 0}) + String rawInput = + "0x" + + "00000000000000000000000000000000000000000000000000000000000000a0" + // strings array offset + + "0000000000000000000000000000000000000000000000000000000000000000" // ints[0] + + "0000000000000000000000000000000000000000000000000000000000000000" // ints[1] + + "0000000000000000000000000000000000000000000000000000000000000000" // ints[2] + + "0000000000000000000000000000000000000000000000000000000000000000" // ints[3] + + "0000000000000000000000000000000000000000000000000000000000000080" + // offset strings[0] + + "00000000000000000000000000000000000000000000000000000000000000a0" + // offset strings[1] + + "00000000000000000000000000000000000000000000000000000000000000c0" + // offset strings[2] + + "00000000000000000000000000000000000000000000000000000000000000e0" + // offset strings[3] + + "0000000000000000000000000000000000000000000000000000000000000000" // strings[0] + + "0000000000000000000000000000000000000000000000000000000000000000" // strings[1] + + "0000000000000000000000000000000000000000000000000000000000000000" // strings[2] + + "0000000000000000000000000000000000000000000000000000000000000000"; // strings[3] + + assertEquals( + FunctionReturnDecoder.decode(rawInput, outputParameters), + Arrays.asList( + new StaticArray4( + Utf8String.class, + new Utf8String(""), + new Utf8String(""), + new Utf8String(""), + new Utf8String("")), + new StaticArray4( + Uint256.class, + new Uint256(0), + new Uint256(0), + new Uint256(0), + new Uint256(0)))); + } + + @Test + @SuppressWarnings("unchecked") + public void testDecodeDynamicStructWithStaticStruct() { + String rawInput = + "0x0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "000000000000000000000000000000000000000000000000000000000000000a" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6461746100000000000000000000000000000000000000000000000000000000"; + assertEquals( + FunctionReturnDecoder.decode( + rawInput, AbiV2TestFixture.getQuxFunction.getOutputParameters()), + Arrays.asList( + new AbiV2TestFixture.Qux( + new AbiV2TestFixture.Bar(BigInteger.ONE, BigInteger.TEN), "data"))); + } + + @Test + public void testBuildDynamicStructWithStaticStruct() throws ClassNotFoundException { + // This is a version of testDecodeDynamicStructWithStaticStruct() that builds + // the decoding TypeReferences using inner types. + String rawInput = + "0x0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "000000000000000000000000000000000000000000000000000000000000000a" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6461746100000000000000000000000000000000000000000000000000000000"; + // (uint256, uint256) static struct. + TypeReference staticStruct = + new TypeReference( + false, + Arrays.asList( + TypeReference.makeTypeReference("uint256"), + TypeReference.makeTypeReference("uint256"))) { + }; + + // ((uint256, uint256), string) dynamic struct containing static struct. + TypeReference dynamicStruct = + new TypeReference( + false, + Arrays.asList(staticStruct, TypeReference.makeTypeReference("string"))) { + }; + + List decodedData = + FunctionReturnDecoder.decode(rawInput, Utils.convert(Arrays.asList(dynamicStruct))); + List decodedDynamicStruct = ((DynamicStruct) decodedData.get(0)).getValue(); + + List decodedStaticStruct = ((StaticStruct) decodedDynamicStruct.get(0)).getValue(); + assertEquals(decodedStaticStruct.get(0).getValue(), BigInteger.ONE); + assertEquals(decodedStaticStruct.get(1).getValue(), BigInteger.TEN); + + assertEquals(decodedDynamicStruct.get(1).getValue(), "data"); + } + + @Test + public void testDynamicStructOfDynamicStructWithAdditionalParametersReturn() + throws ClassNotFoundException { + // Return data from 'testInputAndOutput' function of this contract + // https://sepolia.etherscan.io/address/0x009C10396226ECFE3E39b3f1AEFa072E37578e30#readContract + // struct MyStruct { + // uint256 value1; + // string value2; + // string value3; + // MyStruct2 value4; + // } + // + // struct MyStruct2 { + // string value1; + // string value2; + // } + // function testInputAndOutput(MyStruct memory struc) external pure + // returns(string memory valueBefore, MyStruct memory, string memory valueAfter) { + // + // return ("valuebefore", mystruc, "valueafter"); + // + // } + String returnedData = + "0x000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000000b76616c75656265666f72650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000001320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004313233340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000063078313233340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a76616c7565616674657200000000000000000000000000000000000000000000"; + + List> myStruct2Types = new ArrayList<>(); + List> myStructTypes = new ArrayList<>(); + List> myParameters = new ArrayList<>(); + + myStruct2Types.add(TypeReference.makeTypeReference("string")); + myStruct2Types.add(TypeReference.makeTypeReference("string")); + + myStructTypes.add(TypeReference.makeTypeReference("uint256")); + myStructTypes.add(TypeReference.makeTypeReference("string")); + myStructTypes.add(TypeReference.makeTypeReference("string")); + myStructTypes.add(new TypeReference(false, myStruct2Types) { + }); + + myParameters.add(TypeReference.makeTypeReference("string")); + myParameters.add(new TypeReference(false, myStructTypes) { + }); + + myParameters.add(TypeReference.makeTypeReference("string")); + + List decodedData = + FunctionReturnDecoder.decode(returnedData, Utils.convert(myParameters)); + + assertEquals(decodedData.get(0).getValue(), "valuebefore"); + + List structData = ((DynamicStruct) decodedData.get(1)).getValue(); + + assertEquals(structData.get(0).getValue(), BigInteger.valueOf(1)); + assertEquals(structData.get(1).getValue(), "2"); + assertEquals(structData.get(2).getValue(), "3"); + + List innerStructData = ((DynamicStruct) structData.get(3)).getValue(); + + assertEquals(innerStructData.get(0).getValue(), "1234"); + assertEquals(innerStructData.get(1).getValue(), "0x1234"); + + assertEquals(decodedData.get(2).getValue(), "valueafter"); + } + + @Test + public void testDynamicStructOfStaticStructReturn() throws ClassNotFoundException { + String returnedData = + "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000004746573740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000047465737400000000000000000000000000000000000000000000000000000000"; + + List> myStruct2Types = new ArrayList<>(); + List> myStructTypes = new ArrayList<>(); + + myStruct2Types.add(TypeReference.makeTypeReference("uint256")); + + myStructTypes.add(TypeReference.makeTypeReference("uint256")); + myStructTypes.add(TypeReference.makeTypeReference("string")); + myStructTypes.add(TypeReference.makeTypeReference("string")); + myStructTypes.add(new TypeReference(false, myStruct2Types) { + }); + + TypeReference dynamicStruct = + new TypeReference(false, myStructTypes) { + }; + + List decodedData = + FunctionReturnDecoder.decode( + returnedData, Utils.convert(Arrays.asList(dynamicStruct))); + + List decodedStruct = ((DynamicStruct) decodedData.get(0)).getValue(); + + assertEquals(decodedStruct.get(0).getValue(), BigInteger.valueOf(1)); + assertEquals(decodedStruct.get(1).getValue(), "test"); + assertEquals(decodedStruct.get(2).getValue(), "test"); + + List innerStructData = ((StaticStruct) decodedStruct.get(3)).getValue(); + + assertEquals(innerStructData.get(0).getValue(), BigInteger.valueOf(1)); + } + + @Test + public void testBuildEventOfArrayOfDynamicStruct() throws ClassNotFoundException { + // The full event signature is + // + // Stamp3(uint256 indexed stampId, address indexed caller, bool odd, + // (uint256,bool,string) topMessage, (uint256,bool,string)[] messages), + // + // but we are only decoding the non-indexed data portion of it represented by + // 'bool odd, (uint256,bool,string) topMessage, (uint256,bool,string)[] messages'. + // + // Transaction: + // https://testnet.treasurescan.io/tx/0x041e53e7571283d462df99a95b2c21324279657f26a3adef907095d2d9c5ed85?tab=logs + // Contract: + // https://testnet.treasurescan.io/address/0x5167E9A422aCEd95C2D0b62bF05a7847a9a942B2 + String data = + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000568656c6c6f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000015000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000002676d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000002676d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000017000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000002676d000000000000000000000000000000000000000000000000000000000000"; + + TypeReference tupleTr = + new TypeReference( + false, + Arrays.asList( + TypeReference.makeTypeReference("uint256"), + TypeReference.makeTypeReference("bool"), + TypeReference.makeTypeReference("string"))) { + }; + + // Decode data according to the above signature for the non-indexed parameters. + List decodedData = + FunctionReturnDecoder.decode( + data, + Utils.convert( + Arrays.asList( + // bool odd + TypeReference.makeTypeReference("bool"), + + // (uint256,bool,string) + tupleTr, + + // (uint256,bool,string)[] + new TypeReference(false) { + @Override + public TypeReference getSubTypeReference() { + return tupleTr; + } + + @Override + public java.lang.reflect.Type getType() { + return new java.lang.reflect.ParameterizedType() { + @Override + public java.lang.reflect.Type[] + getActualTypeArguments() { + return new java.lang.reflect.Type[] { + tupleTr.getType() + }; + } + + @Override + public java.lang.reflect.Type getRawType() { + return DynamicArray.class; + } + + @Override + public java.lang.reflect.Type getOwnerType() { + return Class.class; + } + }; + } + }))); + + assertEquals(decodedData.get(0).getValue(), false); + + List tupleData = ((DynamicStruct) decodedData.get(1)).getValue(); + + assertEquals(tupleData.get(0).getValue(), BigInteger.valueOf(20)); + assertEquals(tupleData.get(1).getValue(), false); + assertEquals(tupleData.get(2).getValue(), "hello"); + + List tupleArrayData = + ((DynamicArray) decodedData.get(2)).getValue(); + + List tupleArrayEntry0 = tupleArrayData.get(0).getValue(); + assertEquals(tupleArrayEntry0.get(0).getValue(), BigInteger.valueOf(21)); + assertEquals(tupleArrayEntry0.get(1).getValue(), true); + assertEquals(tupleArrayEntry0.get(2).getValue(), "gm"); + + List tupleArrayEntry1 = tupleArrayData.get(1).getValue(); + assertEquals(tupleArrayEntry1.get(0).getValue(), BigInteger.valueOf(22)); + assertEquals(tupleArrayEntry1.get(1).getValue(), false); + assertEquals(tupleArrayEntry1.get(2).getValue(), "gm"); + + List tupleArrayEntry2 = tupleArrayData.get(2).getValue(); + assertEquals(tupleArrayEntry2.get(0).getValue(), BigInteger.valueOf(23)); + assertEquals(tupleArrayEntry2.get(1).getValue(), true); + assertEquals(tupleArrayEntry2.get(2).getValue(), "gm"); + } + + @Test + public void testDecodeAddress() { + String address = FunctionReturnDecoder.decodeAddress("00000000000000000000000051231ac2bedf45d3ef980ed0a8422d86310b70ef"); + assertEquals("THNDnhjKW9QPCetsMmxq5ebX9Ufmyr1m1C", address); + } + + @Test + public void testDecodeDynamicBytes() { + byte[] bytes = FunctionReturnDecoder.decodeDynamicBytes(""); + assertNull( bytes); + + bytes = FunctionReturnDecoder.decodeDynamicBytes("0x"); + assertNull( bytes); + + String rawInput = FunctionEncoder.encodeConstructor(Collections.singletonList(new DynamicBytes("hello".getBytes()))); + bytes = FunctionReturnDecoder.decodeDynamicBytes(rawInput); + assertArrayEquals("hello".getBytes(), bytes); + } + + @Test + public void testDecodeMultiDimStaticArray() { + String data = "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000003" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "0000000000000000000000000000000000000000000000000000000000000005" + + "0000000000000000000000000000000000000000000000000000000000000006"; + List outPutArrays + = FunctionReturnDecoder.decode(data, AbiV2TestFixture.setGetMultiDimStaticArrayFunction.getOutputParameters()); + + StaticArray3> outputStaticArray = (StaticArray3>)outPutArrays.get(0); + + assertEquals(3, outputStaticArray.getValue().size()); + + @SuppressWarnings("unchecked") + StaticArray3> expected = + (StaticArray3>) AbiV2TestFixture.setGetMultiDimStaticArrayFunction + .getInputParameters() + .get(0); + + assertEquals(expected, outputStaticArray); + } + + @Test + public void testDecodeMultiDimDynamicArrayFunction() throws ClassNotFoundException { + //setGetMultiDimDynamicArrayFunction(uint256[][][]) + // [[[1]],[[2,3]],[[4,5],[6,7,8]]] + String data = "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000003" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "00000000000000000000000000000000000000000000000000000000000000e0" + + "0000000000000000000000000000000000000000000000000000000000000180" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000003" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "00000000000000000000000000000000000000000000000000000000000000a0" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "0000000000000000000000000000000000000000000000000000000000000005" + + "0000000000000000000000000000000000000000000000000000000000000003" + + "0000000000000000000000000000000000000000000000000000000000000006" + + "0000000000000000000000000000000000000000000000000000000000000007" + + "0000000000000000000000000000000000000000000000000000000000000008"; + + List outPutDynamicArrays + = FunctionReturnDecoder.decode(data, AbiV2TestFixture.setGetMultiDimDynamicArrayFunction.getOutputParameters()); + + @SuppressWarnings("unchecked") + DynamicArray>> outputDynamicArray = (DynamicArray>>)outPutDynamicArrays.get(0); + + @SuppressWarnings("unchecked") + DynamicArray>> expected = + (DynamicArray>> )AbiV2TestFixture.setGetMultiDimDynamicArrayFunction + .getInputParameters() + .get(0); + assertEquals(expected, outPutDynamicArrays.get(0)); + } + + /** + * A TypeReference whose reflective type names a class that cannot be loaded, + * forcing {@code getClassType()} to throw {@link ClassNotFoundException}. + */ + private static TypeReference unresolvableTypeReference() { + return new TypeReference() { + @Override + public java.lang.reflect.Type getType() { + return new java.lang.reflect.Type() { + @Override + public String getTypeName() { + return "org.tron.trident.abi.datatypes.DoesNotExist"; + } + }; + } + }; + } + + @Test + public void testGetDataOffsetThrowsCheckedExceptionForUnresolvableReference() { + // getDataOffset declares ClassNotFoundException; resolution failures must + // surface as that checked type, not as isDynamic's unchecked wrapper. + Assertions.assertThrows( + ClassNotFoundException.class, + () -> DefaultFunctionReturnDecoder.getDataOffset( + "0000000000000000000000000000000000000000000000000000000000000020", + 0, + unresolvableTypeReference())); + } + + @Test + public void testDecodeReportsInvalidClassReferenceForUnresolvableReference() { + List> refs = Collections.singletonList(unresolvableTypeReference()); + UnsupportedOperationException e = Assertions.assertThrows( UnsupportedOperationException.class, - () -> - FunctionReturnDecoder.decode( - rawInput, - AbiV2TestFixture.getFooBarFunction.getOutputParameters())); - - // assertEquals( - // FunctionReturnDecoder.decode( - // rawInput, - // AbiV2TestFixture.getFooBarFunction.getOutputParameters()), - // Arrays.asList( - // new AbiV2TestFixture.Foo("id", "name"), - // new AbiV2TestFixture.Bar(BigInteger.ONE, BigInteger.TEN))); + () -> FunctionReturnDecoder.decode( + "0x0000000000000000000000000000000000000000000000000000000000000020", + Utils.convert(refs))); + assertEquals("Invalid class reference provided", e.getMessage()); } } diff --git a/abi/src/test/java/org/tron/trident/abi/LocaleIndependenceTest.java b/abi/src/test/java/org/tron/trident/abi/LocaleIndependenceTest.java new file mode 100644 index 00000000..f3a6fdb9 --- /dev/null +++ b/abi/src/test/java/org/tron/trident/abi/LocaleIndependenceTest.java @@ -0,0 +1,45 @@ +package org.tron.trident.abi; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.math.BigInteger; +import java.util.Locale; +import org.junit.jupiter.api.Test; +import org.tron.trident.abi.datatypes.AbiTypes; +import org.tron.trident.abi.datatypes.DynamicArray; +import org.tron.trident.abi.datatypes.generated.Int256; + +/** + * ABI signature tokens are built by lowercasing Java class simple names; that + * conversion must not depend on the JVM default locale. The canonical failure + * mode is Turkish ("tr"), whose case mapping turns 'I' into dotless 'ı' — + * corrupting e.g. "Int256" into "ınt256" and silently changing selectors. + */ +public class LocaleIndependenceTest { + + @Test + public void testAbiTokensStableUnderTurkishLocale() throws Exception { + Locale original = Locale.getDefault(); + try { + Locale.setDefault(new Locale("tr", "TR")); + // Sanity: this locale really maps 'I' to dotless 'ı' by default. + assertEquals("ı", "I".toLowerCase()); + + // Class-name → token via the TypeReference path (Utils.getSimpleTypeName). + assertEquals("int256", Utils.getTypeName(TypeReference.create(Int256.class))); + + // Class-name → token via the instance path (AbiTypes.getTypeAString), + // as used by Array/Struct getTypeAsString. + assertEquals("int256", AbiTypes.getTypeAString(Int256.class)); + assertEquals( + "int256[]", + new DynamicArray<>(Int256.class, new Int256(BigInteger.ONE)).getTypeAsString()); + + // Primitive wrapper type names (PrimitiveType constructor). + assertEquals( + "int", new org.tron.trident.abi.datatypes.primitive.Int(1).getTypeAsString()); + } finally { + Locale.setDefault(original); + } + } +} diff --git a/abi/src/test/java/org/tron/trident/abi/StaticArrayInStructDecodeTest.java b/abi/src/test/java/org/tron/trident/abi/StaticArrayInStructDecodeTest.java new file mode 100644 index 00000000..4f2858f4 --- /dev/null +++ b/abi/src/test/java/org/tron/trident/abi/StaticArrayInStructDecodeTest.java @@ -0,0 +1,245 @@ +package org.tron.trident.abi; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.tron.trident.abi.datatypes.DynamicStruct; +import org.tron.trident.abi.datatypes.Function; +import org.tron.trident.abi.datatypes.StaticStruct; +import org.tron.trident.abi.datatypes.Type; +import org.tron.trident.abi.datatypes.Utf8String; +import org.tron.trident.abi.datatypes.generated.StaticArray2; +import org.tron.trident.abi.datatypes.generated.Uint256; +import org.tron.trident.abi.datatypes.reflection.Parameterized; + +/** + * Regression coverage: decoding a struct that DIRECTLY contains a fixed-size array member (e.g. Solidity + * {@code struct S { uint256 a; uint256[2] b; }}) crashes. When the struct TypeReference is + * built the normal way ({@code new TypeReference(){}}, i.e. {@code innerTypes == null}), + * {@link DefaultFunctionReturnDecoder#build} dispatches to the reflection path + * {@code TypeDecoder.decodeStaticStructElement} (and {@code decodeDynamicStructElements} for + * dynamic structs), which has no {@code StaticArray} branch: the {@code StaticArrayN} field + * falls into {@code decode(input.substring(...), 0, StaticArrayN.class)} which throws + * {@code UnsupportedOperationException("Array types must be wrapped in a TypeReference")}. + * + *

The existing AbiV2TestFixture structs only cover scalar fields (Bar), nested-struct + * fields (Fuzz) and dynamic-array fields (ArrayStruct) — never a fixed-size array member — + * which is why this path was never exercised. + */ +public class StaticArrayInStructDecodeTest { + + /** Solidity: {@code struct StaticArrayInStruct { uint256 a; uint256[2] b; }} (all-static). */ + public static class StaticArrayInStruct extends StaticStruct { + public StaticArrayInStruct( + Uint256 a, @Parameterized(type = Uint256.class) StaticArray2 b) { + super(a, b); + } + } + + // ABI encoding of StaticArrayInStruct(a = 1, b = [2, 3]). + // All-static struct => inline, three 32-byte words: [a, b[0], b[1]]. + private static final String ENCODED = + "0000000000000000000000000000000000000000000000000000000000000001" // a = 1 + + "0000000000000000000000000000000000000000000000000000000000000002" // b[0] = 2 + + "0000000000000000000000000000000000000000000000000000000000000003"; // b[1] = 3 + + private static Function decodeFunction() { + return new Function( + "getStaticArrayInStruct", + Collections.emptyList(), + Arrays.asList(new TypeReference() {})); + } + + /** + * Regression guard: a static struct that directly contains a fixed-size array + * member must decode via the reflection path ({@code new TypeReference(){}}, + * {@code innerTypes == null}). Before the fix this threw + * {@code UnsupportedOperationException("Array types must be wrapped in a TypeReference")}. + */ + @Test + @SuppressWarnings("unchecked") + public void decodesStaticArrayMemberViaReflectionPath() { + Function function = decodeFunction(); + + List decoded = FunctionReturnDecoder.decode(ENCODED, function.getOutputParameters()); + + StaticArrayInStruct s = (StaticArrayInStruct) decoded.get(0); + assertEquals(BigInteger.ONE, ((Uint256) s.getValue().get(0)).getValue()); + + StaticArray2 b = (StaticArray2) s.getValue().get(1); + assertEquals(BigInteger.valueOf(2), b.getValue().get(0).getValue()); + assertEquals(BigInteger.valueOf(3), b.getValue().get(1).getValue()); + } + + /** Solidity: {@code struct DynStruct { string a; uint256[2] b; }} (dynamic, with static array). */ + public static class DynStruct extends DynamicStruct { + public DynStruct( + Utf8String a, @Parameterized(type = Uint256.class) StaticArray2 b) { + super(a, b); + } + } + + // ABI encoding of a function returning DynStruct(a = "hi", b = [2, 3]). + // Dynamic struct => outer offset pointer, then struct head [a-offset, b[0], b[1]], then a's tail. + private static final String ENCODED_DYNAMIC = + "0000000000000000000000000000000000000000000000000000000000000020" // outer offset -> struct + + "0000000000000000000000000000000000000000000000000000000000000060" // a: offset 0x60 (3 words) + + "0000000000000000000000000000000000000000000000000000000000000002" // b[0] = 2 + + "0000000000000000000000000000000000000000000000000000000000000003" // b[1] = 3 + + "0000000000000000000000000000000000000000000000000000000000000002" // a: length = 2 + + "6869000000000000000000000000000000000000000000000000000000000000"; // "hi" + + /** + * Regression guard on the dynamic-struct reflection path + * ({@code decodeDynamicStructElements}): a DynamicStruct with a static array member must decode. + */ + @Test + @SuppressWarnings("unchecked") + public void decodesStaticArrayMemberInDynamicStruct() { + Function function = + new Function( + "getDynStruct", + Collections.emptyList(), + Arrays.asList(new TypeReference() {})); + + List decoded = + FunctionReturnDecoder.decode(ENCODED_DYNAMIC, function.getOutputParameters()); + + DynStruct s = (DynStruct) decoded.get(0); + assertEquals("hi", ((Utf8String) s.getValue().get(0)).getValue()); + + StaticArray2 b = (StaticArray2) s.getValue().get(1); + assertEquals(BigInteger.valueOf(2), b.getValue().get(0).getValue()); + assertEquals(BigInteger.valueOf(3), b.getValue().get(1).getValue()); + } + + /** + * Solidity: {@code struct StringArrayStruct { uint256 a; string[2] b; }}. + * A fixed-size array of dynamic elements is itself ABI-dynamic: the struct head stores + * an offset for {@code b}, and b's tail is head/tail encoded (per-element offsets). + */ + public static class StringArrayStruct extends DynamicStruct { + public StringArrayStruct( + Uint256 a, @Parameterized(type = Utf8String.class) StaticArray2 b) { + super(a, b); + } + } + + // ABI encoding of StringArrayStruct(a = 1, b = ["hi", "world"]). + private static final String ENCODED_STRING_ARRAY = + "0000000000000000000000000000000000000000000000000000000000000020" // outer offset -> struct + + "0000000000000000000000000000000000000000000000000000000000000001" // a = 1 + + "0000000000000000000000000000000000000000000000000000000000000040" // b: offset 0x40 + + "0000000000000000000000000000000000000000000000000000000000000040" // b[0]: offset 0x40 (rel. to b) + + "0000000000000000000000000000000000000000000000000000000000000080" // b[1]: offset 0x80 (rel. to b) + + "0000000000000000000000000000000000000000000000000000000000000002" // b[0]: length = 2 + + "6869000000000000000000000000000000000000000000000000000000000000" // "hi" + + "0000000000000000000000000000000000000000000000000000000000000005" // b[1]: length = 5 + + "776f726c64000000000000000000000000000000000000000000000000000000"; // "world" + + /** + * A DynamicStruct whose fixed-size array member has DYNAMIC elements ({@code string[2]}) + * must decode on the constructor-reflection path ({@code new TypeReference(){}}). + * Before the fix, {@code decodeDynamicStructElements} misclassified the field as static + * (Class-level isDynamic check) and {@code decodeStaticArrayStructField} threw + * {@code UnsupportedOperationException}. + */ + @Test + @SuppressWarnings("unchecked") + public void decodesDynamicElementStaticArrayMemberInDynamicStruct() { + Function function = + new Function( + "getStringArrayStruct", + Collections.emptyList(), + Arrays.asList(new TypeReference() {})); + + List decoded = + FunctionReturnDecoder.decode(ENCODED_STRING_ARRAY, function.getOutputParameters()); + + StringArrayStruct s = (StringArrayStruct) decoded.get(0); + assertEquals(BigInteger.ONE, ((Uint256) s.getValue().get(0)).getValue()); + + StaticArray2 b = (StaticArray2) s.getValue().get(1); + assertEquals("hi", b.getValue().get(0).getValue()); + assertEquals("world", b.getValue().get(1).getValue()); + } + + /** The hand-written hex above must match what the encoder actually produces (round-trip). */ + @Test + public void stringArrayStructEncodingMatchesHandWrittenLayout() { + StringArrayStruct s = + new StringArrayStruct( + new Uint256(BigInteger.ONE), + new StaticArray2<>(Utf8String.class, new Utf8String("hi"), new Utf8String("world"))); + + // Struct encoding without the outer offset word (64 hex chars). + assertEquals(ENCODED_STRING_ARRAY.substring(64), TypeEncoder.encode(s)); + } + + /** + * Solidity: {@code struct MixedStruct { uint256 a; string[2] b; string c; }} — the + * dynamic-element fixed array is NOT the last dynamic member, exercising the head-slot + * bookkeeping (two offset slots) and the tail-boundary length math between b and c. + */ + public static class MixedStruct extends DynamicStruct { + public MixedStruct( + Uint256 a, + @Parameterized(type = Utf8String.class) StaticArray2 b, + Utf8String c) { + super(a, b, c); + } + } + + // ABI encoding of MixedStruct(a = 1, b = ["hi", "world"], c = "x"). + private static final String ENCODED_MIXED = + "0000000000000000000000000000000000000000000000000000000000000020" // outer offset -> struct + + "0000000000000000000000000000000000000000000000000000000000000001" // a = 1 + + "0000000000000000000000000000000000000000000000000000000000000060" // b: offset 0x60 + + "0000000000000000000000000000000000000000000000000000000000000120" // c: offset 0x120 + + "0000000000000000000000000000000000000000000000000000000000000040" // b[0]: offset 0x40 (rel. to b) + + "0000000000000000000000000000000000000000000000000000000000000080" // b[1]: offset 0x80 (rel. to b) + + "0000000000000000000000000000000000000000000000000000000000000002" // b[0]: length = 2 + + "6869000000000000000000000000000000000000000000000000000000000000" // "hi" + + "0000000000000000000000000000000000000000000000000000000000000005" // b[1]: length = 5 + + "776f726c64000000000000000000000000000000000000000000000000000000" // "world" + + "0000000000000000000000000000000000000000000000000000000000000001" // c: length = 1 + + "7800000000000000000000000000000000000000000000000000000000000000"; // "x" + + @Test + @SuppressWarnings("unchecked") + public void decodesDynamicElementStaticArrayFollowedByDynamicField() { + Function function = + new Function( + "getMixedStruct", + Collections.emptyList(), + Arrays.asList(new TypeReference() {})); + + List decoded = + FunctionReturnDecoder.decode(ENCODED_MIXED, function.getOutputParameters()); + + MixedStruct s = (MixedStruct) decoded.get(0); + assertEquals(BigInteger.ONE, ((Uint256) s.getValue().get(0)).getValue()); + + StaticArray2 b = (StaticArray2) s.getValue().get(1); + assertEquals("hi", b.getValue().get(0).getValue()); + assertEquals("world", b.getValue().get(1).getValue()); + + assertEquals("x", ((Utf8String) s.getValue().get(2)).getValue()); + } + + /** The hand-written hex above must match what the encoder actually produces (round-trip). */ + @Test + public void mixedStructEncodingMatchesHandWrittenLayout() { + MixedStruct s = + new MixedStruct( + new Uint256(BigInteger.ONE), + new StaticArray2<>(Utf8String.class, new Utf8String("hi"), new Utf8String("world")), + new Utf8String("x")); + + assertEquals(ENCODED_MIXED.substring(64), TypeEncoder.encode(s)); + } +} diff --git a/abi/src/test/java/org/tron/trident/abi/StructFieldReflectionDecodeTest.java b/abi/src/test/java/org/tron/trident/abi/StructFieldReflectionDecodeTest.java new file mode 100644 index 00000000..33747eb2 --- /dev/null +++ b/abi/src/test/java/org/tron/trident/abi/StructFieldReflectionDecodeTest.java @@ -0,0 +1,237 @@ +package org.tron.trident.abi; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.tron.trident.abi.datatypes.DynamicStruct; +import org.tron.trident.abi.datatypes.StaticStruct; +import org.tron.trident.abi.datatypes.TrcToken; +import org.tron.trident.abi.datatypes.Type; +import org.tron.trident.abi.datatypes.Utf8String; +import org.tron.trident.abi.datatypes.generated.StaticArray2; +import org.tron.trident.abi.datatypes.generated.Uint256; +import org.tron.trident.abi.datatypes.reflection.Parameterized; + +/** + * Regression tests for the constructor-reflection decode path + * ({@code new TypeReference() {}} with no innerTypes) on struct classes + * whose fields previously broke it: StaticArrayN members, nested static structs + * with non-public Java fields, and doubly-nested static structs. + */ +public class StructFieldReflectionDecodeTest { + + /** Solidity: struct Order { uint256[2] limits; string name; } */ + public static class Order extends DynamicStruct { + public Order(@Parameterized(type = Uint256.class) StaticArray2 limits, + Utf8String name) { + super(limits, name); + } + } + + /** Static struct whose Java fields are intentionally non-public. */ + public static class Point extends StaticStruct { + private final BigInteger x; + private final BigInteger y; + + public Point(Uint256 x, Uint256 y) { + super(x, y); + this.x = x.getValue(); + this.y = y.getValue(); + } + + public BigInteger getX() { + return x; + } + + public BigInteger getY() { + return y; + } + } + + /** Solidity: struct Person { Point location; string name; } */ + public static class Person extends DynamicStruct { + public Person(Point location, Utf8String name) { + super(location, name); + } + } + + public static class Inner extends StaticStruct { + public Inner(Uint256 x, Uint256 y) { + super(x, y); + } + } + + /** Nested static struct spanning 3 words, not 2 (its constructor parameter count). */ + public static class Mid extends StaticStruct { + public Mid(Inner inner, Uint256 d) { + super(inner, d); + } + } + + public static class Outer extends StaticStruct { + public Outer(Mid mid, Uint256 c) { + super(mid, c); + } + } + + /** Solidity: struct TokenPair { trcToken[2] tokens; string name; } */ + public static class TokenPair extends DynamicStruct { + public TokenPair(@Parameterized(type = TrcToken.class) StaticArray2 tokens, + Utf8String name) { + super(tokens, name); + } + } + + /** Solidity: struct Region { Point[2] corners; string name; } */ + public static class Region extends DynamicStruct { + public Region(@Parameterized(type = Point.class) StaticArray2 corners, + Utf8String name) { + super(corners, name); + } + } + + private static final String DYNAMIC_STRUCT_HEAD = + "0000000000000000000000000000000000000000000000000000000000000020"; + + @Test + public void testDecodeStructWithStaticArrayFieldViaReflection() { + Order order = new Order( + new StaticArray2<>(Uint256.class, + new Uint256(BigInteger.ONE), new Uint256(BigInteger.TEN)), + new Utf8String("hi")); + + String returnedData = DYNAMIC_STRUCT_HEAD + TypeEncoder.encode(order); + + List decoded = FunctionReturnDecoder.decode( + returnedData, + Utils.convert(Arrays.asList(new TypeReference() {}))); + + assertEquals(1, decoded.size()); + assertTrue(decoded.get(0) instanceof Order); + Order decodedOrder = (Order) decoded.get(0); + + @SuppressWarnings("unchecked") + StaticArray2 limits = (StaticArray2) decodedOrder.getValue().get(0); + assertEquals( + Arrays.asList(new Uint256(BigInteger.ONE), new Uint256(BigInteger.TEN)), + limits.getValue()); + assertEquals(new Utf8String("hi"), decodedOrder.getValue().get(1)); + } + + @Test + public void testDecodeStructWithPrivateFieldNestedStaticStruct() { + Person person = new Person( + new Point(new Uint256(BigInteger.valueOf(3)), new Uint256(BigInteger.valueOf(7))), + new Utf8String("alice")); + + String returnedData = DYNAMIC_STRUCT_HEAD + TypeEncoder.encode(person); + + List decoded = FunctionReturnDecoder.decode( + returnedData, + Utils.convert(Arrays.asList(new TypeReference() {}))); + + assertEquals(1, decoded.size()); + assertTrue(decoded.get(0) instanceof Person); + Person decodedPerson = (Person) decoded.get(0); + + Point location = (Point) decodedPerson.getValue().get(0); + assertEquals(BigInteger.valueOf(3), location.getX()); + assertEquals(BigInteger.valueOf(7), location.getY()); + assertEquals(new Utf8String("alice"), decodedPerson.getValue().get(1)); + } + + @Test + public void testDecodeStructWithTrcTokenStaticArrayFieldViaReflection() { + TokenPair pair = new TokenPair( + new StaticArray2<>(TrcToken.class, + new TrcToken(BigInteger.valueOf(1000016)), + new TrcToken(BigInteger.valueOf(1000017))), + new Utf8String("pair")); + + String returnedData = DYNAMIC_STRUCT_HEAD + TypeEncoder.encode(pair); + + List decoded = FunctionReturnDecoder.decode( + returnedData, + Utils.convert(Arrays.asList(new TypeReference() {}))); + + assertEquals(1, decoded.size()); + assertTrue(decoded.get(0) instanceof TokenPair); + TokenPair decodedPair = (TokenPair) decoded.get(0); + + @SuppressWarnings("unchecked") + StaticArray2 tokens = (StaticArray2) decodedPair.getValue().get(0); + assertEquals( + Arrays.asList( + new TrcToken(BigInteger.valueOf(1000016)), + new TrcToken(BigInteger.valueOf(1000017))), + tokens.getValue()); + assertEquals(new Utf8String("pair"), decodedPair.getValue().get(1)); + } + + @Test + public void testTrcTokenStaticArrayFieldSignature() { + assertEquals("(trcToken[2],string)", Utils.getStructType(TokenPair.class)); + } + + @Test + public void testDecodeStructWithStructElementStaticArrayViaReflection() { + Region region = new Region( + new StaticArray2<>(Point.class, + new Point(new Uint256(BigInteger.ONE), new Uint256(BigInteger.valueOf(2))), + new Point(new Uint256(BigInteger.valueOf(3)), new Uint256(BigInteger.valueOf(4)))), + new Utf8String("region")); + + String returnedData = DYNAMIC_STRUCT_HEAD + TypeEncoder.encode(region); + + List decoded = FunctionReturnDecoder.decode( + returnedData, + Utils.convert(Arrays.asList(new TypeReference() {}))); + + assertEquals(1, decoded.size()); + assertTrue(decoded.get(0) instanceof Region); + Region decodedRegion = (Region) decoded.get(0); + + @SuppressWarnings("unchecked") + StaticArray2 corners = (StaticArray2) decodedRegion.getValue().get(0); + assertEquals(BigInteger.ONE, corners.getValue().get(0).getX()); + assertEquals(BigInteger.valueOf(2), corners.getValue().get(0).getY()); + assertEquals(BigInteger.valueOf(3), corners.getValue().get(1).getX()); + assertEquals(BigInteger.valueOf(4), corners.getValue().get(1).getY()); + assertEquals(new Utf8String("region"), decodedRegion.getValue().get(1)); + } + + @Test + public void testStructElementStaticArraySignature() { + assertEquals("((uint256,uint256)[2],string)", Utils.getStructType(Region.class)); + } + + @Test + public void testDecodeDoublyNestedStaticStruct() { + Outer outer = new Outer( + new Mid( + new Inner(new Uint256(BigInteger.ONE), new Uint256(BigInteger.valueOf(2))), + new Uint256(BigInteger.valueOf(3))), + new Uint256(BigInteger.valueOf(4))); + + String returnedData = TypeEncoder.encode(outer); + + List decoded = FunctionReturnDecoder.decode( + returnedData, + Utils.convert(Arrays.asList(new TypeReference() {}))); + + assertEquals(1, decoded.size()); + assertTrue(decoded.get(0) instanceof Outer); + Outer decodedOuter = (Outer) decoded.get(0); + + Mid mid = (Mid) decodedOuter.getValue().get(0); + Inner inner = (Inner) mid.getValue().get(0); + assertEquals(new Uint256(BigInteger.ONE), inner.getValue().get(0)); + assertEquals(new Uint256(BigInteger.valueOf(2)), inner.getValue().get(1)); + assertEquals(new Uint256(BigInteger.valueOf(3)), mid.getValue().get(1)); + assertEquals(new Uint256(BigInteger.valueOf(4)), decodedOuter.getValue().get(1)); + } +} diff --git a/abi/src/test/java/org/tron/trident/abi/TridentAbiEncodeDecodeCompatibilityTest.java b/abi/src/test/java/org/tron/trident/abi/TridentAbiEncodeDecodeCompatibilityTest.java new file mode 100644 index 00000000..6ff4d05d --- /dev/null +++ b/abi/src/test/java/org/tron/trident/abi/TridentAbiEncodeDecodeCompatibilityTest.java @@ -0,0 +1,598 @@ +package org.tron.trident.abi; + +import static org.junit.jupiter.api.Assertions.fail; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.ParameterizedType; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; +import java.util.zip.GZIPInputStream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.tron.trident.abi.datatypes.DynamicArray; +import org.tron.trident.abi.datatypes.DynamicStruct; +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.utils.Numeric; + +/** + * Verifies that Trident's ABI encoding and return decoding match the reference + * bytes recorded in fixtures of pre-generated random test cases. + * + *

Fixture flow: + *

    + *
  1. Load the gzipped JSON fixture(s) from the test classpath.
  2. + *
  3. Parse the JSON-encoded value tree into native Java values.
  4. + *
  5. Use {@link TypeDecoder#instantiateType} to build Trident {@code Type} instances + * for parameter encoding checks.
  6. + *
  7. Build {@link TypeReference}s from ABI outputs for return decoding checks.
  8. + *
  9. Compare both encoded parameters and decode/re-encode round trips to the + * expected hex string from the fixture.
  10. + *
+ */ +@DisplayName("Trident ABI Encode Decode Compatibility") +public class TridentAbiEncodeDecodeCompatibilityTest { + + private static final ObjectMapper MAPPER = new JsonMapper(); + /** + * Test-data fixtures loaded from the test classpath. Each entry is a gzipped JSON + * array of pre-generated ABI compatibility test cases. Multiple fixtures are merged so + * the aggregated tests run across all of them; case names are prefixed with the + * fixture file (without extension) so failure messages identify the source fixture. + */ + private static final String[] TEST_DATA_RESOURCES = { + "contract-interface.json.gz", + "contract-interface-abi2.json.gz", + }; + + /** + * A single fixture entry parsed from the JSON test data. + */ + public static class TestCase { + public String name; // case name (e.g. "random-0") + public String types; // raw type list (e.g. "[\"address\",\"address\",\"address\"]") + public String values; // raw value list, JSON-encoded + public String result; // expected encoded hex output + public String abi; // raw ABI interface JSON + + public TestCase(JsonNode obj) { + this.name = obj.get("name").asText(); + this.types = obj.get("types").asText(); + this.values = obj.get("values").asText(); + this.result = obj.get("result").asText(); + this.abi = obj.get("interface").asText(); + } + + @Override + public String toString() { + return name + " - " + types; + } + } + + /** + * Converts ABI definitions + raw JSON values into Trident {@code Type} instances, + * including nested tuples (structs) and arrays. + */ + static class AbiTypeInstantiator { + + /** + * Converts a fixture-encoded value node into the plain Java argument shape + * that {@link TypeDecoder#instantiateType} expects (BigInteger, String, List, etc.). + */ + static class JsonValueConverter { + static Object convert(JsonNode valueObj) { + if (valueObj.isArray()) { + List list = new ArrayList<>(); + for (JsonNode item : valueObj) { + list.add(convert(item)); + } + return list; + } + if (!valueObj.isObject()) { + throw new IllegalArgumentException( + "Expected object or array, got: " + valueObj.getNodeType()); + } + String type = valueObj.get("type").asText(); + JsonNode rawValue = valueObj.get("value"); + + switch (type) { + case "number": + if (rawValue.isNumber() || rawValue.isTextual()) { + return new BigInteger(rawValue.asText()); + } + return BigInteger.ZERO; + case "string": + return rawValue.asText(); + case "boolean": + return rawValue.asBoolean(); + case "buffer": + String bufValue = rawValue.asText(); + return bufValue.startsWith("0x") ? bufValue : "0x" + bufValue; + case "tuple": + List list = new ArrayList<>(); + for (JsonNode item : rawValue) { + list.add(convert(item)); + } + return list; + default: + throw new IllegalArgumentException("Unknown type: " + type); + } + } + } + + Type instantiateType(String typeStr, JsonNode value, JsonNode abiComponentDef) + throws Exception { + if (typeStr.contains("tuple")) { + return instantiateTypeWithTupleSupport(typeStr, value, abiComponentDef); + } + Object convertedValue = JsonValueConverter.convert(value); + return TypeDecoder.instantiateType(typeStr, convertedValue); + } + + private Type instantiateTypeWithTupleSupport(String typeStr, JsonNode valueObj, + JsonNode abiDef) throws Exception { + int firstBracket = typeStr.indexOf('['); + if (firstBracket < 0) { + if (typeStr.contains("tuple")) { + if (valueObj.isArray()) { + throw new IllegalArgumentException( + "Unexpected JSON array for tuple base type: " + typeStr); + } + return instantiateTuple(abiDef, valueObj); + } + Object convertedValue = JsonValueConverter.convert(valueObj); + return TypeDecoder.instantiateType(typeStr, convertedValue); + } + + String baseType = typeStr.substring(0, firstBracket); + String arrayParts = typeStr.substring(firstBracket); + + return instantiateTupleArrayRightToLeft(baseType, arrayParts, valueObj, abiDef); + } + + private Type instantiateTuple(JsonNode input, JsonNode valueObj) throws Exception { + JsonNode componentsArray = input.get("components"); + JsonNode tupleValuesArray = valueObj.get("value"); + + List componentTypes = new ArrayList<>(); + boolean hasDynamicComponent = false; + + for (int i = 0; i < componentsArray.size(); i++) { + JsonNode component = componentsArray.get(i); + String componentType = component.get("type").asText(); + JsonNode componentValue = tupleValuesArray.get(i); + + Type componentTypeObj; + if (componentType.contains("tuple")) { + componentTypeObj = instantiateTypeWithTupleSupport(componentType, + componentValue, component); + } else { + Object convertedValue = JsonValueConverter.convert(componentValue); + componentTypeObj = TypeDecoder.instantiateType(componentType, convertedValue); + } + componentTypes.add(componentTypeObj); + + if (TypeEncoder.isDynamic(componentTypeObj)) { + hasDynamicComponent = true; + } + } + + return hasDynamicComponent + ? new DynamicStruct(componentTypes) + : new StaticStruct(componentTypes); + } + + private Type instantiateTupleArrayRightToLeft(String baseType, String arrayParts, + JsonNode valueObj, JsonNode abiDef) throws Exception { + int lastBracket = arrayParts.lastIndexOf('['); + String lastArrayPart = arrayParts.substring(lastBracket); + String remainingArrayParts = arrayParts.substring(0, lastBracket); + + if (!valueObj.isArray()) { + throw new IllegalArgumentException( + "Expected JSON array for array type: " + baseType + arrayParts); + } + + if (!remainingArrayParts.isEmpty()) { + List elements = new ArrayList<>(); + for (JsonNode elementValue : valueObj) { + Type elementType = instantiateTupleArrayRightToLeft(baseType, + remainingArrayParts, elementValue, abiDef); + elements.add(elementType); + } + return wrapAsArray(elements, lastArrayPart); + } + + List tupleElements = new ArrayList<>(); + for (JsonNode elementValue : valueObj) { + Type tupleInstance = instantiateTuple(abiDef, elementValue); + tupleElements.add(tupleInstance); + } + return wrapAsArray(tupleElements, lastArrayPart); + } + + private static Type wrapAsArray(List elements, String arrayPart) { + if ("[]".equals(arrayPart)) { + return new DynamicArray<>(Type.class, elements); + } + int arraySize = Integer.parseInt( + arrayPart.substring(1, arrayPart.length() - 1)); + return new StaticArray(Type.class, arraySize, elements) { }; + } + } + + /** + * Converts ABI output definitions into {@link TypeReference}s for return decoding, + * including tuple structs and tuple arrays. + */ + static class AbiTypeReferenceBuilder { + static List> buildOutputTypeReferences(JsonNode outputsArray) + throws Exception { + List> typeReferences = new ArrayList<>(); + for (JsonNode output : outputsArray) { + String type = output.get("type").asText(); + typeReferences.add(type.contains("tuple") + ? makeTupleTypeReference(type, output) + : TypeReference.makeTypeReference(type)); + } + return Utils.convert(typeReferences); + } + + private static TypeReference makeTupleTypeReference(String typeStr, + JsonNode componentDef) throws Exception { + String arrayParts = typeStr.substring("tuple".length()); + JsonNode components = componentDef.get("components"); + + boolean dynamic = false; + List> innerTypes = new ArrayList<>(); + for (JsonNode component : components) { + String componentType = component.get("type").asText(); + if (isAbiTypeDynamic(componentType, component)) { + dynamic = true; + } + innerTypes.add(componentType.contains("tuple") + ? makeTupleTypeReference(componentType, component) + : TypeReference.makeTypeReference(componentType)); + } + + final List> finalInnerTypes = innerTypes; + TypeReference tupleRef = dynamic + ? new TypeReference(false, finalInnerTypes) { } + : new TypeReference(false, finalInnerTypes) { }; + + return arrayParts.isEmpty() ? tupleRef : wrapInArrayTypeReference(tupleRef, arrayParts); + } + + private static boolean isAbiTypeDynamic(String typeStr, JsonNode componentDef) { + if ("string".equals(typeStr) || "bytes".equals(typeStr) || typeStr.endsWith("[]")) { + return true; + } + if (typeStr.startsWith("tuple")) { + JsonNode components = componentDef.get("components"); + if (components == null) { + return true; + } + for (JsonNode component : components) { + if (isAbiTypeDynamic(component.get("type").asText(), component)) { + return true; + } + } + return false; + } + if (typeStr.endsWith("]")) { + return isAbiTypeDynamic(typeStr.substring(0, typeStr.lastIndexOf('[')), + componentDef); + } + return false; + } + + private static TypeReference wrapInArrayTypeReference(TypeReference baseTypeRef, + String arrayParts) throws Exception { + String remaining = arrayParts; + while (!remaining.isEmpty()) { + int firstBracket = remaining.indexOf('['); + int closeBracket = remaining.indexOf(']', firstBracket); + String arrayPart = remaining.substring(firstBracket, closeBracket + 1); + remaining = remaining.substring(closeBracket + 1); + final TypeReference innerRef = baseTypeRef; + + if ("[]".equals(arrayPart)) { + baseTypeRef = new TypeReference>() { + @Override + public TypeReference getSubTypeReference() { + return innerRef; + } + + @Override + public java.lang.reflect.Type getType() { + return parameterizedType(DynamicArray.class, innerRef); + } + }; + } else { + int size = Integer.parseInt(arrayPart.substring(1, arrayPart.length() - 1)); + final Class arrayClass = Class.forName( + "org.tron.trident.abi.datatypes.generated.StaticArray" + size); + baseTypeRef = + new TypeReference.StaticArrayTypeReference>(size) { + @Override + public TypeReference getSubTypeReference() { + return innerRef; + } + + @Override + public java.lang.reflect.Type getType() { + return parameterizedType(arrayClass, innerRef); + } + }; + } + } + return baseTypeRef; + } + + private static ParameterizedType parameterizedType(final Class rawType, + final TypeReference innerRef) { + return new ParameterizedType() { + @Override + public java.lang.reflect.Type[] getActualTypeArguments() { + return new java.lang.reflect.Type[] {innerRef.getType()}; + } + + @Override + public java.lang.reflect.Type getRawType() { + return rawType; + } + + @Override + public java.lang.reflect.Type getOwnerType() { + return null; + } + }; + } + } + + /** + * Loads every gzipped fixture in {@link #TEST_DATA_RESOURCES} and merges them into + * one list, prefixing each case name with the fixture file stem so JUnit reports + * stay disambiguated when both fixtures contain cases like "random-0". + */ + static List loadTestCases() throws IOException { + List all = new ArrayList<>(); + for (String resource : TEST_DATA_RESOURCES) { + all.addAll(loadTestCasesFromResource(resource)); + } + return all; + } + + private static List loadTestCasesFromResource(String resourceName) + throws IOException { + try (InputStream gz = TridentAbiEncodeDecodeCompatibilityTest.class + .getClassLoader() + .getResourceAsStream(resourceName)) { + if (gz == null) { + throw new IOException( + "Test data resource not found on classpath: " + resourceName); + } + String content; + try (GZIPInputStream in = new GZIPInputStream(gz); + ByteArrayOutputStream out = new ByteArrayOutputStream()) { + byte[] buf = new byte[8192]; + int n; + while ((n = in.read(buf)) != -1) { + out.write(buf, 0, n); + } + content = out.toString(StandardCharsets.UTF_8.name()); + } + JsonNode array = MAPPER.readTree(content); + String stem = resourceName.replaceFirst("\\.json\\.gz$", ""); + return StreamSupport.stream(array.spliterator(), false) + .map(node -> { + TestCase tc = new TestCase(node); + tc.name = stem + "/" + tc.name; + return tc; + }) + .collect(Collectors.toList()); + } + } + + /** + * Parameter encoding test. + * + *

Re-encodes each fixture's outputs as the inputs of a constructor call, + * then asserts the resulting hex matches the expected encoding from the fixture. + * Type identification correctness gates selector correctness; byte-for-byte + * encoding correctness gates wire compatibility. + * + *

Runs as a single aggregated test (not per-case parameterized) so passing + * and skipped cases produce no per-case test-event output; on failure the + * assertion message lists the failing case names plus the skipped-case count. + */ + @Test + @DisplayName("Parameter Encoding Should Match Expected Result") + void testParameterEncoding() throws Exception { + List failures = new ArrayList<>(); + int skipped = 0; + for (TestCase testCase : loadTestCases()) { + try { + String actual = encodeParameterCase(testCase); + String expected = testCase.result.toLowerCase(); + if (!expected.equals(actual)) { + failures.add(testCase + ": encoding mismatch" + + "\n values: " + testCase.values + + "\n expected: " + expected + + "\n actual: " + actual); + } + } catch (SkippedCaseException e) { + skipped++; + } catch (Exception e) { + failures.add(testCase + ": " + e.getClass().getSimpleName() + + " - " + e.getMessage()); + } + } + if (!failures.isEmpty()) { + int shown = Math.min(failures.size(), 20); + fail("Parameter encoding failed for " + failures.size() + " case(s)" + + " (" + skipped + " skipped):\n " + + String.join("\n ", failures.subList(0, shown)) + + (failures.size() > shown ? "\n ..." : "")); + } + } + + /** + * Runs one parameter-encoding fixture case and returns the lower-case + * hex-prefixed encoding. Throws {@link SkippedCaseException} for cases outside + * the current Trident impl's support (formerly Assumptions-based skips). + */ + private static String encodeParameterCase(TestCase testCase) throws Exception { + // 1. Parse the ABI interface definition. + JsonNode interfaceArray = MAPPER.readTree(testCase.abi); + ObjectNode functionAbi = (ObjectNode) interfaceArray.get(0); + + // Copy outputs into inputs so we exercise the parameter encoder against + // the (known-good) output encoding from the fixture. + JsonNode outputsArray = functionAbi.get("outputs"); + functionAbi.set("inputs", outputsArray); + + // 2. Extract the type list from the (now-rewritten) inputs. + JsonNode inputsArray = functionAbi.get("inputs"); + List types = new ArrayList<>(); + for (JsonNode input : inputsArray) { + types.add(input.get("type").asText()); + } + + // 3. Instantiate Trident Type values. + AbiTypeInstantiator instantiator = new AbiTypeInstantiator(); + JsonNode valuesArray = MAPPER.readTree(testCase.values); + List values = new ArrayList<>(); + + for (int i = 0; i < types.size(); i++) { + String typeStr = types.get(i); + JsonNode valueElement = valuesArray.get(i); + JsonNode inputDef = inputsArray.get(i); + + // Some fixtures encode signed-int values as the unsigned representation + // of the wrapped negative number (e.g. int8=189 represents -67). Trident + // encodes the value literally, producing a zero-extended slot instead of + // a sign-extended one. This is a documented behavior difference; skip + // such cases rather than fail. + if (isSignedIntOutOfRange(typeStr, valueElement)) { + throw new SkippedCaseException( + "Skipping " + typeStr + + ": fixture value outside signed range " + + "(reference auto-wraps, trident encodes literally)"); + } + + try { + values.add(instantiator.instantiateType(typeStr, valueElement, inputDef)); + } catch (Exception e) { + // Skip cases where the type isn't supported by the current Trident impl, + // or where the fixture value violates the declared bit-width. + // Constructor-thrown exceptions arrive wrapped in InvocationTargetException + // via reflection in TypeDecoder.instantiateAtomicType, so unwrap before checking. + Throwable cause = + e instanceof java.lang.reflect.InvocationTargetException + ? e.getCause() + : e; + if (cause instanceof UnsupportedOperationException + || cause instanceof IllegalArgumentException) { + throw new SkippedCaseException( + "Skipping " + typeStr + ": " + + cause.getClass().getSimpleName() + + " - " + cause.getMessage()); + } + throw e; + } + } + + // 4. Encode. + String encoded = FunctionEncoder.encodeConstructor(values); + return Numeric.prependHexPrefix(encoded).toLowerCase(); + } + + /** + * Signals a fixture case outside the current impl's support; counted as + * skipped by the aggregated tests instead of failing them. + */ + private static final class SkippedCaseException extends Exception { + SkippedCaseException(String message) { + super(message); + } + } + + /** + * Return decoding test. + * + *

Decodes each fixture's expected output bytes using the ABI output + * definitions, then re-encodes the decoded values. The final bytes must still + * match the fixture, which exercises return-offset handling for dynamic values + * and structs. + * + *

Runs as a single aggregated test (not per-case parameterized) so passing + * cases produce no per-case test-event output; on failure the assertion message + * lists the failing case names. + */ + @Test + @DisplayName("Encode Decode Should Round Trip Expected Result") + void testEncodeDecodeRoundTrip() throws Exception { + List failures = new ArrayList<>(); + for (TestCase testCase : loadTestCases()) { + try { + JsonNode interfaceArray = MAPPER.readTree(testCase.abi); + JsonNode outputsArray = interfaceArray.get(0).get("outputs"); + List> outputReferences = + AbiTypeReferenceBuilder.buildOutputTypeReferences(outputsArray); + + List decodedValues = + FunctionReturnDecoder.decode(testCase.result, outputReferences); + + String encoded = FunctionEncoder.encodeConstructor(decodedValues); + String actual = Numeric.prependHexPrefix(encoded).toLowerCase(); + String expected = testCase.result.toLowerCase(); + if (!expected.equals(actual)) { + failures.add(testCase + ": decode round-trip mismatch" + + "\n expected: " + expected + + "\n actual: " + actual); + } + } catch (Exception e) { + failures.add(testCase + ": " + e.getClass().getSimpleName() + + " - " + e.getMessage()); + } + } + if (!failures.isEmpty()) { + int shown = Math.min(failures.size(), 20); + fail("Decode round-trip failed for " + failures.size() + " case(s):\n " + + String.join("\n ", failures.subList(0, shown)) + + (failures.size() > shown ? "\n ..." : "")); + } + } + + /** + * Whether the given fixture value falls outside the signed range of the declared + * {@code intN} type. Only matches scalar signed ints (not arrays/tuples, not uint). + * Treats bare {@code int} as its ABI alias {@code int256}. + */ + private static boolean isSignedIntOutOfRange(String typeStr, JsonNode valueNode) { + if (!typeStr.matches("int\\d*")) { + return false; + } + if (!valueNode.isObject() || !"number".equals(valueNode.path("type").asText())) { + return false; + } + String suffix = typeStr.substring(3); + int bitSize = suffix.isEmpty() ? 256 : Integer.parseInt(suffix); + BigInteger v = new BigInteger(valueNode.get("value").asText()); + BigInteger max = BigInteger.ONE.shiftLeft(bitSize - 1).subtract(BigInteger.ONE); + BigInteger min = BigInteger.ONE.shiftLeft(bitSize - 1).negate(); + return v.compareTo(max) > 0 || v.compareTo(min) < 0; + } +} diff --git a/abi/src/test/java/org/tron/trident/abi/TypeDecoderTest.java b/abi/src/test/java/org/tron/trident/abi/TypeDecoderTest.java index f71e4e99..73545ded 100644 --- a/abi/src/test/java/org/tron/trident/abi/TypeDecoderTest.java +++ b/abi/src/test/java/org/tron/trident/abi/TypeDecoderTest.java @@ -18,8 +18,10 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.math.BigInteger; +import java.util.Arrays; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.tron.trident.abi.AbiV2TestFixture.Bar; import org.tron.trident.abi.datatypes.Address; import org.tron.trident.abi.datatypes.Bool; import org.tron.trident.abi.datatypes.Bytes; @@ -66,6 +68,7 @@ import org.tron.trident.abi.datatypes.generated.Int80; import org.tron.trident.abi.datatypes.generated.Int88; import org.tron.trident.abi.datatypes.generated.Int96; +import org.tron.trident.abi.datatypes.generated.StaticArray1; import org.tron.trident.abi.datatypes.generated.StaticArray2; import org.tron.trident.abi.datatypes.generated.StaticArray3; import org.tron.trident.abi.datatypes.generated.Uint104; @@ -1036,6 +1039,17 @@ public void testTrcToken() throws Exception { (new TrcToken(1003020))); } + @Test + public void testTrcTokenLargeUnsignedRoundTrip() { + // trcToken is unsigned; a word with the top bit set must decode to the + // positive value, not a two's-complement negative one. + String hex = "8000000000000000000000000000000000000000000000000000000000000005"; + BigInteger expected = BigInteger.ONE.shiftLeft(255).add(BigInteger.valueOf(5)); + + assertEquals(new TrcToken(expected), TypeDecoder.decodeNumeric(hex, TrcToken.class)); + assertEquals(hex, TypeEncoder.encode(new TrcToken(expected))); + } + @Test public void testUtf8String() throws Exception { assertEquals( @@ -1067,7 +1081,9 @@ public void testStaticArray() throws Exception { assertEquals( TypeDecoder.decodeStaticArray( - "000000000000000000000000000000000000000000000000000000000000000d" + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "000000000000000000000000000000000000000000000000000000000000000d" + "48656c6c6f2c20776f726c642100000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000d" + "776f726c64212048656c6c6f2c00000000000000000000000000000000000000", @@ -1080,6 +1096,8 @@ public void testStaticArray() throws Exception { new Utf8String("Hello, world!"), new Utf8String("world! Hello,")))); + + Type arr = TypeDecoder.instantiateType("uint256[2]", new long[] {10, Long.MAX_VALUE}); assertTrue(arr instanceof StaticArray2); @@ -1092,6 +1110,66 @@ public void testStaticArray() throws Exception { staticArray2.getValue().get(1), (new Uint256(BigInteger.valueOf(Long.MAX_VALUE)))); } + @Test + public void testIsDynamicUnresolvableElementTypeThrows() { + // A raw StaticArray reference with no element type information: answering + // "static" here used to silently corrupt downstream offset math; it must throw. + assertThrows( + UnsupportedOperationException.class, + () -> TypeDecoder.isDynamic( + TypeReference.create(org.tron.trident.abi.datatypes.generated.StaticArray2.class))); + } + + @Test + public void testEmptyStaticArrayInstantiateType() throws Exception { + Type arr = TypeDecoder.instantiateType("uint256[0]", new long[] {}); + + assertTrue(arr instanceof org.tron.trident.abi.datatypes.generated.StaticArray0); + assertTrue(((org.tron.trident.abi.datatypes.generated.StaticArray0) arr) + .getValue().isEmpty()); + assertEquals("uint256[0]", arr.getTypeAsString()); + } + + @Test + public void testNonEmptyValuesForZeroLengthStaticArrayRejected() { + UnsupportedOperationException e = assertThrows( + UnsupportedOperationException.class, + () -> new org.tron.trident.abi.datatypes.generated.StaticArray0<>( + Uint256.class, + Arrays.asList( + new Uint256(BigInteger.ONE), new Uint256(BigInteger.valueOf(2))))); + assertTrue(e.getMessage().contains("[0] elements")); + } + + @Test + public void testDecodeNumericTruncatedInput() { + // 17 bytes instead of the 32 a Uint256 word requires: must fail fast, not + // zero-pad the tail (which would return value << 120). + assertThrows( + IndexOutOfBoundsException.class, + () -> TypeDecoder.decodeNumeric( + "00000000000000000000000000000000000f4e0c", Uint256.class)); + // Empty input must not silently decode to zero. + assertThrows( + IndexOutOfBoundsException.class, + () -> TypeDecoder.decodeNumeric("", Uint256.class)); + } + + @Test + public void testTrcTokenStaticArray() throws Exception { + assertEquals( + TypeDecoder.decodeStaticArray( + "00000000000000000000000000000000000000000000000000000000000f4e0c" + + "00000000000000000000000000000000000000000000000000000000000f4e0d", + 0, + TypeReference.makeTypeReference("trcToken[2]"), + 2), + (new StaticArray2<>( + TrcToken.class, + new TrcToken(1003020), + new TrcToken(1003021)))); + } + @Test public void testEmptyStaticArray() { assertThrows( @@ -1106,13 +1184,6 @@ public void testEmptyStaticArray() { 0)); } - @Test - public void testEmptyStaticArrayInstantiateType() { - assertThrows( - ClassNotFoundException.class, - () -> TypeDecoder.instantiateType("uint256[0]", new long[] {})); - } - @Test public void testDynamicArray() throws Exception { assertEquals( @@ -1139,6 +1210,8 @@ public void testDynamicArray() throws Exception { assertEquals( TypeDecoder.decodeDynamicArray( "0000000000000000000000000000000000000000000000000000000000000002" // length + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + "000000000000000000000000000000000000000000000000000000000000000d" + "48656c6c6f2c20776f726c642100000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000d" @@ -1164,6 +1237,62 @@ public void testDynamicArray() throws Exception { assertEquals(dynamicArray.getValue().get(1), (new Utf8String("world! Hello,"))); } + @Test + public void testDynamicArrayOfDynamicArrays() throws Exception { + assertEquals( + TypeDecoder.decodeDynamicArray( + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "00000000000000000000000000000000000000000000000000000000000000a0" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000000", + 0, + new TypeReference>>() {}), + new DynamicArray( + DynamicArray.class, + Arrays.asList( + new DynamicArray( + AbiV2TestFixture.Bar.class, + new AbiV2TestFixture.Bar( + new Uint256(BigInteger.ZERO), + new Uint256(BigInteger.ZERO))), + new DynamicArray( + AbiV2TestFixture.Bar.class, + new AbiV2TestFixture.Bar( + new Uint256(BigInteger.ONE), + new Uint256(BigInteger.ZERO)))))); + } + + @Test + public void testDynamicArrayOfStaticArrays() throws Exception { + assertEquals( + TypeDecoder.decodeDynamicArray( + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000000", + 0, + new TypeReference>>() {}), + new DynamicArray( + StaticArray1.class, + Arrays.asList( + new StaticArray1( + AbiV2TestFixture.Bar.class, + new AbiV2TestFixture.Bar( + new Uint256(BigInteger.ZERO), + new Uint256(BigInteger.ZERO))), + new StaticArray1( + AbiV2TestFixture.Bar.class, + new AbiV2TestFixture.Bar( + new Uint256(BigInteger.ONE), + new Uint256(BigInteger.ZERO)))))); + } + @SuppressWarnings("unchecked") @Test public void multiDimArrays() throws Exception { @@ -1189,4 +1318,32 @@ public void multiDimArrays() throws Exception { row1 = staticArray3StaticArray3.getValue().get(1).getValue().get(1); assertEquals(row1.getValue().get(1), (new Uint256(2))); } + + @Test + public void testDecodeNestedStaticArray() { + // uint256[2][2] + // Element 1: uint256[2] = [64, 2] + // Element 2: uint256[2] = [3, 4] + String input = "0000000000000000000000000000000000000000000000000000000000000040" // 64 + + "0000000000000000000000000000000000000000000000000000000000000002" // 2 + + "0000000000000000000000000000000000000000000000000000000000000003" // 3 + + "0000000000000000000000000000000000000000000000000000000000000004"; // 4 + + // Using TypeReference to define uint256[2][2] + TypeReference.StaticArrayTypeReference>> typeRef = + new TypeReference.StaticArrayTypeReference>>(2) { + @Override + public TypeReference getSubTypeReference() { + return new TypeReference.StaticArrayTypeReference>(2) {}; + } + }; + + StaticArray2> result = TypeDecoder.decodeStaticArray(input, 0, typeRef, 2); + + assertEquals(2, result.getValue().size()); + assertEquals(java.math.BigInteger.valueOf(64), result.getValue().get(0).getValue().get(0).getValue()); + assertEquals(java.math.BigInteger.valueOf(2), result.getValue().get(0).getValue().get(1).getValue()); + assertEquals(java.math.BigInteger.valueOf(3), result.getValue().get(1).getValue().get(0).getValue()); + assertEquals(java.math.BigInteger.valueOf(4), result.getValue().get(1).getValue().get(1).getValue()); + } } diff --git a/abi/src/test/java/org/tron/trident/abi/TypeEncoderPackedTest.java b/abi/src/test/java/org/tron/trident/abi/TypeEncoderPackedTest.java new file mode 100644 index 00000000..3e601e2f --- /dev/null +++ b/abi/src/test/java/org/tron/trident/abi/TypeEncoderPackedTest.java @@ -0,0 +1,1189 @@ +package org.tron.trident.abi; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.math.BigInteger; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.tron.trident.abi.datatypes.Address; +import org.tron.trident.abi.datatypes.Bool; +import org.tron.trident.abi.datatypes.Bytes; +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.Int; +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; +import org.tron.trident.abi.datatypes.Utf8String; +import org.tron.trident.abi.datatypes.generated.Bytes1; +import org.tron.trident.abi.datatypes.generated.Bytes4; +import org.tron.trident.abi.datatypes.generated.Bytes6; +import org.tron.trident.abi.datatypes.generated.Int104; +import org.tron.trident.abi.datatypes.generated.Int112; +import org.tron.trident.abi.datatypes.generated.Int120; +import org.tron.trident.abi.datatypes.generated.Int128; +import org.tron.trident.abi.datatypes.generated.Int136; +import org.tron.trident.abi.datatypes.generated.Int144; +import org.tron.trident.abi.datatypes.generated.Int152; +import org.tron.trident.abi.datatypes.generated.Int16; +import org.tron.trident.abi.datatypes.generated.Int160; +import org.tron.trident.abi.datatypes.generated.Int168; +import org.tron.trident.abi.datatypes.generated.Int176; +import org.tron.trident.abi.datatypes.generated.Int184; +import org.tron.trident.abi.datatypes.generated.Int192; +import org.tron.trident.abi.datatypes.generated.Int200; +import org.tron.trident.abi.datatypes.generated.Int208; +import org.tron.trident.abi.datatypes.generated.Int216; +import org.tron.trident.abi.datatypes.generated.Int224; +import org.tron.trident.abi.datatypes.generated.Int232; +import org.tron.trident.abi.datatypes.generated.Int24; +import org.tron.trident.abi.datatypes.generated.Int240; +import org.tron.trident.abi.datatypes.generated.Int248; +import org.tron.trident.abi.datatypes.generated.Int32; +import org.tron.trident.abi.datatypes.generated.Int40; +import org.tron.trident.abi.datatypes.generated.Int48; +import org.tron.trident.abi.datatypes.generated.Int56; +import org.tron.trident.abi.datatypes.generated.Int64; +import org.tron.trident.abi.datatypes.generated.Int72; +import org.tron.trident.abi.datatypes.generated.Int8; +import org.tron.trident.abi.datatypes.generated.Int80; +import org.tron.trident.abi.datatypes.generated.Int88; +import org.tron.trident.abi.datatypes.generated.Int96; +import org.tron.trident.abi.datatypes.generated.StaticArray0; +import org.tron.trident.abi.datatypes.generated.StaticArray2; +import org.tron.trident.abi.datatypes.generated.StaticArray3; +import org.tron.trident.abi.datatypes.generated.Uint104; +import org.tron.trident.abi.datatypes.generated.Uint112; +import org.tron.trident.abi.datatypes.generated.Uint120; +import org.tron.trident.abi.datatypes.generated.Uint128; +import org.tron.trident.abi.datatypes.generated.Uint136; +import org.tron.trident.abi.datatypes.generated.Uint144; +import org.tron.trident.abi.datatypes.generated.Uint152; +import org.tron.trident.abi.datatypes.generated.Uint16; +import org.tron.trident.abi.datatypes.generated.Uint160; +import org.tron.trident.abi.datatypes.generated.Uint168; +import org.tron.trident.abi.datatypes.generated.Uint176; +import org.tron.trident.abi.datatypes.generated.Uint184; +import org.tron.trident.abi.datatypes.generated.Uint192; +import org.tron.trident.abi.datatypes.generated.Uint200; +import org.tron.trident.abi.datatypes.generated.Uint208; +import org.tron.trident.abi.datatypes.generated.Uint216; +import org.tron.trident.abi.datatypes.generated.Uint224; +import org.tron.trident.abi.datatypes.generated.Uint232; +import org.tron.trident.abi.datatypes.generated.Uint24; +import org.tron.trident.abi.datatypes.generated.Uint240; +import org.tron.trident.abi.datatypes.generated.Uint248; +import org.tron.trident.abi.datatypes.generated.Uint32; +import org.tron.trident.abi.datatypes.generated.Uint40; +import org.tron.trident.abi.datatypes.generated.Uint48; +import org.tron.trident.abi.datatypes.generated.Uint56; +import org.tron.trident.abi.datatypes.generated.Uint64; +import org.tron.trident.abi.datatypes.generated.Uint72; +import org.tron.trident.abi.datatypes.generated.Uint8; +import org.tron.trident.abi.datatypes.generated.Uint80; +import org.tron.trident.abi.datatypes.generated.Uint88; +import org.tron.trident.abi.datatypes.generated.Uint96; +import org.tron.trident.abi.datatypes.primitive.Byte; +import org.tron.trident.abi.datatypes.primitive.Char; +import org.tron.trident.abi.datatypes.primitive.Long; +import org.tron.trident.abi.datatypes.primitive.Short; + + +public class TypeEncoderPackedTest { + + @Test + public void testAddressEncodePacked() { + Address address = new Address("0x663e27AdC18d862dA9A82f060310621D379e469a"); + + assertEquals("663e27adc18d862da9a82f060310621d379e469a", TypeEncoder.encodePacked(address)); + + Address addressLong = + new Address( + 256, "0xa04462684b510796c186d19abfa6929742f79394583d6efb1243bbb473f21d9f"); + assertEquals( + "a04462684b510796c186d19abfa6929742f79394583d6efb1243bbb473f21d9f", + TypeEncoder.encodePacked(addressLong)); + } + + @Test + public void testBoolEncodePacked() { + assertEquals(TypeEncoder.encodePacked(new Bool(false)), ("00")); + assertEquals(TypeEncoder.encodePacked(new Bool(true)), ("01")); + } + + @Test + public void testUintEncodePacked() { + Uint zero8 = new Uint8(BigInteger.ZERO); + assertEquals("00", TypeEncoder.encodePacked(zero8)); + + Uint max8 = new Uint8(255); + assertEquals("ff", TypeEncoder.encodePacked(max8)); + + Uint zero16 = new Uint16(BigInteger.ZERO); + assertEquals("0000", TypeEncoder.encodePacked(zero16)); + Uint max16 = new Uint16(65535); + assertEquals("ffff", TypeEncoder.encodePacked(max16)); + + Uint zero24 = new Uint24(BigInteger.ZERO); + assertEquals("000000", TypeEncoder.encodePacked(zero24)); + Uint max24 = new Uint24(16777215); + assertEquals("ffffff", TypeEncoder.encodePacked(max24)); + + Uint zero32 = new Uint32(BigInteger.ZERO); + assertEquals("00000000", TypeEncoder.encodePacked(zero32)); + Uint max32 = new Uint32(BigInteger.valueOf(4294967295L)); + assertEquals("ffffffff", TypeEncoder.encodePacked(max32)); + + Uint zero40 = new Uint40(BigInteger.ZERO); + assertEquals("0000000000", TypeEncoder.encodePacked(zero40)); + Uint max40 = new Uint40(BigInteger.valueOf(1099511627775L)); + assertEquals("ffffffffff", TypeEncoder.encodePacked(max40)); + + Uint zero48 = new Uint48(BigInteger.ZERO); + assertEquals("000000000000", TypeEncoder.encodePacked(zero48)); + Uint max48 = new Uint48(BigInteger.valueOf(281474976710655L)); + assertEquals("ffffffffffff", TypeEncoder.encodePacked(max48)); + + Uint zero56 = new Uint56(BigInteger.ZERO); + assertEquals("00000000000000", TypeEncoder.encodePacked(zero56)); + Uint max56 = new Uint56(BigInteger.valueOf(72057594037927935L)); + assertEquals("ffffffffffffff", TypeEncoder.encodePacked(max56)); + + Uint zero64 = new Uint64(BigInteger.ZERO); + assertEquals("0000000000000000", TypeEncoder.encodePacked(zero64)); + + Uint maxLong = new Uint64(BigInteger.valueOf(java.lang.Long.MAX_VALUE)); + assertEquals("7fffffffffffffff", TypeEncoder.encodePacked(maxLong)); + + Uint maxValue64 = + new Uint( + new BigInteger( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + 16)); + assertEquals( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + TypeEncoder.encodePacked(maxValue64)); + + Uint largeValue = + new Uint( + new BigInteger( + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe", + 16)); + assertEquals( + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe", + TypeEncoder.encodePacked(largeValue)); + + Uint zero72 = new Uint72(BigInteger.ZERO); + assertEquals("000000000000000000", TypeEncoder.encodePacked(zero72)); + Uint max72 = new Uint72(new BigInteger("4722366482869645213695")); + assertEquals("ffffffffffffffffff", TypeEncoder.encodePacked(max72)); + + Uint zero80 = new Uint80(BigInteger.ZERO); + assertEquals("00000000000000000000", TypeEncoder.encodePacked(zero80)); + Uint max80 = new Uint80(new BigInteger("1208925819614629174706175")); + assertEquals("ffffffffffffffffffff", TypeEncoder.encodePacked(max80)); + + Uint zero88 = new Uint88(BigInteger.ZERO); + assertEquals("0000000000000000000000", TypeEncoder.encodePacked(zero88)); + Uint max88 = new Uint88(new BigInteger("309485009821345068724781055")); + assertEquals("ffffffffffffffffffffff", TypeEncoder.encodePacked(max88)); + + Uint zero96 = new Uint96(BigInteger.ZERO); + assertEquals("000000000000000000000000", TypeEncoder.encodePacked(zero96)); + Uint max96 = new Uint96(new BigInteger("79228162514264337593543950335")); + assertEquals("ffffffffffffffffffffffff", TypeEncoder.encodePacked(max96)); + + Uint zero104 = new Uint104(BigInteger.ZERO); + assertEquals("00000000000000000000000000", TypeEncoder.encodePacked(zero104)); + Uint max104 = new Uint104(new BigInteger("20282409603651670423947251286015")); + assertEquals("ffffffffffffffffffffffffff", TypeEncoder.encodePacked(max104)); + + Uint zero112 = new Uint112(BigInteger.ZERO); + assertEquals("0000000000000000000000000000", TypeEncoder.encodePacked(zero112)); + Uint max112 = new Uint112(new BigInteger("5192296858534827628530496329220095")); + assertEquals("ffffffffffffffffffffffffffff", TypeEncoder.encodePacked(max112)); + + Uint zero120 = new Uint120(BigInteger.ZERO); + assertEquals("000000000000000000000000000000", TypeEncoder.encodePacked(zero120)); + Uint max120 = new Uint120(new BigInteger("1329227995784915872903807060280344575")); + assertEquals("ffffffffffffffffffffffffffffff", TypeEncoder.encodePacked(max120)); + + Uint zero128 = new Uint128(BigInteger.ZERO); + assertEquals("00000000000000000000000000000000", TypeEncoder.encodePacked(zero128)); + Uint max128 = new Uint128(new BigInteger("340282366920938463463374607431768211455")); + assertEquals("ffffffffffffffffffffffffffffffff", TypeEncoder.encodePacked(max128)); + + Uint zero136 = new Uint136(BigInteger.ZERO); + assertEquals("0000000000000000000000000000000000", TypeEncoder.encodePacked(zero136)); + Uint max136 = new Uint136(new BigInteger("87112285931760246646623899502532662132735")); + assertEquals("ffffffffffffffffffffffffffffffffff", TypeEncoder.encodePacked(max136)); + + Uint zero144 = new Uint144(BigInteger.ZERO); + assertEquals("000000000000000000000000000000000000", TypeEncoder.encodePacked(zero144)); + Uint max144 = new Uint144(new BigInteger("22300745198530623141535718272648361505980415")); + assertEquals("ffffffffffffffffffffffffffffffffffff", TypeEncoder.encodePacked(max144)); + + Uint zero152 = new Uint152(BigInteger.ZERO); + assertEquals("00000000000000000000000000000000000000", TypeEncoder.encodePacked(zero152)); + Uint max152 = new Uint152(new BigInteger("5708990770823839524233143877797980545530986495")); + assertEquals("ffffffffffffffffffffffffffffffffffffff", TypeEncoder.encodePacked(max152)); + + Uint zero160 = new Uint160(BigInteger.ZERO); + assertEquals("0000000000000000000000000000000000000000", TypeEncoder.encodePacked(zero160)); + Uint max160 = + new Uint160(new BigInteger("1461501637330902918203684832716283019655932542975")); + assertEquals("ffffffffffffffffffffffffffffffffffffffff", TypeEncoder.encodePacked(max160)); + + Uint zero168 = new Uint168(BigInteger.ZERO); + assertEquals( + "000000000000000000000000000000000000000000", TypeEncoder.encodePacked(zero168)); + Uint max168 = + new Uint168(new BigInteger("374144419156711147060143317175368453031918731001855")); + assertEquals( + "ffffffffffffffffffffffffffffffffffffffffff", TypeEncoder.encodePacked(max168)); + + Uint zero176 = new Uint176(BigInteger.ZERO); + assertEquals( + "00000000000000000000000000000000000000000000", TypeEncoder.encodePacked(zero176)); + Uint max176 = + new Uint176( + new BigInteger("95780971304118053647396689196894323976171195136475135")); + assertEquals( + "ffffffffffffffffffffffffffffffffffffffffffff", TypeEncoder.encodePacked(max176)); + + Uint zero184 = new Uint184(BigInteger.ZERO); + assertEquals( + "0000000000000000000000000000000000000000000000", + TypeEncoder.encodePacked(zero184)); + Uint max184 = + new Uint184( + new BigInteger("24519928653854221733733552434404946937899825954937634815")); + assertEquals( + "ffffffffffffffffffffffffffffffffffffffffffffff", TypeEncoder.encodePacked(max184)); + + Uint zero192 = new Uint192(BigInteger.ZERO); + assertEquals( + "000000000000000000000000000000000000000000000000", + TypeEncoder.encodePacked(zero192)); + Uint max192 = + new Uint192( + new BigInteger( + "6277101735386680763835789423207666416102355444464034512895")); + assertEquals( + "ffffffffffffffffffffffffffffffffffffffffffffffff", + TypeEncoder.encodePacked(max192)); + + Uint zero200 = new Uint200(BigInteger.ZERO); + assertEquals( + "00000000000000000000000000000000000000000000000000", + TypeEncoder.encodePacked(zero200)); + Uint max200 = + new Uint200( + new BigInteger( + "1606938044258990275541962092341162602522202993782792835301375")); + assertEquals( + "ffffffffffffffffffffffffffffffffffffffffffffffffff", + TypeEncoder.encodePacked(max200)); + + Uint zero208 = new Uint208(BigInteger.ZERO); + assertEquals( + "0000000000000000000000000000000000000000000000000000", + TypeEncoder.encodePacked(zero208)); + Uint max208 = + new Uint208( + new BigInteger( + "411376139330301510538742295639337626245683966408394965837152255")); + assertEquals( + "ffffffffffffffffffffffffffffffffffffffffffffffffffff", + TypeEncoder.encodePacked(max208)); + + Uint zero216 = new Uint216(BigInteger.ZERO); + assertEquals( + "000000000000000000000000000000000000000000000000000000", + TypeEncoder.encodePacked(zero216)); + Uint max216 = + new Uint216( + new BigInteger( + "105312291668557186697918027683670432318895095400549111254310977535")); + assertEquals( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffff", + TypeEncoder.encodePacked(max216)); + + Uint zero224 = new Uint224(BigInteger.ZERO); + assertEquals( + "00000000000000000000000000000000000000000000000000000000", + TypeEncoder.encodePacked(zero224)); + Uint max224 = + new Uint224( + new BigInteger( + "26959946667150639794667015087019630673637144422540572481103610249215")); + assertEquals( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + TypeEncoder.encodePacked(max224)); + + Uint zero232 = new Uint232(BigInteger.ZERO); + assertEquals( + "0000000000000000000000000000000000000000000000000000000000", + TypeEncoder.encodePacked(zero232)); + Uint max232 = + new Uint232( + new BigInteger( + "6901746346790563787434755862277025452451108972170386555162524223799295")); + assertEquals( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + TypeEncoder.encodePacked(max232)); + + Uint zero240 = new Uint240(BigInteger.ZERO); + assertEquals( + "000000000000000000000000000000000000000000000000000000000000", + TypeEncoder.encodePacked(zero240)); + Uint max240 = + new Uint240( + new BigInteger( + "1766847064778384329583297500742918515827483896875618958121606201292619775")); + assertEquals( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + TypeEncoder.encodePacked(max240)); + + Uint zero248 = new Uint248(BigInteger.ZERO); + assertEquals( + "00000000000000000000000000000000000000000000000000000000000000", + TypeEncoder.encodePacked(zero248)); + Uint max248 = + new Uint248( + new BigInteger( + "452312848583266388373324160190187140051835877600158453279131187530910662655")); + assertEquals( + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + TypeEncoder.encodePacked(max248)); + } + + @Test + public void testIntEncodePacked() { + Int zero8 = new Int8(BigInteger.ZERO); + assertEquals("00", TypeEncoder.encodePacked(zero8)); + + Int max8 = new Int8(BigInteger.valueOf(127)); + assertEquals("7f", TypeEncoder.encodePacked(max8)); + + Int min8 = new Int8(BigInteger.valueOf(-128)); + assertEquals("80", TypeEncoder.encodePacked(min8)); + + Int zero16 = new Int16(BigInteger.ZERO); + assertEquals("0000", TypeEncoder.encodePacked(zero16)); + + Int max16 = new Int16(BigInteger.valueOf(32767)); + assertEquals("7fff", TypeEncoder.encodePacked(max16)); + + Int min16 = new Int16(BigInteger.valueOf(-32768)); + assertEquals("8000", TypeEncoder.encodePacked(min16)); + + Int zero24 = new Int24(BigInteger.ZERO); + assertEquals("000000", TypeEncoder.encodePacked(zero24)); + + Int max24 = new Int24(BigInteger.valueOf(8388607)); + assertEquals("7fffff", TypeEncoder.encodePacked(max24)); + + Int min24 = new Int24(BigInteger.valueOf(-8388608)); + assertEquals("800000", TypeEncoder.encodePacked(min24)); + + Int zero32 = new Int32(BigInteger.ZERO); + assertEquals("00000000", TypeEncoder.encodePacked(zero32)); + + Int max32 = new Int32(BigInteger.valueOf(2147483647)); + assertEquals("7fffffff", TypeEncoder.encodePacked(max32)); + + Int min32 = new Int32(BigInteger.valueOf(-2147483648)); + assertEquals("80000000", TypeEncoder.encodePacked(min32)); + + Int zero40 = new Int40(BigInteger.ZERO); + assertEquals("0000000000", TypeEncoder.encodePacked(zero40)); + + Int max40 = new Int40(BigInteger.valueOf(549755813887L)); + assertEquals("7fffffffff", TypeEncoder.encodePacked(max40)); + + Int min40 = new Int40(BigInteger.valueOf(-549755813888L)); + assertEquals("8000000000", TypeEncoder.encodePacked(min40)); + + Int zero48 = new Int48(BigInteger.ZERO); + assertEquals("000000000000", TypeEncoder.encodePacked(zero48)); + + Int max48 = new Int48(BigInteger.valueOf(140737488355327L)); + assertEquals("7fffffffffff", TypeEncoder.encodePacked(max48)); + + Int min48 = new Int48(BigInteger.valueOf(-140737488355328L)); + assertEquals("800000000000", TypeEncoder.encodePacked(min48)); + + Int zero56 = new Int56(BigInteger.ZERO); + assertEquals("00000000000000", TypeEncoder.encodePacked(zero56)); + + Int max56 = new Int56(BigInteger.valueOf(36028797018963967L)); + assertEquals("7fffffffffffff", TypeEncoder.encodePacked(max56)); + + Int min56 = new Int56(BigInteger.valueOf(-36028797018963968L)); + assertEquals("80000000000000", TypeEncoder.encodePacked(min56)); + + Int zero64 = new Int64(BigInteger.ZERO); + assertEquals("0000000000000000", TypeEncoder.encodePacked(zero64)); + + Int max64 = new Int64(BigInteger.valueOf(java.lang.Long.MAX_VALUE)); + assertEquals("7fffffffffffffff", TypeEncoder.encodePacked(max64)); + + Int min64 = new Int64(BigInteger.valueOf(java.lang.Long.MIN_VALUE)); + assertEquals("8000000000000000", TypeEncoder.encodePacked(min64)); + + Int zero72 = new Int72(BigInteger.ZERO); + assertEquals("000000000000000000", TypeEncoder.encodePacked(zero72)); + + Int max72 = new Int72(new BigInteger("2361183241434822606847")); + assertEquals("7fffffffffffffffff", TypeEncoder.encodePacked(max72)); + + Int min72 = new Int72(new BigInteger("-2361183241434822606848")); + assertEquals("800000000000000000", TypeEncoder.encodePacked(min72)); + + Int zero80 = new Int80(BigInteger.ZERO); + assertEquals("00000000000000000000", TypeEncoder.encodePacked(zero80)); + + Int max80 = new Int80(new BigInteger("604462909807314587353087")); + assertEquals("7fffffffffffffffffff", TypeEncoder.encodePacked(max80)); + + Int min80 = new Int80(new BigInteger("-604462909807314587353088")); + assertEquals("80000000000000000000", TypeEncoder.encodePacked(min80)); + + Int zero88 = new Int88(BigInteger.ZERO); + assertEquals("0000000000000000000000", TypeEncoder.encodePacked(zero88)); + + Int max88 = new Int88(new BigInteger("154742504910672534362390527")); + assertEquals("7fffffffffffffffffffff", TypeEncoder.encodePacked(max88)); + + Int min88 = new Int88(new BigInteger("-154742504910672534362390528")); + assertEquals("8000000000000000000000", TypeEncoder.encodePacked(min88)); + + Int zero96 = new Int96(BigInteger.ZERO); + assertEquals("000000000000000000000000", TypeEncoder.encodePacked(zero96)); + + Int max96 = new Int96(new BigInteger("39614081257132168796771975167")); + assertEquals("7fffffffffffffffffffffff", TypeEncoder.encodePacked(max96)); + + Int min96 = new Int96(new BigInteger("-39614081257132168796771975168")); + assertEquals("800000000000000000000000", TypeEncoder.encodePacked(min96)); + + Int zero104 = new Int104(BigInteger.ZERO); + assertEquals("00000000000000000000000000", TypeEncoder.encodePacked(zero104)); + + Int max104 = new Int104(new BigInteger("10141204801825835211973625643007")); + assertEquals("7fffffffffffffffffffffffff", TypeEncoder.encodePacked(max104)); + + Int min104 = new Int104(new BigInteger("-10141204801825835211973625643008")); + assertEquals("80000000000000000000000000", TypeEncoder.encodePacked(min104)); + + Int zero112 = new Int112(BigInteger.ZERO); + + assertEquals("0000000000000000000000000000", TypeEncoder.encodePacked(zero112)); + + Int max112 = new Int112(new BigInteger("2596148429267413814265248164610047")); + assertEquals("7fffffffffffffffffffffffffff", TypeEncoder.encodePacked(max112)); + + Int min112 = new Int112(new BigInteger("-2596148429267413814265248164610048")); + assertEquals("8000000000000000000000000000", TypeEncoder.encodePacked(min112)); + + Int zero120 = new Int120(BigInteger.ZERO); + + assertEquals("000000000000000000000000000000", TypeEncoder.encodePacked(zero120)); + + Int max120 = new Int120(new BigInteger("664613997892457936451903530140172287")); + assertEquals("7fffffffffffffffffffffffffffff", TypeEncoder.encodePacked(max120)); + + Int min120 = new Int120(new BigInteger("-664613997892457936451903530140172288")); + assertEquals("800000000000000000000000000000", TypeEncoder.encodePacked(min120)); + + Int zero128 = new Int128(BigInteger.ZERO); + + assertEquals("00000000000000000000000000000000", TypeEncoder.encodePacked(zero128)); + + Int max128 = new Int128(new BigInteger("170141183460469231731687303715884105727")); + assertEquals("7fffffffffffffffffffffffffffffff", TypeEncoder.encodePacked(max128)); + + Int min128 = new Int128(new BigInteger("-170141183460469231731687303715884105728")); + assertEquals("80000000000000000000000000000000", TypeEncoder.encodePacked(min128)); + + Int zero136 = new Int136(BigInteger.ZERO); + + assertEquals("0000000000000000000000000000000000", TypeEncoder.encodePacked(zero136)); + + Int max136 = new Int136(new BigInteger("43556142965880123323311949751266331066367")); + assertEquals("7fffffffffffffffffffffffffffffffff", TypeEncoder.encodePacked(max136)); + + Int min136 = new Int136(new BigInteger("-43556142965880123323311949751266331066368")); + assertEquals("8000000000000000000000000000000000", TypeEncoder.encodePacked(min136)); + + Int zero144 = new Int144(BigInteger.ZERO); + + assertEquals("000000000000000000000000000000000000", TypeEncoder.encodePacked(zero144)); + + Int max144 = new Int144(new BigInteger("11150372599265311570767859136324180752990207")); + assertEquals("7fffffffffffffffffffffffffffffffffff", TypeEncoder.encodePacked(max144)); + + Int min144 = new Int144(new BigInteger("-11150372599265311570767859136324180752990208")); + assertEquals("800000000000000000000000000000000000", TypeEncoder.encodePacked(min144)); + + Int zero152 = new Int152(BigInteger.ZERO); + + assertEquals("00000000000000000000000000000000000000", TypeEncoder.encodePacked(zero152)); + + Int max152 = new Int152(new BigInteger("2854495385411919762116571938898990272765493247")); + assertEquals("7fffffffffffffffffffffffffffffffffffff", TypeEncoder.encodePacked(max152)); + + Int min152 = new Int152(new BigInteger("-2854495385411919762116571938898990272765493248")); + assertEquals("80000000000000000000000000000000000000", TypeEncoder.encodePacked(min152)); + + Int zero160 = new Int160(BigInteger.ZERO); + + assertEquals("0000000000000000000000000000000000000000", TypeEncoder.encodePacked(zero160)); + + Int max160 = new Int160(new BigInteger("730750818665451459101842416358141509827966271487")); + assertEquals("7fffffffffffffffffffffffffffffffffffffff", TypeEncoder.encodePacked(max160)); + + Int min160 = + new Int160(new BigInteger("-730750818665451459101842416358141509827966271488")); + assertEquals("8000000000000000000000000000000000000000", TypeEncoder.encodePacked(min160)); + + Int zero168 = new Int168(BigInteger.ZERO); + + assertEquals( + "000000000000000000000000000000000000000000", TypeEncoder.encodePacked(zero168)); + + Int max168 = + new Int168(new BigInteger("187072209578355573530071658587684226515959365500927")); + assertEquals( + "7fffffffffffffffffffffffffffffffffffffffff", TypeEncoder.encodePacked(max168)); + + Int min168 = + new Int168(new BigInteger("-187072209578355573530071658587684226515959365500928")); + assertEquals( + "800000000000000000000000000000000000000000", TypeEncoder.encodePacked(min168)); + + Int zero176 = new Int176(BigInteger.ZERO); + + assertEquals( + "00000000000000000000000000000000000000000000", TypeEncoder.encodePacked(zero176)); + + Int max176 = + new Int176(new BigInteger("47890485652059026823698344598447161988085597568237567")); + assertEquals( + "7fffffffffffffffffffffffffffffffffffffffffff", TypeEncoder.encodePacked(max176)); + + Int min176 = + new Int176( + new BigInteger("-47890485652059026823698344598447161988085597568237568")); + assertEquals( + "80000000000000000000000000000000000000000000", TypeEncoder.encodePacked(min176)); + + Int zero184 = new Int184(BigInteger.ZERO); + + assertEquals( + "0000000000000000000000000000000000000000000000", + TypeEncoder.encodePacked(zero184)); + + Int max184 = + new Int184( + new BigInteger("12259964326927110866866776217202473468949912977468817407")); + assertEquals( + "7fffffffffffffffffffffffffffffffffffffffffffff", TypeEncoder.encodePacked(max184)); + + Int min184 = + new Int184( + new BigInteger( + "-12259964326927110866866776217202473468949912977468817408")); + assertEquals( + "8000000000000000000000000000000000000000000000", TypeEncoder.encodePacked(min184)); + + Int zero192 = new Int192(BigInteger.ZERO); + + assertEquals( + "000000000000000000000000000000000000000000000000", + TypeEncoder.encodePacked(zero192)); + + Int max192 = + new Int192( + new BigInteger( + "3138550867693340381917894711603833208051177722232017256447")); + assertEquals( + "7fffffffffffffffffffffffffffffffffffffffffffffff", + TypeEncoder.encodePacked(max192)); + + Int min192 = + new Int192( + new BigInteger( + "-3138550867693340381917894711603833208051177722232017256448")); + assertEquals( + "800000000000000000000000000000000000000000000000", + TypeEncoder.encodePacked(min192)); + + Int zero200 = new Int200(BigInteger.ZERO); + + assertEquals( + "00000000000000000000000000000000000000000000000000", + TypeEncoder.encodePacked(zero200)); + + Int max200 = + new Int200( + new BigInteger( + "803469022129495137770981046170581301261101496891396417650687")); + assertEquals( + "7fffffffffffffffffffffffffffffffffffffffffffffffff", + TypeEncoder.encodePacked(max200)); + + Int min200 = + new Int200( + new BigInteger( + "-803469022129495137770981046170581301261101496891396417650688")); + assertEquals( + "80000000000000000000000000000000000000000000000000", + TypeEncoder.encodePacked(min200)); + + Int zero208 = new Int208(BigInteger.ZERO); + + assertEquals( + "0000000000000000000000000000000000000000000000000000", + TypeEncoder.encodePacked(zero208)); + + Int max208 = + new Int208( + new BigInteger( + "205688069665150755269371147819668813122841983204197482918576127")); + assertEquals( + "7fffffffffffffffffffffffffffffffffffffffffffffffffff", + TypeEncoder.encodePacked(max208)); + + Int min208 = + new Int208( + new BigInteger( + "-205688069665150755269371147819668813122841983204197482918576128")); + assertEquals( + "8000000000000000000000000000000000000000000000000000", + TypeEncoder.encodePacked(min208)); + + Int zero216 = new Int216(BigInteger.ZERO); + + assertEquals( + "000000000000000000000000000000000000000000000000000000", + TypeEncoder.encodePacked(zero216)); + + Int max216 = + new Int216( + new BigInteger( + "52656145834278593348959013841835216159447547700274555627155488767")); + assertEquals( + "7fffffffffffffffffffffffffffffffffffffffffffffffffffff", + TypeEncoder.encodePacked(max216)); + + Int min216 = + new Int216( + new BigInteger( + "-52656145834278593348959013841835216159447547700274555627155488768")); + assertEquals( + "800000000000000000000000000000000000000000000000000000", + TypeEncoder.encodePacked(min216)); + + Int zero224 = new Int224(BigInteger.ZERO); + + assertEquals( + "00000000000000000000000000000000000000000000000000000000", + TypeEncoder.encodePacked(zero224)); + + Int max224 = + new Int224( + new BigInteger( + "13479973333575319897333507543509815336818572211270286240551805124607")); + assertEquals( + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffff", + TypeEncoder.encodePacked(max224)); + + Int min224 = + new Int224( + new BigInteger( + "-13479973333575319897333507543509815336818572211270286240551805124608")); + assertEquals( + "80000000000000000000000000000000000000000000000000000000", + TypeEncoder.encodePacked(min224)); + + Int zero232 = new Int232(BigInteger.ZERO); + + assertEquals( + "0000000000000000000000000000000000000000000000000000000000", + TypeEncoder.encodePacked(zero232)); + + Int max232 = + new Int232( + new BigInteger( + "3450873173395281893717377931138512726225554486085193277581262111899647")); + assertEquals( + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + TypeEncoder.encodePacked(max232)); + + Int min232 = + new Int232( + new BigInteger( + "-3450873173395281893717377931138512726225554486085193277581262111899648")); + assertEquals( + "8000000000000000000000000000000000000000000000000000000000", + TypeEncoder.encodePacked(min232)); + + Int zero240 = new Int240(BigInteger.ZERO); + + assertEquals( + "000000000000000000000000000000000000000000000000000000000000", + TypeEncoder.encodePacked(zero240)); + + Int max240 = + new Int240( + new BigInteger( + "883423532389192164791648750371459257913741948437809479060803100646309887")); + assertEquals( + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + TypeEncoder.encodePacked(max240)); + + Int min240 = + new Int240( + new BigInteger( + "-883423532389192164791648750371459257913741948437809479060803100646309888")); + assertEquals( + "800000000000000000000000000000000000000000000000000000000000", + TypeEncoder.encodePacked(min240)); + + Int zero248 = new Int248(BigInteger.ZERO); + + assertEquals( + "00000000000000000000000000000000000000000000000000000000000000", + TypeEncoder.encodePacked(zero248)); + + Int max248 = + new Int248( + new BigInteger( + "226156424291633194186662080095093570025917938800079226639565593765455331327")); + assertEquals( + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + TypeEncoder.encodePacked(max248)); + + Int min248 = + new Int248( + new BigInteger( + "-226156424291633194186662080095093570025917938800079226639565593765455331328")); + assertEquals( + "80000000000000000000000000000000000000000000000000000000000000", + TypeEncoder.encodePacked(min248)); + Int minusOne = new Int(BigInteger.valueOf(-1)); + assertEquals( + TypeEncoder.encodePacked(minusOne), + ("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); + } + + @Test + public void testStaticBytesEncodePacked() { + Bytes staticBytes = new Bytes6(new byte[] {0, 1, 2, 3, 4, 5}); + assertEquals(TypeEncoder.encodePacked(staticBytes), ("000102030405")); + + Bytes empty = new Bytes1(new byte[] {0}); + assertEquals(TypeEncoder.encodePacked(empty), ("00")); + + Bytes ones = new Bytes1(new byte[] {127}); + assertEquals(TypeEncoder.encodePacked(ones), ("7f")); + + Bytes dave = new Bytes4("dave".getBytes()); + assertEquals(TypeEncoder.encodePacked(dave), ("64617665")); + } + + @Test + public void testUtf8StringEncodePacked() { + Utf8String string = new Utf8String("Hello, world!"); + assertEquals("48656c6c6f2c20776f726c6421", TypeEncoder.encodePacked(string)); + + Utf8String largeString = new Utf8String("Very long string value for test!"); + assertEquals( + "56657279206c6f6e6720737472696e672076616c756520666f72207465737421", + TypeEncoder.encodePacked(largeString)); + + Utf8String veryLargeString = + new Utf8String("Very long string value for test!Very long string value for test!"); + assertEquals( + "56657279206c6f6e6720737472696e672076616c756520666f722074657374" + + "2156657279206c6f6e6720737472696e672076616c756520666f72207465737421", + TypeEncoder.encodePacked(veryLargeString)); + } + + @Test + @SuppressWarnings("unchecked") + public void testSupertypeDeclaredArrayEncodePacked() { + // Arrays declared with a supertype componentType (e.g. Type.class) blind + // the componentType gate; the actual element types must be validated too. + + // Dynamic scalar elements smuggled under Type.class. + DynamicArray hiddenString = + new DynamicArray<>(Type.class, new Uint16(0x45), new Utf8String("x")); + assertThrows( + UnsupportedOperationException.class, () -> TypeEncoder.encodePacked(hiddenString)); + + // Structs resurface one level down inside a supertype-declared array. + DynamicArray hiddenStruct = + new DynamicArray<>(Type.class, new AbiV2TestFixture.Foo("id", "name")); + assertThrows( + UnsupportedOperationException.class, () -> TypeEncoder.encodePacked(hiddenStruct)); + + // Nested arrays, likewise forbidden in packed mode. + DynamicArray hiddenNestedArray = + new DynamicArray<>( + Type.class, new DynamicArray<>(Uint16.class, new Uint16(0x45))); + assertThrows( + UnsupportedOperationException.class, + () -> TypeEncoder.encodePacked(hiddenNestedArray)); + + // Benign supertype declaration: allowed element types still encode, and + // identically to a precisely-declared array. + DynamicArray benign = + new DynamicArray<>(Type.class, new Uint16(0x45), new Uint16(0x7)); + assertEquals( + TypeEncoder.encodePacked( + new DynamicArray<>(Uint16.class, new Uint16(0x45), new Uint16(0x7))), + TypeEncoder.encodePacked(benign)); + } + + @Test + public void testStaticArrayEncodePacked() { + StaticArray0 empty = new StaticArray0<>(Uint16.class); + + assertEquals("", TypeEncoder.encodePacked(empty)); + + StaticArray3 array = + new StaticArray3<>( + Uint16.class, new Uint16(0x45), new Uint16(0x7), new Uint16(65535)); + + assertEquals( + "0000000000000000000000000000000000000000000000000000000000000045" + + "0000000000000000000000000000000000000000000000000000000000000007" + + "000000000000000000000000000000000000000000000000000000000000ffff", + TypeEncoder.encodePacked(array)); + + StaticArray3 strings = + new StaticArray3<>( + Utf8String.class, + new Utf8String("test"), + new Utf8String("test"), + new Utf8String("test")); + + Assertions.assertThrows( + UnsupportedOperationException.class, () -> TypeEncoder.encodePacked(strings)); + + StaticArray3 dynamicStuct = + new StaticArray3<>( + AbiV2TestFixture.Foo.class, + new AbiV2TestFixture.Foo("", ""), + new AbiV2TestFixture.Foo("id", "name"), + new AbiV2TestFixture.Foo("", "")); + Assertions.assertThrows( + UnsupportedOperationException.class, () -> TypeEncoder.encodePacked(dynamicStuct)); + + StaticArray3 staticStruct = + new StaticArray3<>( + AbiV2TestFixture.Bar.class, + new AbiV2TestFixture.Bar(BigInteger.ONE, BigInteger.ZERO), + new AbiV2TestFixture.Bar(BigInteger.ONE, BigInteger.ZERO), + new AbiV2TestFixture.Bar(BigInteger.ONE, BigInteger.ZERO)); + + Assertions.assertThrows( + UnsupportedOperationException.class, () -> TypeEncoder.encodePacked(staticStruct)); + + StaticArray ufixed = + new StaticArray2<>( + Ufixed.class, + new Ufixed(BigInteger.valueOf(0x2), BigInteger.valueOf(0x2)), + new Ufixed(BigInteger.valueOf(0x8), BigInteger.valueOf(0x8))); + + Assertions.assertThrows( + UnsupportedOperationException.class, () -> TypeEncoder.encodePacked(ufixed)); + + StaticArray fixed = + new StaticArray2<>( + Fixed.class, + new Fixed(BigInteger.valueOf(0x2), BigInteger.valueOf(0x2)), + new Fixed(BigInteger.valueOf(0x8), BigInteger.valueOf(0x8))); + + Assertions.assertThrows( + UnsupportedOperationException.class, () -> TypeEncoder.encodePacked(fixed)); + + StaticArray arrayOfEmptyBytes = + new StaticArray2<>( + DynamicBytes.class, + new DynamicBytes(new byte[0]), + new DynamicBytes(new byte[0])); + + Assertions.assertThrows( + UnsupportedOperationException.class, + () -> TypeEncoder.encodePacked(arrayOfEmptyBytes)); + } + + @Test + public void testStructEncodePackedNotSupported() { + // Structs passed DIRECTLY to encodePacked (not as array elements) must be + // rejected: abi.encodePacked does not support structs. Since DynamicStruct + // extends DynamicArray and StaticStruct extends StaticArray, without an explicit + // guard they would slip through the array dispatch and yield corrupt bytes. + Assertions.assertThrows( + UnsupportedOperationException.class, + () -> TypeEncoder.encodePacked(new AbiV2TestFixture.Foo("id", "name"))); + + Assertions.assertThrows( + UnsupportedOperationException.class, + () -> TypeEncoder.encodePacked( + new AbiV2TestFixture.Bar(BigInteger.ONE, BigInteger.TEN))); + + Assertions.assertThrows( + UnsupportedOperationException.class, + () -> TypeEncoder.encodePacked( + new DynamicStruct(new Uint(BigInteger.valueOf(7)), new Utf8String("hi")))); + + Assertions.assertThrows( + UnsupportedOperationException.class, + () -> TypeEncoder.encodePacked( + new StaticStruct( + new Uint(BigInteger.ONE), new Uint(BigInteger.valueOf(2))))); + + // Same rejection through the public FunctionEncoder entry point. + Assertions.assertThrows( + UnsupportedOperationException.class, + () -> FunctionEncoder.encodeConstructorPacked( + java.util.Collections.singletonList( + new AbiV2TestFixture.Foo("id", "name")))); + } + + @Test + public void testDynamicBytesEncodePacked() { + DynamicBytes dynamicBytes = new DynamicBytes(new byte[] {0, 1, 2, 3, 4, 5}); + assertEquals("000102030405", TypeEncoder.encodePacked(dynamicBytes)); + + DynamicBytes zero = new DynamicBytes(new byte[] {0}); + assertEquals("00", TypeEncoder.encodePacked(zero)); + + DynamicBytes empty = new DynamicBytes(new byte[] {}); + assertEquals("", TypeEncoder.encodePacked(empty)); + + DynamicBytes dave = new DynamicBytes("dave".getBytes()); + assertEquals("64617665", TypeEncoder.encodePacked(dave)); + + DynamicBytes loremIpsum = + new DynamicBytes( + ("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod " + + "tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim " + + "veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex " + + "ea commodo consequat. Duis aute irure dolor in reprehenderit in " + + "voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur " + + "sint occaecat cupidatat non proident, sunt in culpa qui officia " + + "deserunt mollit anim id est laborum.") + .getBytes()); + assertEquals( + ("4c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73" + + "656374657475722061646970697363696e6720656c69742c2073656420646f20" + + "656975736d6f642074656d706f7220696e6369646964756e74207574206c6162" + + "6f726520657420646f6c6f7265206d61676e6120616c697175612e2055742065" + + "6e696d206164206d696e696d2076656e69616d2c2071756973206e6f73747275" + + "6420657865726369746174696f6e20756c6c616d636f206c61626f726973206e" + + "69736920757420616c697175697020657820656120636f6d6d6f646f20636f6e" + + "7365717561742e2044756973206175746520697275726520646f6c6f7220696e" + + "20726570726568656e646572697420696e20766f6c7570746174652076656c69" + + "7420657373652063696c6c756d20646f6c6f726520657520667567696174206e" + + "756c6c612070617269617475722e204578636570746575722073696e74206f63" + + "63616563617420637570696461746174206e6f6e2070726f6964656e742c2073" + + "756e7420696e2063756c706120717569206f666669636961206465736572756e" + + "74206d6f6c6c697420616e696d20696420657374206c61626f72756d2e"), + TypeEncoder.encodePacked(loremIpsum)); + } + + @Test + public void testDynamicArrayEncodePacked() { + DynamicArray empty = new DynamicArray<>(Uint.class); + + assertEquals("", TypeEncoder.encodePacked(empty)); + + DynamicArray array = + new DynamicArray<>( + Uint.class, + new Uint(BigInteger.ONE), + new Uint(BigInteger.valueOf(2)), + new Uint(BigInteger.valueOf(3))); + + assertEquals( + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000003", + TypeEncoder.encodePacked(array)); + + DynamicArray uints = + new DynamicArray<>( + Uint32.class, + new Uint32(BigInteger.ONE), + new Uint32(BigInteger.valueOf(2))); + + assertEquals( + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + TypeEncoder.encodePacked(uints)); + + DynamicArray strings = + new DynamicArray<>( + Utf8String.class, + new Utf8String("one"), + new Utf8String("two"), + new Utf8String("three")); + + Assertions.assertThrows( + UnsupportedOperationException.class, () -> TypeEncoder.encodePacked(strings)); + + DynamicArray dynamicStuct = + new DynamicArray<>( + AbiV2TestFixture.Foo.class, + new AbiV2TestFixture.Foo("", ""), + new AbiV2TestFixture.Foo("id", "name"), + new AbiV2TestFixture.Foo("", "")); + Assertions.assertThrows( + UnsupportedOperationException.class, () -> TypeEncoder.encodePacked(dynamicStuct)); + + DynamicArray staticStruct = + new DynamicArray<>( + AbiV2TestFixture.Bar.class, + new AbiV2TestFixture.Bar(BigInteger.ONE, BigInteger.ZERO), + new AbiV2TestFixture.Bar(BigInteger.ONE, BigInteger.ZERO), + new AbiV2TestFixture.Bar(BigInteger.ONE, BigInteger.ZERO)); + + Assertions.assertThrows( + UnsupportedOperationException.class, () -> TypeEncoder.encodePacked(staticStruct)); + + DynamicArray ufixed = + new DynamicArray<>( + Ufixed.class, + new Ufixed(BigInteger.valueOf(0x2), BigInteger.valueOf(0x2)), + new Ufixed(BigInteger.valueOf(0x8), BigInteger.valueOf(0x8))); + + Assertions.assertThrows( + UnsupportedOperationException.class, () -> TypeEncoder.encodePacked(ufixed)); + + DynamicArray fixed = + new DynamicArray<>( + Fixed.class, + new Fixed(BigInteger.valueOf(0x2), BigInteger.valueOf(0x2)), + new Fixed(BigInteger.valueOf(0x8), BigInteger.valueOf(0x8))); + + Assertions.assertThrows( + UnsupportedOperationException.class, () -> TypeEncoder.encodePacked(fixed)); + + DynamicArray arrayOfEmptyBytes = + new DynamicArray<>( + DynamicBytes.class, + new DynamicBytes(new byte[0]), + new DynamicBytes(new byte[0])); + + Assertions.assertThrows( + UnsupportedOperationException.class, + () -> TypeEncoder.encodePacked(arrayOfEmptyBytes)); + } + + @Test + public void testPrimitiveByteEncodePacked() { + assertEquals("00", TypeEncoder.encodePacked(new Byte((byte) 0))); + assertEquals("7f", TypeEncoder.encodePacked(new Byte((byte) 127))); + } + + @Test + public void testPrimitiveCharEncodePacked() { + assertEquals("61", TypeEncoder.encodePacked(new Char('a'))); + assertEquals("20", TypeEncoder.encodePacked(new Char(' '))); + } + + @Test + public void testPrimitiveIntEncodePacked() { + assertEquals( + "00000000", TypeEncoder.encodePacked(new org.tron.trident.abi.datatypes.primitive.Int(0))); + + assertEquals( + "80000000", + TypeEncoder.encodePacked( + new org.tron.trident.abi.datatypes.primitive.Int(Integer.MIN_VALUE))); + + assertEquals( + "7fffffff", + TypeEncoder.encodePacked( + new org.tron.trident.abi.datatypes.primitive.Int(Integer.MAX_VALUE))); + } + + @Test + public void testPrimitiveShortEncodePacked() { + assertEquals("0000", TypeEncoder.encodePacked(new Short((short) 0))); + + assertEquals("8000", TypeEncoder.encodePacked(new Short(java.lang.Short.MIN_VALUE))); + + assertEquals("7fff", TypeEncoder.encodePacked(new Short(java.lang.Short.MAX_VALUE))); + } + + @Test + public void testPrimitiveLongEncodePacked() { + assertEquals("0000000000000000", TypeEncoder.encodePacked(new Long(0))); + + assertEquals( + "8000000000000000", TypeEncoder.encodePacked(new Long(java.lang.Long.MIN_VALUE))); + + assertEquals( + "7fffffffffffffff", TypeEncoder.encodePacked(new Long(java.lang.Long.MAX_VALUE))); + } + + @Test + public void testPrimitiveFloatEncodePacked() { + assertThrows( + UnsupportedOperationException.class, + () -> TypeEncoder.encodePacked(new org.tron.trident.abi.datatypes.primitive.Float(0))); + } + + @Test + public void testPrimitiveDouble() { + assertThrows( + UnsupportedOperationException.class, + () -> TypeEncoder.encodePacked(new org.tron.trident.abi.datatypes.primitive.Double(0))); + } + + @Test + public void testEncodePackedRejectsNestedStaticArray() { + // Solidity's abi.encodePacked rejects nested arrays at compile time; before + // this guard the encoder silently fell back to standard ABI encoding + // (offset/length words), producing bytes that hash to wrong signatures. + StaticArray2> nestedStatic = + new StaticArray2<>( + new StaticArray2<>(Bool.class, new Bool(true), new Bool(false)), + new StaticArray2<>(Bool.class, new Bool(false), new Bool(true))); + assertThrows( + UnsupportedOperationException.class, + () -> TypeEncoder.encodePacked(nestedStatic)); + + StaticArray2> nestedDynamicLeaf = + new StaticArray2<>( + new StaticArray2<>(Utf8String.class, new Utf8String("a"), new Utf8String("b")), + new StaticArray2<>(Utf8String.class, new Utf8String("c"), new Utf8String("d"))); + assertThrows( + UnsupportedOperationException.class, + () -> TypeEncoder.encodePacked(nestedDynamicLeaf)); + } + + @Test + @SuppressWarnings({"unchecked", "rawtypes"}) + public void testEncodePackedRejectsStaticArrayInDynamicArray() { + DynamicArray nested = + new DynamicArray( + StaticArray2.class, + new StaticArray2<>(Bool.class, new Bool(true), new Bool(false))); + assertThrows( + UnsupportedOperationException.class, + () -> TypeEncoder.encodePacked(nested)); + } +} diff --git a/abi/src/test/java/org/tron/trident/abi/TypeEncoderTest.java b/abi/src/test/java/org/tron/trident/abi/TypeEncoderTest.java index 49f43461..a661ba00 100644 --- a/abi/src/test/java/org/tron/trident/abi/TypeEncoderTest.java +++ b/abi/src/test/java/org/tron/trident/abi/TypeEncoderTest.java @@ -15,10 +15,15 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.tron.trident.abi.TypeEncoder.encode; import java.math.BigInteger; +import java.util.Arrays; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.tron.trident.abi.AbiV2TestFixture.Bar; +import org.tron.trident.abi.AbiV2TestFixture.Foo; import org.tron.trident.abi.datatypes.Address; import org.tron.trident.abi.datatypes.Bool; import org.tron.trident.abi.datatypes.Bytes; @@ -63,6 +68,7 @@ import org.tron.trident.abi.datatypes.generated.Int88; import org.tron.trident.abi.datatypes.generated.Int96; import org.tron.trident.abi.datatypes.generated.StaticArray2; +import org.tron.trident.abi.datatypes.generated.StaticArray3; import org.tron.trident.abi.datatypes.generated.Uint104; import org.tron.trident.abi.datatypes.generated.Uint112; import org.tron.trident.abi.datatypes.generated.Uint120; @@ -84,6 +90,7 @@ import org.tron.trident.abi.datatypes.generated.Uint24; import org.tron.trident.abi.datatypes.generated.Uint240; import org.tron.trident.abi.datatypes.generated.Uint248; +import org.tron.trident.abi.datatypes.generated.Uint256; import org.tron.trident.abi.datatypes.generated.Uint32; import org.tron.trident.abi.datatypes.generated.Uint40; import org.tron.trident.abi.datatypes.generated.Uint48; @@ -1215,6 +1222,137 @@ public void testDynamicArray() { + "0000000000000000000000000000000000000000000000000000000000000003")); } + @Test + public void testDynamicStringsArray() { + DynamicArray array = + new DynamicArray<>( + Utf8String.class, + new Utf8String("web3j"), + new Utf8String("arrays"), + new Utf8String("encoding")); + + assertEquals( + ("0000000000000000000000000000000000000000000000000000000000000003" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "00000000000000000000000000000000000000000000000000000000000000a0" + + "00000000000000000000000000000000000000000000000000000000000000e0" + + "0000000000000000000000000000000000000000000000000000000000000005" + + "776562336a000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000006" + + "6172726179730000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000008" + + "656e636f64696e67000000000000000000000000000000000000000000000000"), + TypeEncoder.encodeDynamicArray(array)); + } + + @Test + public void testDynamicArrayOfDynamicArraysOfStaticStructs() { + DynamicArray> array = + new DynamicArray( + DynamicArray.class, + Arrays.asList( + new DynamicArray( + Bar.class, + new Bar( + new Uint256(BigInteger.ZERO), + new Uint256(BigInteger.ZERO))), + new DynamicArray( + Bar.class, + new Bar( + new Uint256(BigInteger.ONE), + new Uint256(BigInteger.ZERO))))); + assertEquals( + ("0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "00000000000000000000000000000000000000000000000000000000000000a0" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000000"), + TypeEncoder.encodeDynamicArray(array)); + } + + @Test + public void testStructsDynamicArray() { + DynamicArray array = + new DynamicArray<>( + Foo.class, + new Foo("id", "name"), + new Foo("id", "name"), + new Foo("id", "name")); + + assertEquals( + TypeEncoder.encodeDynamicArray(array), + ("0000000000000000000000000000000000000000000000000000000000000003" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000120" + + "00000000000000000000000000000000000000000000000000000000000001e0" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000")); + } + + @Test + public void testDynamicStructStaticArray() { + StaticArray3 array = + new StaticArray3<>( + Foo.class, new Foo("", ""), new Foo("id", "name"), new Foo("", "")); + + assertEquals( + ("0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "6964000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6e616d6500000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000"), + TypeEncoder.encodeArrayValues(array)); + } + + @Test + public void testStaticStructStaticArray() { + StaticArray3 array = + new StaticArray3<>( + Bar.class, + new Bar(BigInteger.ONE, BigInteger.ZERO), + new Bar(BigInteger.ONE, BigInteger.ZERO), + new Bar(BigInteger.ONE, BigInteger.ZERO)); + + assertEquals( + TypeEncoder.encodeArrayValues(array), + ("0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000000")); + } + + @SuppressWarnings("unchecked") @Test public void testEmptyArray() { @@ -1354,21 +1492,21 @@ public void testArrayOfStrings() { @Test public void testPrimitiveByte() { Assertions.assertEquals( - TypeEncoder.encode(new Byte((byte) 0)), + encode(new Byte((byte) 0)), ("0000000000000000000000000000000000000000000000000000000000000000")); Assertions.assertEquals( - TypeEncoder.encode(new Byte((byte) 127)), + encode(new Byte((byte) 127)), ("7f00000000000000000000000000000000000000000000000000000000000000")); } @Test public void testPrimitiveChar() { Assertions.assertEquals( - TypeEncoder.encode(new Char('a')), + encode(new Char('a')), ("0000000000000000000000000000000000000000000000000000000000000001" + "6100000000000000000000000000000000000000000000000000000000000000")); Assertions.assertEquals( - TypeEncoder.encode(new Char(' ')), + encode(new Char(' ')), ("0000000000000000000000000000000000000000000000000000000000000001" + "2000000000000000000000000000000000000000000000000000000000000000")); } @@ -1376,45 +1514,45 @@ public void testPrimitiveChar() { @Test public void testPrimitiveInt() { Assertions.assertEquals( - TypeEncoder.encode(new Int(0)), + encode(new Int(0)), ("0000000000000000000000000000000000000000000000000000000000000000")); Assertions.assertEquals( - TypeEncoder.encode(new Int(Integer.MIN_VALUE)), + encode(new Int(Integer.MIN_VALUE)), ("ffffffffffffffffffffffffffffffffffffffffffffffffffffffff80000000")); Assertions.assertEquals( - TypeEncoder.encode(new Int(Integer.MAX_VALUE)), + encode(new Int(Integer.MAX_VALUE)), ("000000000000000000000000000000000000000000000000000000007fffffff")); } @Test public void testPrimitiveShort() { Assertions.assertEquals( - TypeEncoder.encode(new Short((short) 0)), + encode(new Short((short) 0)), ("0000000000000000000000000000000000000000000000000000000000000000")); Assertions.assertEquals( - TypeEncoder.encode(new Short(java.lang.Short.MIN_VALUE)), + encode(new Short(java.lang.Short.MIN_VALUE)), ("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8000")); Assertions.assertEquals( - TypeEncoder.encode(new Short(java.lang.Short.MAX_VALUE)), + encode(new Short(java.lang.Short.MAX_VALUE)), ("0000000000000000000000000000000000000000000000000000000000007fff")); } @Test public void testPrimitiveLong() { Assertions.assertEquals( - TypeEncoder.encode(new Long(0)), + encode(new Long(0)), ("0000000000000000000000000000000000000000000000000000000000000000")); Assertions.assertEquals( - TypeEncoder.encode(new Long(java.lang.Long.MIN_VALUE)), + encode(new Long(java.lang.Long.MIN_VALUE)), ("ffffffffffffffffffffffffffffffffffffffffffffffff8000000000000000")); Assertions.assertEquals( - TypeEncoder.encode(new Long(java.lang.Long.MAX_VALUE)), + encode(new Long(java.lang.Long.MAX_VALUE)), ("0000000000000000000000000000000000000000000000007fffffffffffffff")); } @@ -1422,13 +1560,44 @@ public void testPrimitiveLong() { public void testPrimitiveFloat() { assertThrows( UnsupportedOperationException.class, - () -> TypeEncoder.encode(new org.tron.trident.abi.datatypes.primitive.Float(0))); + () -> encode(new org.tron.trident.abi.datatypes.primitive.Float(0))); } @Test public void testPrimitiveDouble() { assertThrows( UnsupportedOperationException.class, - () -> TypeEncoder.encode(new Double(0))); + () -> encode(new Double(0))); + } + + @Test + public void testStructContainingDynamicBytes() { + String expectedEncoding = + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "00000000000000000000000000000000000000000000000000000000000000a0" + + "0000000000000000000000000000000000000000000000000000000000000007" + + "64796e616d696300000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000005" + + "4279746573000000000000000000000000000000000000000000000000000000"; + assertEquals( + expectedEncoding, + encode(AbiV2TestFixture.addDynamicBytesArrayFunction.getInputParameters().get(0))); + } + + @Test + public void testStaticArrayOfDynamicElements() { + StaticArray2 string2 = new StaticArray2<>( + Utf8String.class, + new Utf8String("hello"), + new Utf8String("world")); + + String encoded = TypeEncoder.encode(string2); + + // In ABI, string[2] is dynamic. Its head should contain offsets. + // Head size = 2 * 32 = 64 bytes (0x40 in hex) + // The first word should be the offset to the first string: 0x00...0040 + assertTrue(encoded.startsWith("0000000000000000000000000000000000000000000000000000000000000040"), + "StaticArray of Utf8String should be encoded with offsets. Found: " + encoded); } } diff --git a/abi/src/test/java/org/tron/trident/abi/UtilsTest.java b/abi/src/test/java/org/tron/trident/abi/UtilsTest.java index 28d1df66..9c6fa4bc 100644 --- a/abi/src/test/java/org/tron/trident/abi/UtilsTest.java +++ b/abi/src/test/java/org/tron/trident/abi/UtilsTest.java @@ -21,19 +21,24 @@ import java.util.List; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.tron.trident.abi.datatypes.Address; import org.tron.trident.abi.datatypes.Bool; 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.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.generated.Int64; import org.tron.trident.abi.datatypes.generated.StaticArray2; +import org.tron.trident.abi.datatypes.generated.StaticArray3; import org.tron.trident.abi.datatypes.generated.Uint256; import org.tron.trident.abi.datatypes.generated.Uint64; +import org.tron.trident.abi.datatypes.reflection.Parameterized; public class UtilsTest { @@ -68,6 +73,21 @@ public void testGetTypeName() { }), ("uint256[]")); } + @Test + public void testGetTypeNameNestedArrayUsesCanonicalAbiName() throws Exception { + assertEquals( + "uint256[2][]", + Utils.getTypeName(new TypeReference>>() { + })); + assertEquals( + "uint256[2][]", + Utils.getTypeName(TypeReference.makeTypeReference("uint256[2][]"))); + assertEquals( + "uint256[2][2]", + Utils.getTypeName(new TypeReference>>() { + })); + } + @Test public void testTypeMap() { final List input = @@ -106,4 +126,221 @@ public void testTypeMapEmpty() { Assertions.assertEquals(Utils.typeMap(new ArrayList<>(), Uint256.class), (new ArrayList())); } + + @Test + public void testValidateTypeReferenceDepthAcceptsNull() { + // Null root is a no-op (defensive); should not throw. + Assertions.assertDoesNotThrow(() -> Utils.validateTypeReferenceDepth(null)); + } + + @Test + public void testValidateTypeReferenceDepthAcceptsRealisticNesting() { + // DynamicArray> — depth 3, well below the limit. + TypeReference ref = new TypeReference>>() {}; + Assertions.assertDoesNotThrow(() -> Utils.validateTypeReferenceDepth(ref)); + } + + @Test + public void testValidateTypeReferenceDepthRejectsExcessiveNesting() { + // Build a chain of innerTypes 15 levels deep — beyond MAX_TYPEREF_DEPTH (10). + TypeReference chain = TypeReference.create(Uint256.class); + for (int i = 0; i < 15; i++) { + final TypeReference child = chain; + chain = new TypeReference(false, Arrays.asList(child)) { }; + } + final TypeReference tooDeep = chain; + UnsupportedOperationException ex = Assertions.assertThrows( + UnsupportedOperationException.class, + () -> Utils.validateTypeReferenceDepth(tooDeep)); + Assertions.assertTrue(ex.getMessage().contains("depth"), + "expected depth-related message, got: " + ex.getMessage()); + } + + @Test + public void testValidateTypeReferenceDepthAcceptsSharedNodes() { + TypeReference shared = TypeReference.create(Uint256.class); + TypeReference left = new TypeReference(false, Arrays.asList(shared)) { }; + TypeReference right = new TypeReference(false, Arrays.asList(shared)) { }; + TypeReference root = new TypeReference(false, Arrays.asList(left, right)) { }; + + Assertions.assertDoesNotThrow(() -> Utils.validateTypeReferenceDepth(root)); + } + + @Test + public void testValidateTypeReferenceDepthRejectsCycle() { + List> rootChildren = new ArrayList<>(); + TypeReference root = new TypeReference(false, rootChildren) { }; + TypeReference child = new TypeReference(false, Arrays.asList(root)) { }; + rootChildren.add(child); + + // A cycle keeps increasing depth along the cyclic path, so it is rejected by + // the depth cap rather than a dedicated cycle check. + UnsupportedOperationException ex = Assertions.assertThrows( + UnsupportedOperationException.class, + () -> Utils.validateTypeReferenceDepth(root)); + Assertions.assertTrue(ex.getMessage().contains("cycle"), + "expected cycle-related message, got: " + ex.getMessage()); + } + + @Test + public void testGetStructTypeWithParameterizedStaticArray() { + // @Parameterized on a generated StaticArrayN field should emit the static + // array suffix (e.g. "uint256[3]"), not collapse to "uint256[]". + String result = Utils.getStructType(TestStructWithParameterizedStaticArray.class); + assertEquals("(address,uint256[3])", result); + } + + @Test + public void testGetStructTypeWithParameterizedDynamicArrayUnchanged() { + // Regression guard: @Parameterized on a DynamicArray field must still + // produce the dynamic-array suffix "[]" — the StaticArray fix must not + // accidentally rewrite this case. + String result = Utils.getStructType(TestStructWithParameterizedDynamicArray.class); + assertEquals("(address,uint256[])", result); + } + + @Test + public void testExtractStaticArraySizeRejectsBareStaticArrayClass() { + // The bare StaticArray base class has no size suffix; using it as a + // field type for @Parameterized must fail loudly rather than silently + // producing a nonsense type string. + IllegalArgumentException ex = Assertions.assertThrows( + IllegalArgumentException.class, + () -> Utils.getTypeReferenceForParameterizedField(StaticArray.class, Uint256.class)); + Assertions.assertTrue(ex.getMessage().contains("StaticArrayN"), + "expected guidance toward StaticArrayN, got: " + ex.getMessage()); + } + + @Test + public void testGetStructTypeRejectsNestedStaticArrayField() { + // The current @Parameterized-based constructor-reflection path represents only + // one element-type level, so nested arrays are explicitly unsupported. It must + // fail loudly instead of emitting an invalid signature like "(staticarray2[2])" + // or silently misreading tail data during decode. + UnsupportedOperationException ex = Assertions.assertThrows( + UnsupportedOperationException.class, + () -> Utils.getStructType(TestStructWithNestedStaticArray.class)); + Assertions.assertTrue(ex.getMessage().contains("Nested arrays"), + "expected nested-array rejection, got: " + ex.getMessage()); + } + + @Test + public void testGetTypeReferenceForParameterizedFieldRejectsNestedDynamicArray() { + // uint256[2][] field shape: DynamicArray field whose @Parameterized element + // is itself an array class. + UnsupportedOperationException ex = Assertions.assertThrows( + UnsupportedOperationException.class, + () -> Utils.getTypeReferenceForParameterizedField( + DynamicArray.class, StaticArray2.class)); + Assertions.assertTrue(ex.getMessage().contains("Nested arrays"), + "expected nested-array rejection, got: " + ex.getMessage()); + } + + @Test + public void testGetTypeReferenceForParameterizedFieldAllowsStructElements() { + // Struct classes extend StaticArray/DynamicArray for encoding purposes but + // are legitimate array element types — the nested-array guard must not + // reject arrays of structs. + Assertions.assertDoesNotThrow( + () -> Utils.getTypeReferenceForParameterizedField( + StaticArray3.class, TestStructWithParameterizedStaticArray.class)); + } + + @Test + public void testGetStructTypeRejectsUnannotatedDynamicArrayField() { + // A DynamicArray field without @Parameterized used to fall through to the + // generic class-name fallback and silently emit the invalid token + // "dynamicarray" into the signature — producing a wrong selector. It must + // fail loudly, naming the struct, the parameter and the missing annotation. + UnsupportedOperationException ex = Assertions.assertThrows( + UnsupportedOperationException.class, + () -> Utils.getStructType(TestStructWithUnannotatedDynamicArray.class)); + Assertions.assertTrue(ex.getMessage().contains("@Parameterized"), + "expected missing-annotation message, got: " + ex.getMessage()); + Assertions.assertTrue( + ex.getMessage().contains(TestStructWithUnannotatedDynamicArray.class.getName()), + "expected struct class name in message, got: " + ex.getMessage()); + Assertions.assertTrue(ex.getMessage().contains("parameter 1"), + "expected parameter index in message, got: " + ex.getMessage()); + } + + @Test + public void testGetStructTypeRejectsUnannotatedStaticArrayField() { + // Same guard for generated StaticArrayN fields, which used to emit + // e.g. "staticarray2" as a signature token. + UnsupportedOperationException ex = Assertions.assertThrows( + UnsupportedOperationException.class, + () -> Utils.getStructType(TestStructWithUnannotatedStaticArray.class)); + Assertions.assertTrue(ex.getMessage().contains("@Parameterized"), + "expected missing-annotation message, got: " + ex.getMessage()); + Assertions.assertTrue(ex.getMessage().contains("StaticArray2"), + "expected field class name in message, got: " + ex.getMessage()); + } + + @Test + public void testGetStructTypeRejectsAnnotatedScalarField() { + // The inverse misuse of the missing-annotation case: @Parameterized on a + // non-array field used to fall into the DynamicArray branch of + // getTypeReferenceForParameterizedField, silently rewriting a "uint256" + // field into "uint256[]" in the signature. + UnsupportedOperationException ex = Assertions.assertThrows( + UnsupportedOperationException.class, + () -> Utils.getStructType(TestStructWithAnnotatedScalarField.class)); + Assertions.assertTrue(ex.getMessage().contains("only applicable to array-typed"), + "expected non-array rejection message, got: " + ex.getMessage()); + Assertions.assertTrue(ex.getMessage().contains("Uint256"), + "expected field class name in message, got: " + ex.getMessage()); + } + + @Test + public void testGetTypeReferenceForParameterizedFieldRejectsNonArrayFieldType() { + UnsupportedOperationException ex = Assertions.assertThrows( + UnsupportedOperationException.class, + () -> Utils.getTypeReferenceForParameterizedField(Uint256.class, Uint256.class)); + Assertions.assertTrue(ex.getMessage().contains("only applicable to array-typed"), + "expected non-array rejection message, got: " + ex.getMessage()); + } +} + +class TestStructWithParameterizedStaticArray extends StaticStruct { + public TestStructWithParameterizedStaticArray( + Address address, @Parameterized(type = Uint256.class) StaticArray3 uint256Array) { + super(address, uint256Array); + } +} + +class TestStructWithParameterizedDynamicArray extends StaticStruct { + public TestStructWithParameterizedDynamicArray( + Address address, @Parameterized(type = Uint256.class) DynamicArray uint256Array) { + super(address, uint256Array); + } +} + +class TestStructWithNestedStaticArray extends StaticStruct { + public TestStructWithNestedStaticArray( + @Parameterized(type = StaticArray2.class) + StaticArray2> matrix) { + super(matrix); + } +} + +class TestStructWithUnannotatedDynamicArray extends StaticStruct { + public TestStructWithUnannotatedDynamicArray( + Address address, DynamicArray uint256Array) { + super(address, uint256Array); + } +} + +class TestStructWithUnannotatedStaticArray extends StaticStruct { + public TestStructWithUnannotatedStaticArray( + Address address, StaticArray2 uint256Array) { + super(address, uint256Array); + } +} + +class TestStructWithAnnotatedScalarField extends StaticStruct { + public TestStructWithAnnotatedScalarField( + Address address, @Parameterized(type = Uint256.class) Uint256 value) { + super(address, value); + } } diff --git a/abi/src/test/java/org/tron/trident/abi/datatypes/AbiTypesTest.java b/abi/src/test/java/org/tron/trident/abi/datatypes/AbiTypesTest.java new file mode 100644 index 00000000..53588f51 --- /dev/null +++ b/abi/src/test/java/org/tron/trident/abi/datatypes/AbiTypesTest.java @@ -0,0 +1,113 @@ +package org.tron.trident.abi.datatypes; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.math.BigInteger; +import java.util.Arrays; +import org.junit.jupiter.api.Test; +import org.tron.trident.abi.AbiV2TestFixture; +import org.tron.trident.abi.datatypes.generated.Uint256; + +public class AbiTypesTest { + + @Test + public void testGetType_rejectsNonTypeClass() { + UnsupportedOperationException ex = + assertThrows( + UnsupportedOperationException.class, + () -> AbiTypes.getType("java.lang.System")); + // sanity: error message names the offending input + assert ex.getMessage().contains("java.lang.System"); + } + + @Test + public void testGetType_rejectsRuntimeClass() { + assertThrows( + UnsupportedOperationException.class, + () -> AbiTypes.getType("java.lang.Runtime")); + } + + @Test + public void testGetType_rejectsNonexistentClass() { + assertThrows( + UnsupportedOperationException.class, + () -> AbiTypes.getType("does.not.exist.Foo")); + } + + @Test + public void testGetType_rejectsEmptyString() { + assertThrows( + UnsupportedOperationException.class, + () -> AbiTypes.getType("")); + } + + @Test + public void testGetType_acceptsBuiltinPrimitive() { + // primitive type names still hit the switch, never the reflective fallback + assertEquals(Address.class, AbiTypes.getType("address")); + assertEquals(Bool.class, AbiTypes.getType("bool")); + assertEquals(Utf8String.class, AbiTypes.getType("string")); + } + + @Test + public void testGetType_acceptsStructSubclassFqcn() { + // AbiV2TestFixture.Foo extends DynamicStruct extends ... extends Type, + // so the reflective fallback must accept it. + Class resolved = AbiTypes.getType(AbiV2TestFixture.Foo.class.getName()); + assertEquals(AbiV2TestFixture.Foo.class, resolved); + } + + @Test + public void testGetTypeAString_trcTokenIsCamelCase() { + assertEquals("trcToken", AbiTypes.getTypeAString(TrcToken.class)); + } + + @Test + public void testTrcTokenInstanceTypeString() { + assertEquals("trcToken", new TrcToken(BigInteger.valueOf(1000016)).getTypeAsString()); + } + + @Test + public void testGetTypeAString_trcTokenRoundTrip() { + assertEquals( + TrcToken.class, AbiTypes.getType(AbiTypes.getTypeAString(TrcToken.class))); + } + + @Test + public void testTrcTokenCompositeTypeStrings() { + StaticStruct struct = + new StaticStruct( + new TrcToken(BigInteger.ONE), new Uint256(BigInteger.valueOf(2))); + assertEquals("(trcToken,uint256)", struct.getTypeAsString()); + + DynamicArray array = + new DynamicArray<>( + TrcToken.class, + Arrays.asList(new TrcToken(BigInteger.ONE), new TrcToken(BigInteger.TEN))); + assertEquals("trcToken[]", array.getTypeAsString()); + + DynamicArray empty = + new DynamicArray<>(TrcToken.class, Arrays.asList()); + assertEquals("trcToken[]", empty.getTypeAsString()); + } + + @Test + public void testGetTypeAString_bareBaseClassesCanonicalized() { + assertEquals("uint256", AbiTypes.getTypeAString(Uint.class)); + assertEquals("int256", AbiTypes.getTypeAString(Int.class)); + assertEquals("ufixed256", AbiTypes.getTypeAString(Ufixed.class)); + assertEquals("fixed256", AbiTypes.getTypeAString(Fixed.class)); + } + + @Test + public void testBareBaseClassCompositeTypeStrings() { + DynamicArray array = + new DynamicArray<>(Uint.class, new Uint(BigInteger.ONE)); + assertEquals("uint256[]", array.getTypeAsString()); + + StaticStruct struct = + new StaticStruct(new Uint(BigInteger.ONE), new Int(BigInteger.TEN)); + assertEquals("(uint256,int256)", struct.getTypeAsString()); + } +} diff --git a/abi/src/test/java/org/tron/trident/abi/datatypes/CustomErrorTest.java b/abi/src/test/java/org/tron/trident/abi/datatypes/CustomErrorTest.java new file mode 100644 index 00000000..9362f1ab --- /dev/null +++ b/abi/src/test/java/org/tron/trident/abi/datatypes/CustomErrorTest.java @@ -0,0 +1,30 @@ +package org.tron.trident.abi.datatypes; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.tron.trident.abi.TypeReference; +import org.tron.trident.abi.datatypes.generated.Uint256; + +public class CustomErrorTest { + @Test + public void testCreation() { + + List> parameters = + Arrays.>asList( + new TypeReference

() {}, new TypeReference() {}); + CustomError event = new CustomError("MyError", parameters); + + assertEquals(event.getName(), "MyError"); + + List> actualParameters = event.getParameters(); + assertEquals(parameters.size(), actualParameters.size(), + "parameter count mismatch"); + for (int i = 0; i < parameters.size(); i++) { + assertEquals(parameters.get(i), actualParameters.get(i)); + } + } + +} diff --git a/abi/src/test/java/org/tron/trident/abi/datatypes/DynamicArrayTest.java b/abi/src/test/java/org/tron/trident/abi/datatypes/DynamicArrayTest.java new file mode 100644 index 00000000..ad755caa --- /dev/null +++ b/abi/src/test/java/org/tron/trident/abi/datatypes/DynamicArrayTest.java @@ -0,0 +1,95 @@ +package org.tron.trident.abi.datatypes; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.IntStream; +import org.junit.jupiter.api.Test; +import org.tron.trident.abi.AbiV2TestFixture; +import org.tron.trident.abi.datatypes.generated.Uint256; +import org.tron.trident.abi.datatypes.generated.Uint8; + +public class DynamicArrayTest { + + @Test + public void testEmptyDynamicArray() { + final DynamicArray
array = + new DynamicArray<>(Address.class, Collections.emptyList()); + + assertEquals(Address.TYPE_NAME + "[]", array.getTypeAsString()); + } + + @Test + public void testDynamicArrayWithDynamicStruct() { + final List list = Collections.singletonList(new DynamicStruct()); + final DynamicArray array = new DynamicArray<>(DynamicStruct.class, list); + + assertEquals("()[]", array.getTypeAsString()); + } + + @Test + public void testEmptyDynamicArrayOfConcreteStruct() { + final DynamicArray array = + new DynamicArray<>(AbiV2TestFixture.Nazzy.class, Collections.emptyList()); + + assertEquals("((string,string)[])[]", array.getTypeAsString()); + } + + @Test + public void testDynamicArrayWithAbiType() { + final DynamicArray array = new DynamicArray<>(Uint.class, arrayOfUints(1)); + + assertEquals("uint256[]", array.getTypeAsString()); + } + + @Test + public void testMultidimensionalDynamicArray() { + DynamicArray array = + new DynamicArray<>( + DynamicArray.class, + Collections.singletonList( + new DynamicArray<>( + DynamicArray.class, + Collections.singletonList( + new DynamicArray<>( + Uint256.class, new ArrayList<>()))))); + assertEquals("uint256[][][]", array.getTypeAsString()); + } + + @Test + public void testEmptyDynamicArrayOfRawArrayTypeThrows() { + // Nested empty arrays cannot recover the inner element type via reflection + // (Java type erasure); componentType is the raw DynamicArray.class, so + // emitting an ABI type string like "dynamicarray[]" would be malformed. + // Must fail loudly, same fail-fast policy as the empty generic struct case. + final DynamicArray empty = + new DynamicArray<>(DynamicArray.class, Collections.emptyList()); + + UnsupportedOperationException ex = assertThrows( + UnsupportedOperationException.class, + empty::getTypeAsString); + assertTrue(ex.getMessage().contains("nested array"), + "expected nested-array guidance, got: " + ex.getMessage()); + } + + @Test + public void testEmptyDynamicArrayOfGenericStructTypeThrows() { + final DynamicArray empty = + new DynamicArray<>(DynamicStruct.class, Collections.emptyList()); + + UnsupportedOperationException ex = assertThrows( + UnsupportedOperationException.class, + empty::getTypeAsString); + assertTrue(ex.getMessage().contains("generic struct"), + "expected generic-struct guidance, got: " + ex.getMessage()); + } + + private Uint[] arrayOfUints(int length) { + return IntStream.rangeClosed(1, length).mapToObj(Uint8::new).toArray(Uint[]::new); + } + +} diff --git a/abi/src/test/java/org/tron/trident/abi/datatypes/StaticArrayTest.java b/abi/src/test/java/org/tron/trident/abi/datatypes/StaticArrayTest.java index 1c2b7e7b..6807b1d9 100644 --- a/abi/src/test/java/org/tron/trident/abi/datatypes/StaticArrayTest.java +++ b/abi/src/test/java/org/tron/trident/abi/datatypes/StaticArrayTest.java @@ -16,8 +16,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; +import java.util.Collections; import java.util.stream.IntStream; import org.junit.jupiter.api.Test; +import org.tron.trident.abi.datatypes.generated.StaticArray0; import org.tron.trident.abi.datatypes.generated.StaticArray3; import org.tron.trident.abi.datatypes.generated.StaticArray32; import org.tron.trident.abi.datatypes.generated.Uint8; @@ -62,6 +64,14 @@ public void throwsIfSizeIsAboveMaxOf32() { } } + @Test + public void testEmptyStaticArray() { + final StaticArray
array = + new StaticArray0<>(Address.class, Collections.emptyList()); + + assertEquals(Address.TYPE_NAME + "[0]", array.getTypeAsString()); + } + private Uint[] arrayOfUints(int length) { return IntStream.rangeClosed(1, length).mapToObj(Uint8::new).toArray(Uint[]::new); } diff --git a/abi/src/test/java/org/tron/trident/abi/datatypes/StaticStructTest.java b/abi/src/test/java/org/tron/trident/abi/datatypes/StaticStructTest.java new file mode 100644 index 00000000..11110651 --- /dev/null +++ b/abi/src/test/java/org/tron/trident/abi/datatypes/StaticStructTest.java @@ -0,0 +1,35 @@ +package org.tron.trident.abi.datatypes; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import org.tron.trident.abi.datatypes.generated.StaticArray4; +import org.tron.trident.abi.datatypes.generated.Uint256; + +public class StaticStructTest { + @Test + public void testStaticStruct() { + Address address1 = Address.DEFAULT; + Address address2 = Address.DEFAULT; + StaticArray4 array4 = + new StaticArray4<>( + Uint256.class, + Uint256.DEFAULT, + Uint256.DEFAULT, + Uint256.DEFAULT, + Uint256.DEFAULT); + StaticStruct struct = new StaticStruct(address1, address2, array4); + + // (address,address,uint256[4]) + String expected = + "(" + + address1.getTypeAsString() + + "," + + address2.getTypeAsString() + + "," + + array4.getTypeAsString() + + ")"; + assertEquals(expected, struct.getTypeAsString()); + } + +} diff --git a/abi/src/test/java/org/tron/trident/abi/datatypes/Utf8StringTest.java b/abi/src/test/java/org/tron/trident/abi/datatypes/Utf8StringTest.java index 1dec0910..53d04400 100644 --- a/abi/src/test/java/org/tron/trident/abi/datatypes/Utf8StringTest.java +++ b/abi/src/test/java/org/tron/trident/abi/datatypes/Utf8StringTest.java @@ -15,7 +15,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; import org.junit.jupiter.api.Test; +import org.tron.trident.abi.TypeEncoder; +import org.tron.trident.abi.datatypes.generated.StaticArray2; public class Utf8StringTest { @@ -24,4 +29,107 @@ public void testToString() { assertEquals(new Utf8String("").toString(), ("")); assertEquals(new Utf8String("string").toString(), ("string")); } + @Test + public void testBytes32PaddedLength() { + String string32Bytes = "12345678901234567890123456789012"; + String string33Bytes = "123456789012345678901234567890123"; + assertEquals(32, new Utf8String("").bytes32PaddedLength()); + assertEquals(64, new Utf8String("string").bytes32PaddedLength()); + assertEquals(64, new Utf8String(string32Bytes).bytes32PaddedLength()); + assertEquals(96, new Utf8String(string33Bytes).bytes32PaddedLength()); + } + + @Test + public void testBytes32PaddedLengthWithNullValue() { + // equals()/hashCode() already tolerate null value; bytes32PaddedLength + // must too, otherwise reflective construction paths can NPE before + // the value ever gets validated. + assertEquals(32, new Utf8String(null).bytes32PaddedLength()); + } + + @Test + public void testUtf8StringArrayChinese() { + // "你好" is 2 Chinese characters. Each is 3 bytes in UTF-8. Total 6 bytes. + Utf8String s1 = new Utf8String("\u4f60\u597d"); + // "世界" is 2 Chinese characters. Each is 3 bytes in UTF-8. Total 6 bytes. + Utf8String s2 = new Utf8String("\u4e16\u754c"); + + DynamicArray array = + new DynamicArray<>(Utf8String.class, Arrays.asList(s1, s2)); + + String encoded = TypeEncoder.encode(array); + + String expectedLength = "0000000000000000000000000000000000000000000000000000000000000002"; + String expectedOffset1 = "0000000000000000000000000000000000000000000000000000000000000040"; + String expectedOffset2 = "0000000000000000000000000000000000000000000000000000000000000080"; + + assertEquals( + expectedLength + expectedOffset1 + expectedOffset2, encoded.substring(0, 64 * 3)); + } + + @Test + public void testUtf8StringLongChinese() { + // 14 characters * 3 bytes = 42 bytes. + String longString = + "\u4f60\u597d\u4e16\u754c\u4f60\u597d\u4e16\u754c\u4f60\u597d\u4e16\u754c\u4f60\u597d"; + Utf8String s1 = new Utf8String(longString); + + DynamicArray array = + new DynamicArray<>(Utf8String.class, Collections.singletonList(s1)); + String encoded = TypeEncoder.encode(array); + + String expectedLength = "0000000000000000000000000000000000000000000000000000000000000001"; + String expectedOffset1 = "0000000000000000000000000000000000000000000000000000000000000020"; + String expectedS1Length = + "000000000000000000000000000000000000000000000000000000000000002a"; + + assertEquals( + expectedLength + expectedOffset1 + expectedS1Length, encoded.substring(0, 64 * 3)); + } + + @Test + public void testUtf8String33Bytes() { + // 10 Chinese characters = 30 bytes. + 3 chars = 33 bytes. + String s33 = "\u4f60\u597d\u4e16\u754c\u4f60\u597d\u4e16\u754c\u4f60\u597d" + "aaa"; + Utf8String string33 = new Utf8String(s33); + assertEquals(33, s33.getBytes(StandardCharsets.UTF_8).length); + + assertEquals(96, string33.bytes32PaddedLength()); + } + + @Test + public void testUtf8StringEmpty() { + Utf8String string = new Utf8String(""); + assertEquals(32, string.bytes32PaddedLength()); + } + + @Test + public void testStaticUtf8StringArray() { + Utf8String s1 = new Utf8String("a"); + Utf8String s2 = new Utf8String("b"); + + StaticArray2 array = new StaticArray2<>(Utf8String.class, s1, s2); + + String encoded = TypeEncoder.encode(array); + + String expectedOffset1 = "0000000000000000000000000000000000000000000000000000000000000040"; + String expectedOffset2 = "0000000000000000000000000000000000000000000000000000000000000080"; + + assertEquals(expectedOffset1 + expectedOffset2, encoded.substring(0, 64 * 2)); + } + + @Test + public void testStaticUtf8StringArrayInStruct() { + Utf8String s1 = new Utf8String("a"); + Utf8String s2 = new Utf8String("b"); + StaticArray2 array = new StaticArray2<>(Utf8String.class, s1, s2); + + DynamicStruct struct = new DynamicStruct(array); + + String encoded = TypeEncoder.encode(struct); + + String expectedOffsetArray = + "0000000000000000000000000000000000000000000000000000000000000020"; + assertEquals(expectedOffsetArray, encoded.substring(0, 64)); + } } diff --git a/abi/src/test/resources/contract-interface-abi2.json.gz b/abi/src/test/resources/contract-interface-abi2.json.gz new file mode 100644 index 00000000..047719ff Binary files /dev/null and b/abi/src/test/resources/contract-interface-abi2.json.gz differ diff --git a/abi/src/test/resources/contract-interface.json.gz b/abi/src/test/resources/contract-interface.json.gz new file mode 100644 index 00000000..e0a8216d Binary files /dev/null and b/abi/src/test/resources/contract-interface.json.gz differ diff --git a/core/src/main/java/org/tron/trident/core/contract/abi/AbiUtils.java b/core/src/main/java/org/tron/trident/core/contract/abi/AbiUtils.java index 3949ee1a..5d92a6a1 100644 --- a/core/src/main/java/org/tron/trident/core/contract/abi/AbiUtils.java +++ b/core/src/main/java/org/tron/trident/core/contract/abi/AbiUtils.java @@ -3,6 +3,7 @@ import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; +import java.util.Locale; import org.tron.trident.proto.Common.SmartContract.ABI; /** @@ -231,7 +232,7 @@ private static ABI.Entry.Param parseParam(JSONObject paramObject) { * UnknownEntryType if the type string doesn't match any known type */ private static ABI.Entry.EntryType getEntryType(String type) { - switch (type.toLowerCase()) { + switch (type.toLowerCase(Locale.ROOT)) { case "constructor": return ABI.Entry.EntryType.Constructor; case "function": @@ -257,7 +258,7 @@ private static ABI.Entry.EntryType getEntryType(String type) { * UnknownMutabilityType if the string doesn't match any known type */ private static ABI.Entry.StateMutabilityType getStateMutability(String stateMutability) { - switch (stateMutability.toLowerCase()) { + switch (stateMutability.toLowerCase(Locale.ROOT)) { case "pure": return ABI.Entry.StateMutabilityType.Pure; case "view": diff --git a/core/src/test/java/org/tron/trident/core/AbiV2ContractTest.java b/core/src/test/java/org/tron/trident/core/AbiV2ContractTest.java new file mode 100644 index 00000000..53559591 --- /dev/null +++ b/core/src/test/java/org/tron/trident/core/AbiV2ContractTest.java @@ -0,0 +1,798 @@ +package org.tron.trident.core; + +import org.junit.jupiter.api.*; +import org.tron.trident.abi.FunctionEncoder; +import org.tron.trident.abi.FunctionReturnDecoder; +import org.tron.trident.abi.TypeEncoder; +import org.tron.trident.abi.TypeReference; +import org.tron.trident.abi.Utils; +import org.tron.trident.abi.datatypes.*; +import org.tron.trident.abi.datatypes.generated.StaticArray2; +import org.tron.trident.abi.datatypes.generated.StaticArray3; +import org.tron.trident.abi.datatypes.generated.Uint256; +import org.tron.trident.abi.datatypes.reflection.Parameterized; +import org.tron.trident.core.utils.ByteArray; +import org.tron.trident.proto.Chain.Transaction; +import org.tron.trident.proto.Response; +import org.tron.trident.proto.Response.TransactionExtention; +import org.tron.trident.utils.Base58Check; +import org.tron.trident.utils.Numeric; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + + +/** + * End-to-end test for ABIv2 and encodePacked features. + * This test deploys the AbiV2TestContract and interacts with it to verify + * complex type encoding/decoding and packed encoding. + */ +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +class AbiV2ContractTest { + // 1. Compile AbiV2TestContract.sol to get its ABI and Bytecode. + // 2. Provide a private key for an account on the Nile testnet with some TRX. + private static final String CONTRACT_ABI = "[\n" + + "\t{\n" + + "\t\t\"inputs\": [\n" + + "\t\t\t{\n" + + "\t\t\t\t\"components\": [\n" + + "\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\"internalType\": \"uint256\",\n" + + "\t\t\t\t\t\t\"name\": \"topLevelId\",\n" + + "\t\t\t\t\t\t\"type\": \"uint256\"\n" + + "\t\t\t\t\t},\n" + + "\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\"components\": [\n" + + "\t\t\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\t\t\"internalType\": \"uint256\",\n" + + "\t\t\t\t\t\t\t\t\"name\": \"staticId\",\n" + + "\t\t\t\t\t\t\t\t\"type\": \"uint256\"\n" + + "\t\t\t\t\t\t\t},\n" + + "\t\t\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\t\t\"internalType\": \"address\",\n" + + "\t\t\t\t\t\t\t\t\"name\": \"staticAddress\",\n" + + "\t\t\t\t\t\t\t\t\"type\": \"address\"\n" + + "\t\t\t\t\t\t\t}\n" + + "\t\t\t\t\t\t],\n" + + "\t\t\t\t\t\t\"internalType\": \"struct AbiV2TestContract.StaticInfo\",\n" + + "\t\t\t\t\t\t\"name\": \"staticPart\",\n" + + "\t\t\t\t\t\t\"type\": \"tuple\"\n" + + "\t\t\t\t\t},\n" + + "\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\"components\": [\n" + + "\t\t\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\t\t\"internalType\": \"string\",\n" + + "\t\t\t\t\t\t\t\t\"name\": \"name\",\n" + + "\t\t\t\t\t\t\t\t\"type\": \"string\"\n" + + "\t\t\t\t\t\t\t},\n" + + "\t\t\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\t\t\"internalType\": \"bytes\",\n" + + "\t\t\t\t\t\t\t\t\"name\": \"data\",\n" + + "\t\t\t\t\t\t\t\t\"type\": \"bytes\"\n" + + "\t\t\t\t\t\t\t},\n" + + "\t\t\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\t\t\"internalType\": \"uint256[]\",\n" + + "\t\t\t\t\t\t\t\t\"name\": \"scores\",\n" + + "\t\t\t\t\t\t\t\t\"type\": \"uint256[]\"\n" + + "\t\t\t\t\t\t\t}\n" + + "\t\t\t\t\t\t],\n" + + "\t\t\t\t\t\t\"internalType\": \"struct AbiV2TestContract.DynamicInfo\",\n" + + "\t\t\t\t\t\t\"name\": \"dynamicPart\",\n" + + "\t\t\t\t\t\t\"type\": \"tuple\"\n" + + "\t\t\t\t\t}\n" + + "\t\t\t\t],\n" + + "\t\t\t\t\"internalType\": \"struct AbiV2TestContract.NestedInfo\",\n" + + "\t\t\t\t\"name\": \"_info\",\n" + + "\t\t\t\t\"type\": \"tuple\"\n" + + "\t\t\t}\n" + + "\t\t],\n" + + "\t\t\"name\": \"setAndGetNestedStruct\",\n" + + "\t\t\"outputs\": [\n" + + "\t\t\t{\n" + + "\t\t\t\t\"components\": [\n" + + "\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\"internalType\": \"uint256\",\n" + + "\t\t\t\t\t\t\"name\": \"topLevelId\",\n" + + "\t\t\t\t\t\t\"type\": \"uint256\"\n" + + "\t\t\t\t\t},\n" + + "\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\"components\": [\n" + + "\t\t\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\t\t\"internalType\": \"uint256\",\n" + + "\t\t\t\t\t\t\t\t\"name\": \"staticId\",\n" + + "\t\t\t\t\t\t\t\t\"type\": \"uint256\"\n" + + "\t\t\t\t\t\t\t},\n" + + "\t\t\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\t\t\"internalType\": \"address\",\n" + + "\t\t\t\t\t\t\t\t\"name\": \"staticAddress\",\n" + + "\t\t\t\t\t\t\t\t\"type\": \"address\"\n" + + "\t\t\t\t\t\t\t}\n" + + "\t\t\t\t\t\t],\n" + + "\t\t\t\t\t\t\"internalType\": \"struct AbiV2TestContract.StaticInfo\",\n" + + "\t\t\t\t\t\t\"name\": \"staticPart\",\n" + + "\t\t\t\t\t\t\"type\": \"tuple\"\n" + + "\t\t\t\t\t},\n" + + "\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\"components\": [\n" + + "\t\t\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\t\t\"internalType\": \"string\",\n" + + "\t\t\t\t\t\t\t\t\"name\": \"name\",\n" + + "\t\t\t\t\t\t\t\t\"type\": \"string\"\n" + + "\t\t\t\t\t\t\t},\n" + + "\t\t\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\t\t\"internalType\": \"bytes\",\n" + + "\t\t\t\t\t\t\t\t\"name\": \"data\",\n" + + "\t\t\t\t\t\t\t\t\"type\": \"bytes\"\n" + + "\t\t\t\t\t\t\t},\n" + + "\t\t\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\t\t\"internalType\": \"uint256[]\",\n" + + "\t\t\t\t\t\t\t\t\"name\": \"scores\",\n" + + "\t\t\t\t\t\t\t\t\"type\": \"uint256[]\"\n" + + "\t\t\t\t\t\t\t}\n" + + "\t\t\t\t\t\t],\n" + + "\t\t\t\t\t\t\"internalType\": \"struct AbiV2TestContract.DynamicInfo\",\n" + + "\t\t\t\t\t\t\"name\": \"dynamicPart\",\n" + + "\t\t\t\t\t\t\"type\": \"tuple\"\n" + + "\t\t\t\t\t}\n" + + "\t\t\t\t],\n" + + "\t\t\t\t\"internalType\": \"struct AbiV2TestContract.NestedInfo\",\n" + + "\t\t\t\t\"name\": \"\",\n" + + "\t\t\t\t\"type\": \"tuple\"\n" + + "\t\t\t}\n" + + "\t\t],\n" + + "\t\t\"stateMutability\": \"nonpayable\",\n" + + "\t\t\"type\": \"function\"\n" + + "\t},\n" + + "\t{\n" + + "\t\t\"inputs\": [],\n" + + "\t\t\"name\": \"lastNestedInfo\",\n" + + "\t\t\"outputs\": [\n" + + "\t\t\t{\n" + + "\t\t\t\t\"internalType\": \"uint256\",\n" + + "\t\t\t\t\"name\": \"topLevelId\",\n" + + "\t\t\t\t\"type\": \"uint256\"\n" + + "\t\t\t},\n" + + "\t\t\t{\n" + + "\t\t\t\t\"components\": [\n" + + "\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\"internalType\": \"uint256\",\n" + + "\t\t\t\t\t\t\"name\": \"staticId\",\n" + + "\t\t\t\t\t\t\"type\": \"uint256\"\n" + + "\t\t\t\t\t},\n" + + "\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\"internalType\": \"address\",\n" + + "\t\t\t\t\t\t\"name\": \"staticAddress\",\n" + + "\t\t\t\t\t\t\"type\": \"address\"\n" + + "\t\t\t\t\t}\n" + + "\t\t\t\t],\n" + + "\t\t\t\t\"internalType\": \"struct AbiV2TestContract.StaticInfo\",\n" + + "\t\t\t\t\"name\": \"staticPart\",\n" + + "\t\t\t\t\"type\": \"tuple\"\n" + + "\t\t\t},\n" + + "\t\t\t{\n" + + "\t\t\t\t\"components\": [\n" + + "\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\"internalType\": \"string\",\n" + + "\t\t\t\t\t\t\"name\": \"name\",\n" + + "\t\t\t\t\t\t\"type\": \"string\"\n" + + "\t\t\t\t\t},\n" + + "\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\"internalType\": \"bytes\",\n" + + "\t\t\t\t\t\t\"name\": \"data\",\n" + + "\t\t\t\t\t\t\"type\": \"bytes\"\n" + + "\t\t\t\t\t},\n" + + "\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\"internalType\": \"uint256[]\",\n" + + "\t\t\t\t\t\t\"name\": \"scores\",\n" + + "\t\t\t\t\t\t\"type\": \"uint256[]\"\n" + + "\t\t\t\t\t}\n" + + "\t\t\t\t],\n" + + "\t\t\t\t\"internalType\": \"struct AbiV2TestContract.DynamicInfo\",\n" + + "\t\t\t\t\"name\": \"dynamicPart\",\n" + + "\t\t\t\t\"type\": \"tuple\"\n" + + "\t\t\t}\n" + + "\t\t],\n" + + "\t\t\"stateMutability\": \"view\",\n" + + "\t\t\"type\": \"function\"\n" + + "\t},\n" + + "\t{\n" + + "\t\t\"inputs\": [\n" + + "\t\t\t{\n" + + "\t\t\t\t\"components\": [\n" + + "\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\"internalType\": \"string\",\n" + + "\t\t\t\t\t\t\"name\": \"name\",\n" + + "\t\t\t\t\t\t\"type\": \"string\"\n" + + "\t\t\t\t\t},\n" + + "\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\"internalType\": \"bytes\",\n" + + "\t\t\t\t\t\t\"name\": \"data\",\n" + + "\t\t\t\t\t\t\"type\": \"bytes\"\n" + + "\t\t\t\t\t},\n" + + "\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\"internalType\": \"uint256[]\",\n" + + "\t\t\t\t\t\t\"name\": \"scores\",\n" + + "\t\t\t\t\t\t\"type\": \"uint256[]\"\n" + + "\t\t\t\t\t}\n" + + "\t\t\t\t],\n" + + "\t\t\t\t\"internalType\": \"struct AbiV2TestContract.DynamicInfo[]\",\n" + + "\t\t\t\t\"name\": \"_infos\",\n" + + "\t\t\t\t\"type\": \"tuple[]\"\n" + + "\t\t\t}\n" + + "\t\t],\n" + + "\t\t\"name\": \"setAndGetDynamicArrayOfStructs\",\n" + + "\t\t\"outputs\": [\n" + + "\t\t\t{\n" + + "\t\t\t\t\"components\": [\n" + + "\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\"internalType\": \"string\",\n" + + "\t\t\t\t\t\t\"name\": \"name\",\n" + + "\t\t\t\t\t\t\"type\": \"string\"\n" + + "\t\t\t\t\t},\n" + + "\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\"internalType\": \"bytes\",\n" + + "\t\t\t\t\t\t\"name\": \"data\",\n" + + "\t\t\t\t\t\t\"type\": \"bytes\"\n" + + "\t\t\t\t\t},\n" + + "\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\"internalType\": \"uint256[]\",\n" + + "\t\t\t\t\t\t\"name\": \"scores\",\n" + + "\t\t\t\t\t\t\"type\": \"uint256[]\"\n" + + "\t\t\t\t\t}\n" + + "\t\t\t\t],\n" + + "\t\t\t\t\"internalType\": \"struct AbiV2TestContract.DynamicInfo[]\",\n" + + "\t\t\t\t\"name\": \"\",\n" + + "\t\t\t\t\"type\": \"tuple[]\"\n" + + "\t\t\t}\n" + + "\t\t],\n" + + "\t\t\"stateMutability\": \"pure\",\n" + + "\t\t\"type\": \"function\"\n" + + "\t},\n" + + "\t{\n" + + "\t\t\"inputs\": [\n" + + "\t\t\t{\n" + + "\t\t\t\t\"components\": [\n" + + "\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\"internalType\": \"string\",\n" + + "\t\t\t\t\t\t\"name\": \"name\",\n" + + "\t\t\t\t\t\t\"type\": \"string\"\n" + + "\t\t\t\t\t},\n" + + "\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\"internalType\": \"bytes\",\n" + + "\t\t\t\t\t\t\"name\": \"data\",\n" + + "\t\t\t\t\t\t\"type\": \"bytes\"\n" + + "\t\t\t\t\t},\n" + + "\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\"internalType\": \"uint256[]\",\n" + + "\t\t\t\t\t\t\"name\": \"scores\",\n" + + "\t\t\t\t\t\t\"type\": \"uint256[]\"\n" + + "\t\t\t\t\t}\n" + + "\t\t\t\t],\n" + + "\t\t\t\t\"internalType\": \"struct AbiV2TestContract.DynamicInfo\",\n" + + "\t\t\t\t\"name\": \"_info\",\n" + + "\t\t\t\t\"type\": \"tuple\"\n" + + "\t\t\t}\n" + + "\t\t],\n" + + "\t\t\"name\": \"setAndGetDynamicStruct\",\n" + + "\t\t\"outputs\": [\n" + + "\t\t\t{\n" + + "\t\t\t\t\"components\": [\n" + + "\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\"internalType\": \"string\",\n" + + "\t\t\t\t\t\t\"name\": \"name\",\n" + + "\t\t\t\t\t\t\"type\": \"string\"\n" + + "\t\t\t\t\t},\n" + + "\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\"internalType\": \"bytes\",\n" + + "\t\t\t\t\t\t\"name\": \"data\",\n" + + "\t\t\t\t\t\t\"type\": \"bytes\"\n" + + "\t\t\t\t\t},\n" + + "\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\"internalType\": \"uint256[]\",\n" + + "\t\t\t\t\t\t\"name\": \"scores\",\n" + + "\t\t\t\t\t\t\"type\": \"uint256[]\"\n" + + "\t\t\t\t\t}\n" + + "\t\t\t\t],\n" + + "\t\t\t\t\"internalType\": \"struct AbiV2TestContract.DynamicInfo\",\n" + + "\t\t\t\t\"name\": \"\",\n" + + "\t\t\t\t\"type\": \"tuple\"\n" + + "\t\t\t}\n" + + "\t\t],\n" + + "\t\t\"stateMutability\": \"pure\",\n" + + "\t\t\"type\": \"function\"\n" + + "\t},\n" + + "\t{\n" + + "\t\t\"inputs\": [\n" + + "\t\t\t{\n" + + "\t\t\t\t\"internalType\": \"uint256[2][3]\",\n" + + "\t\t\t\t\"name\": \"_matrix\",\n" + + "\t\t\t\t\"type\": \"uint256[2][3]\"\n" + + "\t\t\t}\n" + + "\t\t],\n" + + "\t\t\"name\": \"setAndGetMultiDimArray\",\n" + + "\t\t\"outputs\": [\n" + + "\t\t\t{\n" + + "\t\t\t\t\"internalType\": \"uint256[2][3]\",\n" + + "\t\t\t\t\"name\": \"\",\n" + + "\t\t\t\t\"type\": \"uint256[2][3]\"\n" + + "\t\t\t}\n" + + "\t\t],\n" + + "\t\t\"stateMutability\": \"pure\",\n" + + "\t\t\"type\": \"function\"\n" + + "\t},\n" + + "\t{\n" + + "\t\t\"inputs\": [\n" + + "\t\t\t{\n" + + "\t\t\t\t\"components\": [\n" + + "\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\"internalType\": \"uint256\",\n" + + "\t\t\t\t\t\t\"name\": \"staticId\",\n" + + "\t\t\t\t\t\t\"type\": \"uint256\"\n" + + "\t\t\t\t\t},\n" + + "\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\"internalType\": \"address\",\n" + + "\t\t\t\t\t\t\"name\": \"staticAddress\",\n" + + "\t\t\t\t\t\t\"type\": \"address\"\n" + + "\t\t\t\t\t}\n" + + "\t\t\t\t],\n" + + "\t\t\t\t\"internalType\": \"struct AbiV2TestContract.StaticInfo[2]\",\n" + + "\t\t\t\t\"name\": \"_infos\",\n" + + "\t\t\t\t\"type\": \"tuple[2]\"\n" + + "\t\t\t}\n" + + "\t\t],\n" + + "\t\t\"name\": \"setAndGetStaticArrayOfStructs\",\n" + + "\t\t\"outputs\": [\n" + + "\t\t\t{\n" + + "\t\t\t\t\"components\": [\n" + + "\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\"internalType\": \"uint256\",\n" + + "\t\t\t\t\t\t\"name\": \"staticId\",\n" + + "\t\t\t\t\t\t\"type\": \"uint256\"\n" + + "\t\t\t\t\t},\n" + + "\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\"internalType\": \"address\",\n" + + "\t\t\t\t\t\t\"name\": \"staticAddress\",\n" + + "\t\t\t\t\t\t\"type\": \"address\"\n" + + "\t\t\t\t\t}\n" + + "\t\t\t\t],\n" + + "\t\t\t\t\"internalType\": \"struct AbiV2TestContract.StaticInfo[2]\",\n" + + "\t\t\t\t\"name\": \"\",\n" + + "\t\t\t\t\"type\": \"tuple[2]\"\n" + + "\t\t\t}\n" + + "\t\t],\n" + + "\t\t\"stateMutability\": \"pure\",\n" + + "\t\t\"type\": \"function\"\n" + + "\t},\n" + + "\t{\n" + + "\t\t\"inputs\": [\n" + + "\t\t\t{\n" + + "\t\t\t\t\"components\": [\n" + + "\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\"internalType\": \"uint256\",\n" + + "\t\t\t\t\t\t\"name\": \"staticId\",\n" + + "\t\t\t\t\t\t\"type\": \"uint256\"\n" + + "\t\t\t\t\t},\n" + + "\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\"internalType\": \"address\",\n" + + "\t\t\t\t\t\t\"name\": \"staticAddress\",\n" + + "\t\t\t\t\t\t\"type\": \"address\"\n" + + "\t\t\t\t\t}\n" + + "\t\t\t\t],\n" + + "\t\t\t\t\"internalType\": \"struct AbiV2TestContract.StaticInfo\",\n" + + "\t\t\t\t\"name\": \"_info\",\n" + + "\t\t\t\t\"type\": \"tuple\"\n" + + "\t\t\t}\n" + + "\t\t],\n" + + "\t\t\"name\": \"setAndGetStaticStruct\",\n" + + "\t\t\"outputs\": [\n" + + "\t\t\t{\n" + + "\t\t\t\t\"components\": [\n" + + "\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\"internalType\": \"uint256\",\n" + + "\t\t\t\t\t\t\"name\": \"staticId\",\n" + + "\t\t\t\t\t\t\"type\": \"uint256\"\n" + + "\t\t\t\t\t},\n" + + "\t\t\t\t\t{\n" + + "\t\t\t\t\t\t\"internalType\": \"address\",\n" + + "\t\t\t\t\t\t\"name\": \"staticAddress\",\n" + + "\t\t\t\t\t\t\"type\": \"address\"\n" + + "\t\t\t\t\t}\n" + + "\t\t\t\t],\n" + + "\t\t\t\t\"internalType\": \"struct AbiV2TestContract.StaticInfo\",\n" + + "\t\t\t\t\"name\": \"\",\n" + + "\t\t\t\t\"type\": \"tuple\"\n" + + "\t\t\t}\n" + + "\t\t],\n" + + "\t\t\"stateMutability\": \"pure\",\n" + + "\t\t\"type\": \"function\"\n" + + "\t},\n" + + "\t{\n" + + "\t\t\"inputs\": [\n" + + "\t\t\t{\n" + + "\t\t\t\t\"internalType\": \"bytes\",\n" + + "\t\t\t\t\"name\": \"javaPacked\",\n" + + "\t\t\t\t\"type\": \"bytes\"\n" + + "\t\t\t},\n" + + "\t\t\t{\n" + + "\t\t\t\t\"internalType\": \"uint256\",\n" + + "\t\t\t\t\"name\": \"id\",\n" + + "\t\t\t\t\"type\": \"uint256\"\n" + + "\t\t\t},\n" + + "\t\t\t{\n" + + "\t\t\t\t\"internalType\": \"address\",\n" + + "\t\t\t\t\"name\": \"addr\",\n" + + "\t\t\t\t\"type\": \"address\"\n" + + "\t\t\t},\n" + + "\t\t\t{\n" + + "\t\t\t\t\"internalType\": \"string\",\n" + + "\t\t\t\t\"name\": \"name\",\n" + + "\t\t\t\t\"type\": \"string\"\n" + + "\t\t\t}\n" + + "\t\t],\n" + + "\t\t\"name\": \"verifyPackedEncoding\",\n" + + "\t\t\"outputs\": [\n" + + "\t\t\t{\n" + + "\t\t\t\t\"internalType\": \"bool\",\n" + + "\t\t\t\t\"name\": \"\",\n" + + "\t\t\t\t\"type\": \"bool\"\n" + + "\t\t\t}\n" + + "\t\t],\n" + + "\t\t\"stateMutability\": \"pure\",\n" + + "\t\t\"type\": \"function\"\n" + + "\t}\n" + + "]"; + private static final String CONTRACT_BYTECODE = "6080604052348015600e575f5ffd5b50611cbc8061001c5f395ff3fe608060405234801561000f575f5ffd5b5060043610610086575f3560e01c806373b8a93d1161005957806373b8a93d1461013a5780639007edcf1461016a578063b73b16011461019a578063f683b31e146101ca57610086565b806328c60ba01461008a57806330a4842d146100ba5780634fb4bedd146100ea5780635f2f84921461010a575b5f5ffd5b6100a4600480360381019061009f91906109dc565b6101fa565b6040516100b19190610be7565b60405180910390f35b6100d460048036038101906100cf9190610d5c565b61020a565b6040516100e19190610e59565b60405180910390f35b6100f261021a565b60405161010193929190610eae565b60405180910390f35b610124600480360381019061011f9190610eea565b610417565b6040516101319190610f15565b60405180910390f35b610154600480360381019061014f919061108a565b610427565b60405161016191906111ca565b60405180910390f35b610184600480360381019061017f9190611260565b610437565b604051610191919061134f565b60405180910390f35b6101b460048036038101906101af919061144d565b6104f8565b6040516101c1919061154f565b60405180910390f35b6101e460048036038101906101df919061156f565b610502565b6040516101f19190611625565b60405180910390f35b610202610546565b819050919050565b610212610567565b819050919050565b5f805f015490806001016040518060400160405290815f8201548152602001600182015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152505090806003016040518060600160405290815f820180546102ac9061166b565b80601f01602080910402602001604051908101604052809291908181526020018280546102d89061166b565b80156103235780601f106102fa57610100808354040283529160200191610323565b820191905f5260205f20905b81548152906001019060200180831161030657829003601f168201915b5050505050815260200160018201805461033c9061166b565b80601f01602080910402602001604051908101604052809291908181526020018280546103689061166b565b80156103b35780601f1061038a576101008083540402835291602001916103b3565b820191905f5260205f20905b81548152906001019060200180831161039657829003601f168201915b505050505081526020016002820180548060200260200160405190810160405280929190818152602001828054801561040957602002820191905f5260205f20905b8154815260200190600101908083116103f5575b505050505081525050905083565b61041f610594565b819050919050565b61042f6105c2565b819050919050565b61043f6105ef565b815f5f820151815f01556020820151816001015f820151815f01556020820151816001015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050506040820151816003015f820151815f0190816104be919061184c565b5060208201518160010190816104d49190611980565b5060408201518160020190816104ea9190611b2b565b505050905050819050919050565b6060819050919050565b5f5f84848460405160200161051993929190611c4e565b60405160208183030381529060405290508080519060200120868051906020012014915050949350505050565b60405180606001604052806060815260200160608152602001606081525090565b60405180604001604052806002905b61057e610594565b8152602001906001900390816105765790505090565b60405180604001604052805f81526020015f73ffffffffffffffffffffffffffffffffffffffff1681525090565b60405180606001604052806003905b6105d961061b565b8152602001906001900390816105d15790505090565b60405180606001604052805f8152602001610608610594565b8152602001610615610546565b81525090565b6040518060400160405280600290602082028036833780820191505090505090565b5f604051905090565b5f5ffd5b5f5ffd5b5f5ffd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b61069882610652565b810181811067ffffffffffffffff821117156106b7576106b6610662565b5b80604052505050565b5f6106c961063d565b90506106d5828261068f565b919050565b5f5ffd5b5f5ffd5b5f5ffd5b5f67ffffffffffffffff821115610700576106ff610662565b5b61070982610652565b9050602081019050919050565b828183375f83830152505050565b5f610736610731846106e6565b6106c0565b905082815260208101848484011115610752576107516106e2565b5b61075d848285610716565b509392505050565b5f82601f830112610779576107786106de565b5b8135610789848260208601610724565b91505092915050565b5f67ffffffffffffffff8211156107ac576107ab610662565b5b6107b582610652565b9050602081019050919050565b5f6107d46107cf84610792565b6106c0565b9050828152602081018484840111156107f0576107ef6106e2565b5b6107fb848285610716565b509392505050565b5f82601f830112610817576108166106de565b5b81356108278482602086016107c2565b91505092915050565b5f67ffffffffffffffff82111561084a57610849610662565b5b602082029050602081019050919050565b5f5ffd5b5f819050919050565b6108718161085f565b811461087b575f5ffd5b50565b5f8135905061088c81610868565b92915050565b5f6108a461089f84610830565b6106c0565b905080838252602082019050602084028301858111156108c7576108c661085b565b5b835b818110156108f057806108dc888261087e565b8452602084019350506020810190506108c9565b5050509392505050565b5f82601f83011261090e5761090d6106de565b5b813561091e848260208601610892565b91505092915050565b5f6060828403121561093c5761093b61064e565b5b61094660606106c0565b90505f82013567ffffffffffffffff811115610965576109646106da565b5b61097184828501610765565b5f83015250602082013567ffffffffffffffff811115610994576109936106da565b5b6109a084828501610803565b602083015250604082013567ffffffffffffffff8111156109c4576109c36106da565b5b6109d0848285016108fa565b60408301525092915050565b5f602082840312156109f1576109f0610646565b5b5f82013567ffffffffffffffff811115610a0e57610a0d61064a565b5b610a1a84828501610927565b91505092915050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f610a5582610a23565b610a5f8185610a2d565b9350610a6f818560208601610a3d565b610a7881610652565b840191505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f610aa782610a83565b610ab18185610a8d565b9350610ac1818560208601610a3d565b610aca81610652565b840191505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b610b078161085f565b82525050565b5f610b188383610afe565b60208301905092915050565b5f602082019050919050565b5f610b3a82610ad5565b610b448185610adf565b9350610b4f83610aef565b805f5b83811015610b7f578151610b668882610b0d565b9750610b7183610b24565b925050600181019050610b52565b5085935050505092915050565b5f606083015f8301518482035f860152610ba68282610a4b565b91505060208301518482036020860152610bc08282610a9d565b91505060408301518482036040860152610bda8282610b30565b9150508091505092915050565b5f6020820190508181035f830152610bff8184610b8c565b905092915050565b5f67ffffffffffffffff821115610c2157610c20610662565b5b602082029050919050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610c5582610c2c565b9050919050565b610c6581610c4b565b8114610c6f575f5ffd5b50565b5f81359050610c8081610c5c565b92915050565b5f60408284031215610c9b57610c9a61064e565b5b610ca560406106c0565b90505f610cb48482850161087e565b5f830152506020610cc784828501610c72565b60208301525092915050565b5f610ce5610ce084610c07565b6106c0565b90508060408402830185811115610cff57610cfe61085b565b5b835b81811015610d285780610d148882610c86565b845260208401935050604081019050610d01565b5050509392505050565b5f82601f830112610d4657610d456106de565b5b6002610d53848285610cd3565b91505092915050565b5f60808284031215610d7157610d70610646565b5b5f610d7e84828501610d32565b91505092915050565b5f60029050919050565b5f81905092915050565b5f819050919050565b610dad81610c4b565b82525050565b604082015f820151610dc75f850182610afe565b506020820151610dda6020850182610da4565b50505050565b5f610deb8383610db3565b60408301905092915050565b5f602082019050919050565b610e0c81610d87565b610e168184610d91565b9250610e2182610d9b565b805f5b83811015610e51578151610e388782610de0565b9650610e4383610df7565b925050600181019050610e24565b505050505050565b5f608082019050610e6c5f830184610e03565b92915050565b610e7b8161085f565b82525050565b604082015f820151610e955f850182610afe565b506020820151610ea86020850182610da4565b50505050565b5f608082019050610ec15f830186610e72565b610ece6020830185610e81565b8181036060830152610ee08184610b8c565b9050949350505050565b5f60408284031215610eff57610efe610646565b5b5f610f0c84828501610c86565b91505092915050565b5f604082019050610f285f830184610e81565b92915050565b5f67ffffffffffffffff821115610f4857610f47610662565b5b602082029050919050565b5f67ffffffffffffffff821115610f6d57610f6c610662565b5b602082029050919050565b5f610f8a610f8584610f53565b6106c0565b90508060208402830185811115610fa457610fa361085b565b5b835b81811015610fcd5780610fb9888261087e565b845260208401935050602081019050610fa6565b5050509392505050565b5f82601f830112610feb57610fea6106de565b5b6002610ff8848285610f78565b91505092915050565b5f61101361100e84610f2e565b6106c0565b9050806040840283018581111561102d5761102c61085b565b5b835b8181101561105657806110428882610fd7565b84526020840193505060408101905061102f565b5050509392505050565b5f82601f830112611074576110736106de565b5b6003611081848285611001565b91505092915050565b5f60c0828403121561109f5761109e610646565b5b5f6110ac84828501611060565b91505092915050565b5f60039050919050565b5f81905092915050565b5f819050919050565b5f60029050919050565b5f81905092915050565b5f819050919050565b5f602082019050919050565b611104816110d2565b61110e81846110dc565b9250611119826110e6565b805f5b838110156111495781516111308782610b0d565b965061113b836110ef565b92505060018101905061111c565b505050505050565b5f61115c83836110fb565b60408301905092915050565b5f602082019050919050565b61117d816110b5565b61118781846110bf565b9250611192826110c9565b805f5b838110156111c25781516111a98782611151565b96506111b483611168565b925050600181019050611195565b505050505050565b5f60c0820190506111dd5f830184611174565b92915050565b5f608082840312156111f8576111f761064e565b5b61120260606106c0565b90505f6112118482850161087e565b5f83015250602061122484828501610c86565b602083015250606082013567ffffffffffffffff811115611248576112476106da565b5b61125484828501610927565b60408301525092915050565b5f6020828403121561127557611274610646565b5b5f82013567ffffffffffffffff8111156112925761129161064a565b5b61129e848285016111e3565b91505092915050565b5f606083015f8301518482035f8601526112c18282610a4b565b915050602083015184820360208601526112db8282610a9d565b915050604083015184820360408601526112f58282610b30565b9150508091505092915050565b5f608083015f8301516113175f860182610afe565b50602083015161132a6020860182610db3565b506040830151848203606086015261134282826112a7565b9150508091505092915050565b5f6020820190508181035f8301526113678184611302565b905092915050565b5f67ffffffffffffffff82111561138957611388610662565b5b602082029050602081019050919050565b5f6113ac6113a78461136f565b6106c0565b905080838252602082019050602084028301858111156113cf576113ce61085b565b5b835b8181101561141657803567ffffffffffffffff8111156113f4576113f36106de565b5b8086016114018982610927565b855260208501945050506020810190506113d1565b5050509392505050565b5f82601f830112611434576114336106de565b5b813561144484826020860161139a565b91505092915050565b5f6020828403121561146257611461610646565b5b5f82013567ffffffffffffffff81111561147f5761147e61064a565b5b61148b84828501611420565b91505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f6114c883836112a7565b905092915050565b5f602082019050919050565b5f6114e682611494565b6114f0818561149e565b935083602082028501611502856114ae565b805f5b8581101561153d578484038952815161151e85826114bd565b9450611529836114d0565b925060208a01995050600181019050611505565b50829750879550505050505092915050565b5f6020820190508181035f83015261156781846114dc565b905092915050565b5f5f5f5f6080858703121561158757611586610646565b5b5f85013567ffffffffffffffff8111156115a4576115a361064a565b5b6115b087828801610803565b94505060206115c18782880161087e565b93505060406115d287828801610c72565b925050606085013567ffffffffffffffff8111156115f3576115f261064a565b5b6115ff87828801610765565b91505092959194509250565b5f8115159050919050565b61161f8161160b565b82525050565b5f6020820190506116385f830184611616565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f600282049050600182168061168257607f821691505b6020821081036116955761169461163e565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f600883026116f77fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff826116bc565b61170186836116bc565b95508019841693508086168417925050509392505050565b5f819050919050565b5f61173c6117376117328461085f565b611719565b61085f565b9050919050565b5f819050919050565b61175583611722565b61176961176182611743565b8484546116c8565b825550505050565b5f5f905090565b611780611771565b61178b81848461174c565b505050565b5f5b828110156117b1576117a65f828401611778565b600181019050611792565b505050565b601f8211156118045782821115611803576117d08161169b565b6117d9836116ad565b6117e2856116ad565b60208610156117ef575f90505b8083016117fe82840382611790565b505050505b5b505050565b5f82821c905092915050565b5f6118245f1984600802611809565b1980831691505092915050565b5f61183c8383611815565b9150826002028217905092915050565b61185582610a23565b67ffffffffffffffff81111561186e5761186d610662565b5b611878825461166b565b6118838282856117b6565b5f60209050601f8311600181146118b4575f84156118a2578287015190505b6118ac8582611831565b865550611913565b601f1984166118c28661169b565b5f5b828110156118e9578489015182556001820191506020850194506020810190506118c4565b868310156119065784890151611902601f891682611815565b8355505b6001600288020188555050505b505050505050565b5f819050815f5260205f209050919050565b601f82111561197b578282111561197a576119478161191b565b611950836116ad565b611959856116ad565b6020861015611966575f90505b80830161197582840382611790565b505050505b5b505050565b61198982610a83565b67ffffffffffffffff8111156119a2576119a1610662565b5b6119ac825461166b565b6119b782828561192d565b5f60209050601f8311600181146119e8575f84156119d6578287015190505b6119e08582611831565b865550611a47565b601f1984166119f68661191b565b5f5b82811015611a1d578489015182556001820191506020850194506020810190506119f8565b86831015611a3a5784890151611a36601f891682611815565b8355505b6001600288020188555050505b505050505050565b5f81549050919050565b5f8190506001806001038301049050919050565b5f819050815f5260205f209050919050565b5f5b82811015611aa057611a955f828401611778565b600181019050611a81565b505050565b81831015611adc57611ab682611a59565b611abf84611a59565b611ac883611a6d565b818101611ad783850382611a7f565b505050505b505050565b68010000000000000000821115611afb57611afa610662565b5b611b0481611a4f565b828255611b12838284611aa5565b505050565b5f611b22825161085f565b80915050919050565b611b3482610ad5565b67ffffffffffffffff811115611b4d57611b4c610662565b5b611b578183611ae1565b611b6083610aef565b611b6983611a6d565b600183045f5b81811015611ba6575f611b8185611b17565b611b8a81611743565b8092506020870196505050808285015550600181019050611b6f565b50505050505050565b5f819050919050565b611bc9611bc48261085f565b611baf565b82525050565b5f8160601b9050919050565b5f611be582611bcf565b9050919050565b5f611bf682611bdb565b9050919050565b611c0e611c0982610c4b565b611bec565b82525050565b5f81905092915050565b5f611c2882610a23565b611c328185611c14565b9350611c42818560208601610a3d565b80840191505092915050565b5f611c598286611bb8565b602082019150611c698285611bfd565b601482019150611c798284611c1e565b915081905094935050505056fea2646970667358221220d4662cb1ae03e587216abbcef7d1fd771291a1d23ffdb75a265b7fedc2c78daf64736f6c63430008210033"; + //also deployed https://sepolia.arbiscan.io/address/0x51f2799edab0fb17c51bb1878f67d325220c858b#readContract + private String contractAddress = "TVvQP8CM7dkFtjtiomxRVMyrcr4kzsLcsj"; + + // ================= STRUCT DEFINITIONS (mirroring Solidity) ================= + + public static class StaticInfo extends StaticStruct { + public Uint256 staticId; + public Address staticAddress; + + public StaticInfo(Uint256 staticId, Address staticAddress) { + super(staticId, staticAddress); + this.staticId = staticId; + this.staticAddress = staticAddress; + } + public StaticInfo(List values) { + super(values); + this.staticId = (Uint256) values.get(0); + this.staticAddress = (Address) values.get(1); + } + public StaticInfo(Type staticId, Type staticAddress) { + this( + (Uint256) staticId, + (Address) staticAddress + ); + } + } + + public static class DynamicInfo extends DynamicStruct { + public Utf8String name; + public DynamicBytes data; + public DynamicArray scores; + + public DynamicInfo(Utf8String name, DynamicBytes data, + @Parameterized(type = Uint256.class) DynamicArray scores) { + super(name, data, scores); + this.name = name; + this.data = data; + this.scores = scores; + } + public DynamicInfo(List values) { + super(values); + this.name = (Utf8String) values.get(0); + this.data = (DynamicBytes) values.get(1); + this.scores = (DynamicArray) values.get(2); + } + public DynamicInfo(Type name, Type data, Type scores) { + this( + (Utf8String) name, + (DynamicBytes) data, + (DynamicArray) scores + ); + } + } + + public static class NestedInfo extends DynamicStruct{ + public Uint256 topLevelId; + public StaticInfo staticPart; + public DynamicInfo dynamicPart; + + public NestedInfo(Uint256 topLevelId, StaticInfo staticPart,@Parameterized(type = DynamicInfo.class) DynamicInfo dynamicPart) { + super(topLevelId, staticPart, dynamicPart); + this.topLevelId = topLevelId; + this.staticPart = staticPart; + this.dynamicPart = dynamicPart; + } + public NestedInfo(List values) { + super(values); + this.topLevelId = (Uint256) values.get(0); + this.staticPart = (StaticInfo) values.get(1); + this.dynamicPart = (DynamicInfo) values.get(2); + } + public NestedInfo(Type topLevelId, Type staticPart, Type dynamicPart) { + this( + (Uint256) topLevelId, + (StaticInfo) staticPart, + (DynamicInfo) dynamicPart + ); + } + } + + static ApiWrapper client; + static String testAddress; + + @BeforeAll + static void setUp() { + client = ApiWrapper.ofNile(ApiWrapper.generateAddress().toPrivateKey()); + testAddress = client.keyPair.toBase58CheckAddress(); + } + + @AfterAll + static void tearDown() { + if (client != null) { + client.close(); + } + } + + @Test + @Order(0) + @Disabled("has deployed") + public void deployContract() throws Exception { + + // Deploy the contract + Response.TransactionExtention transactionExtention = + client.deployContract("AbiV2TestContract", CONTRACT_ABI, CONTRACT_BYTECODE, null, 1000_000_000L, 100, 10_000_000L, 0, null, 0); + + Transaction signTransaction = client.signTransaction(transactionExtention.getTransaction()); + String txId = client.broadcastTransaction(signTransaction); +// System.out.println("Deploy transaction ID: " + txId); + + // Wait for deployment to be confirmed (adjust sleep time if needed for the network) + + Thread.sleep(5000); + + Response.TransactionInfo transactionInfo = client.getTransactionInfoById(txId); + String contractAddress = Base58Check.bytesToBase58(transactionInfo.getContractAddress().toByteArray()); + + assertNotNull(contractAddress, "Contract deployment failed or was not confirmed in time."); +// System.out.println("Contract deployed to address: " + contractAddress); + } + + + @Test + @Order(1) + @DisplayName("Test encoding and decoding of a static struct") + void testStaticStruct() { + StaticInfo inputStruct = new StaticInfo(new Uint256(101), new Address(testAddress)); + + Function function = new Function("setAndGetStaticStruct", + Collections.singletonList(inputStruct), + Collections.singletonList(new TypeReference() {}) + ); + + Response.TransactionExtention transactionExtention = client.triggerConstantContract(client.keyPair.toBase58CheckAddress(), contractAddress, function); + + assertNotNull(transactionExtention.getConstantResultList()); + assertFalse(transactionExtention.getConstantResultList().isEmpty()); + List result = FunctionReturnDecoder.decode( + ByteArray.toHexString(transactionExtention.getConstantResult(0).toByteArray()), + function.getOutputParameters()); + + assertNotNull(result); + assertFalse(result.isEmpty()); + assertEquals(1, result.size()); + + StaticInfo outputStruct = (StaticInfo) result.get(0); + assertEquals(inputStruct.staticId.getValue(), outputStruct.staticId.getValue()); + assertEquals(inputStruct.staticAddress.getValue(), outputStruct.staticAddress.getValue()); + assertEquals(new BigInteger("101"), outputStruct.staticId.getValue()); + assertEquals(testAddress, outputStruct.staticAddress.getValue()); + } + + @Test + @Order(2) + @DisplayName("Test encoding and decoding of a dynamic struct") + void testDynamicStruct() { + DynamicInfo inputStruct = new DynamicInfo( + new Utf8String("Trident"), + new DynamicBytes("TestData".getBytes()), + new DynamicArray<>(Uint256.class, new Uint256(98), new Uint256(99), new Uint256(100)) + ); + + Function function = new Function("setAndGetDynamicStruct", + Collections.singletonList(inputStruct), + Collections.singletonList(new TypeReference() {}) + ); + + Response.TransactionExtention result = client.triggerConstantContract(client.keyPair.toBase58CheckAddress(), contractAddress, function); + + assertEquals(1, result.getConstantResultCount()); + System.out.println(FunctionEncoder.encode(function)); + System.out.println(ByteArray.toHexString(result.getConstantResult(0).toByteArray())); + + List outputStructs + = FunctionReturnDecoder.decode(ByteArray.toHexString(result.getConstantResult(0).toByteArray()), function.getOutputParameters()); + + DynamicInfo outputStruct = (DynamicInfo)outputStructs.get(0); + + assertEquals(inputStruct.name.getValue(), outputStruct.name.getValue()); + assertArrayEquals(inputStruct.data.getValue(), outputStruct.data.getValue()); + assertEquals(inputStruct.scores.getValue().size(), outputStruct.scores.getValue().size()); + assertEquals(inputStruct.scores.getValue().get(0).getValue(), outputStruct.scores.getValue().get(0).getValue()); + assertEquals(inputStruct.scores.getValue().get(1).getValue(), outputStruct.scores.getValue().get(1).getValue()); + assertEquals(inputStruct.scores.getValue().get(2).getValue(), outputStruct.scores.getValue().get(2).getValue()); + } + + @Test + @Order(3) + @DisplayName("Test encoding and decoding of a nested struct") + @Disabled("add private key to enable this case") + void testNestedStruct() throws Exception { + StaticInfo staticPart = new StaticInfo(new Uint256(202), new Address(testAddress)); + DynamicInfo dynamicPart = new DynamicInfo( + new Utf8String("Nested"), + new DynamicBytes(new byte[]{0x1, 0x2, 0x3}), + new DynamicArray<>(Uint256.class, new Uint256(1)) + ); + NestedInfo inputStruct = new NestedInfo(new Uint256(303), staticPart, dynamicPart); + + Function function = new Function("setAndGetNestedStruct", + Collections.singletonList(inputStruct), + Collections.singletonList(new TypeReference() {}) + ); + + TransactionExtention transactionExtention + = client.triggerContract(client.keyPair.toBase58CheckAddress(), + contractAddress, + FunctionEncoder.encode(function), + 0, + 0, + null, + 300_000_000L); + + Transaction signTransaction = client.signTransaction(transactionExtention.getTransaction()); + + String txId = client.broadcastTransaction(signTransaction); + System.out.println("setAndGetNestedStruct transaction ID: " + txId); + assertNotNull(txId); + + + Function function2 = new Function("lastNestedInfo", + Collections.emptyList(), + Collections.singletonList(new TypeReference() { + }) + ); + Response.TransactionExtention result + = client.triggerConstantContract(client.keyPair.toBase58CheckAddress(), contractAddress, function2); + assertEquals(1, result.getConstantResultCount()); + + String inputRaw = "0000000000000000000000000000000000000000000000000000000000000020" + + ByteArray.toHexString(result.getConstantResult(0).toByteArray()); + + List outputStructs + = FunctionReturnDecoder.decode(inputRaw, Utils.convert(Collections.singletonList(new TypeReference() {}))); + + NestedInfo outputStruct = (NestedInfo)outputStructs.get(0); + + assertEquals(inputStruct.topLevelId.getValue(), outputStruct.topLevelId.getValue()); + assertEquals(staticPart.staticId.getValue(), outputStruct.staticPart.staticId.getValue()); + assertEquals(staticPart.staticAddress.getValue(), outputStruct.staticPart.staticAddress.getValue()); + assertEquals(dynamicPart.name.getValue(), outputStruct.dynamicPart.name.getValue()); + assertArrayEquals(dynamicPart.data.getValue(), outputStruct.dynamicPart.data.getValue()); + assertEquals(dynamicPart.scores.getValue().size(), outputStruct.dynamicPart.scores.getValue().size()); + assertEquals(dynamicPart.scores.getValue().get(0).getValue(), outputStruct.dynamicPart.scores.getValue().get(0).getValue()); + } + + + @Test + @Order(4) + @DisplayName("Test static array of static structs") + void testStaticArrayOfStructs() { + StaticArray inputArray = new StaticArray2<>( + new StaticInfo(new Uint256(1), new Address(testAddress)), + new StaticInfo(new Uint256(2), new Address(testAddress)) + ); + + Function function = new Function("setAndGetStaticArrayOfStructs", + Collections.singletonList(inputArray), + Collections.singletonList(new TypeReference>() {}) + ); + + Response.TransactionExtention transactionExtention = client.triggerConstantContract(client.keyPair.toBase58CheckAddress(), contractAddress, function); + + List outPutStructs + = FunctionReturnDecoder.decode(ByteArray.toHexString(transactionExtention.getConstantResult(0).toByteArray()), function.getOutputParameters()); + + StaticArray2 outputStaticArrayOfStructs = (StaticArray2)outPutStructs.get(0); + + assertEquals(2, outputStaticArrayOfStructs.getValue().size()); + assertEquals(BigInteger.valueOf(1), ((StaticInfo)outputStaticArrayOfStructs.getValue().get(0)).staticId.getValue()); + assertEquals(testAddress, ((StaticInfo)outputStaticArrayOfStructs.getValue().get(0)).staticAddress.getValue()); + assertEquals(BigInteger.valueOf(2), ((StaticInfo)outputStaticArrayOfStructs.getValue().get(1)).staticId.getValue()); + assertEquals(testAddress, ((StaticInfo)outputStaticArrayOfStructs.getValue().get(1)).staticAddress.getValue()); + } + + @Test + @Order(5) + @DisplayName("Test multi-dimensional static array") + @SuppressWarnings("unchecked") + void testMultiDimArray() { + // Represents uint256[2][3] + StaticArray3> inputArray = new StaticArray3<>( + (Class)StaticArray2.class, + new StaticArray2<>(Uint256.class, new Uint256(1), new Uint256(2)), + new StaticArray2<>(Uint256.class, new Uint256(3), new Uint256(4)), + new StaticArray2<>(Uint256.class, new Uint256(5), new Uint256(6)) + ); + + Function function = new Function("setAndGetMultiDimArray", + Collections.singletonList(inputArray), + Collections.singletonList(new TypeReference>>() {}) + ); + + Response.TransactionExtention transactionExtention = client.triggerConstantContract(testAddress, contractAddress, function); + assertNotNull(transactionExtention.getConstantResultList()); + assertFalse(transactionExtention.getConstantResultList().isEmpty()); + assertFalse(transactionExtention.getConstantResultList().get(0).isEmpty()); + + List outPutStructs + = FunctionReturnDecoder.decode(ByteArray.toHexString(transactionExtention.getConstantResult(0).toByteArray()), function.getOutputParameters()); + + StaticArray3> outputStaticArrayOfStructs = (StaticArray3>)outPutStructs.get(0); + + assertEquals(3, outputStaticArrayOfStructs.getValue().size()); + + StaticArray2 outputArray1 = (StaticArray2) outputStaticArrayOfStructs.getValue().get(0); + StaticArray2 outputArray2 = (StaticArray2) outputStaticArrayOfStructs.getValue().get(1); + StaticArray2 outputArray3 = (StaticArray2) outputStaticArrayOfStructs.getValue().get(2); + + assertEquals(2, outputArray1.getValue().size()); + assertEquals(2, outputArray2.getValue().size()); + assertEquals(2, outputArray3.getValue().size()); + assertEquals(1, outputArray1.getValue().get(0).getValue().intValue()); + assertEquals(2, outputArray1.getValue().get(1).getValue().intValue()); + assertEquals(3, outputArray2.getValue().get(0).getValue().intValue()); + assertEquals(4, outputArray2.getValue().get(1).getValue().intValue()); + assertEquals(5, outputArray3.getValue().get(0).getValue().intValue()); + assertEquals(6, outputArray3.getValue().get(1).getValue().intValue()); + } + + @Test + @Order(6) + @DisplayName("Test abi.encodePacked verification") + void testEncodePacked() { + // 1. Define inputs + Uint256 id = new Uint256(12345); + Address addr = new Address(client.keyPair.toBase58CheckAddress()); + Utf8String name = new Utf8String("hello packed"); + + // 2. Generate packed bytes in Java + String javaPackedHex = TypeEncoder.encodePacked(id) + TypeEncoder.encodePacked(addr) + TypeEncoder.encodePacked(name); + byte[] javaPackedBytes = Numeric.hexStringToByteArray(javaPackedHex); + + // 3. Prepare function call to Solidity for verification + Function function = new Function("verifyPackedEncoding", + Arrays.asList(new DynamicBytes(javaPackedBytes), id, addr, name), + Collections.singletonList(new TypeReference() {}) + ); + + // 4. Call the contract and get the boolean result + TransactionExtention transactionExtention = client.triggerConstantContract(client.keyPair.toBase58CheckAddress(), + contractAddress, function); + + List result + = FunctionReturnDecoder.decode(ByteArray.toHexString(transactionExtention.getConstantResult(0).toByteArray()), function.getOutputParameters()); + + Bool isMatch = (Bool) result.get(0); + + assertTrue(isMatch.getValue(), "Java's abi.encodePacked result did NOT match Solidity's."); + } +} diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index e1e7357d..bd4f3561 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -41,11 +41,21 @@ + + + + + + + + + + @@ -61,6 +71,11 @@ + + + + + @@ -76,6 +91,19 @@ + + + + + + + + + + + + + @@ -84,6 +112,22 @@ + + + + + + + + + + + + + + + + diff --git a/utils/src/main/java/org/tron/trident/crypto/Hash.java b/utils/src/main/java/org/tron/trident/crypto/Hash.java index 3f7e9ca6..56b68d88 100644 --- a/utils/src/main/java/org/tron/trident/crypto/Hash.java +++ b/utils/src/main/java/org/tron/trident/crypto/Hash.java @@ -16,6 +16,7 @@ import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.Locale; import org.bouncycastle.crypto.digests.RIPEMD160Digest; import org.bouncycastle.crypto.digests.SHA512Digest; import org.bouncycastle.crypto.macs.HMac; @@ -42,7 +43,7 @@ private Hash() { */ public static byte[] hash(byte[] input, String algorithm) { try { - MessageDigest digest = MessageDigest.getInstance(algorithm.toUpperCase()); + MessageDigest digest = MessageDigest.getInstance(algorithm.toUpperCase(Locale.ROOT)); return digest.digest(input); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("Couldn't find a " + algorithm + " provider", e); diff --git a/utils/src/main/java/org/tron/trident/utils/Strings.java b/utils/src/main/java/org/tron/trident/utils/Strings.java index 0ff55475..f8da6aaf 100644 --- a/utils/src/main/java/org/tron/trident/utils/Strings.java +++ b/utils/src/main/java/org/tron/trident/utils/Strings.java @@ -14,6 +14,7 @@ package org.tron.trident.utils; import java.util.List; +import java.util.Locale; /** * String utility functions. @@ -36,7 +37,7 @@ public static String capitaliseFirstLetter(String string) { if (string == null || string.length() == 0) { return string; } else { - return string.substring(0, 1).toUpperCase() + string.substring(1); + return string.substring(0, 1).toUpperCase(Locale.ROOT) + string.substring(1); } } @@ -44,7 +45,7 @@ public static String lowercaseFirstLetter(String string) { if (string == null || string.length() == 0) { return string; } else { - return string.substring(0, 1).toLowerCase() + string.substring(1); + return string.substring(0, 1).toLowerCase(Locale.ROOT) + string.substring(1); } }