From 64b0e7b68a78e79084440334ccbec34532375d05 Mon Sep 17 00:00:00 2001 From: calmacfadden Date: Tue, 26 Mar 2024 12:02:29 +0000 Subject: [PATCH 01/13] Updated TypeReference object to support nested types to support truly dynamic stucts. Signed-off-by: Antlion12 --- .../main/java/org/web3j/abi/TypeDecoder.java | 293 +++++++++++++----- .../java/org/web3j/abi/TypeReference.java | 23 ++ .../web3j/abi/datatypes/DynamicStruct.java | 2 +- .../web3j/abi/DefaultFunctionEncoderTest.java | 58 +++- 4 files changed, 284 insertions(+), 92 deletions(-) diff --git a/abi/src/main/java/org/web3j/abi/TypeDecoder.java b/abi/src/main/java/org/web3j/abi/TypeDecoder.java index 00975c3e1..ee8a9e708 100644 --- a/abi/src/main/java/org/web3j/abi/TypeDecoder.java +++ b/abi/src/main/java/org/web3j/abi/TypeDecoder.java @@ -50,10 +50,8 @@ import org.web3j.abi.datatypes.primitive.Double; import org.web3j.abi.datatypes.primitive.Float; import org.web3j.utils.Numeric; - import static org.web3j.abi.DefaultFunctionReturnDecoder.getDataOffset; import static org.web3j.abi.TypeReference.makeTypeReference; -import static org.web3j.abi.Utils.findStructConstructor; import static org.web3j.abi.Utils.getSimpleTypeName; import static org.web3j.abi.Utils.staticStructNestedPublicFieldsFlatList; @@ -133,6 +131,10 @@ public 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)); } @@ -382,7 +384,7 @@ private static T decodeStaticStructElement( final BiFunction, String, T> consumer) { try { Class classType = typeReference.getClassType(); - Constructor constructor = findStructConstructor(classType); + Constructor constructor = Utils.findStructConstructor(classType); final int length = constructor.getParameterCount(); List elements = new ArrayList<>(length); @@ -428,9 +430,15 @@ private static T instantiateStruct( final TypeReference typeReference, final List parameters) { try { Class classType = typeReference.getClassType(); - Constructor ctor = findStructConstructor(classType); - ctor.setAccessible(true); - return (T) ctor.newInstance(parameters.toArray()); + Constructor ctor; + if(!classType.isAssignableFrom(DynamicStruct.class)) { + ctor = Utils.findStructConstructor(classType); + ctor.setAccessible(true); + + return (T) ctor.newInstance(parameters.toArray()); + } + else + return (T)new DynamicStruct((List)parameters); } catch (ReflectiveOperationException e) { throw new UnsupportedOperationException( "Constructor cannot accept" + Arrays.toString(parameters.toArray()), e); @@ -452,7 +460,7 @@ 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) -> { @@ -472,100 +480,229 @@ private static T decodeDynamicStructElements( final String input, final int offset, final TypeReference typeReference, - final BiFunction, String, T> consumer) { - try { + final BiFunction, String, T> consumer) throws ClassNotFoundException { + final Class classType = typeReference.getClassType(); - Constructor constructor = findStructConstructor(classType); - final int length = constructor.getParameterCount(); - final Map parameters = new HashMap<>(); - int staticOffset = 0; - final List parameterOffsets = new ArrayList<>(); - for (int i = 0; i < length; ++i) { - final Class declaredField = (Class) constructor.getParameterTypes()[i]; - final T value; - final int beginIndex = offset + staticOffset; - if (isDynamic(declaredField)) { - final int parameterOffset = - decodeDynamicStructDynamicParameterOffset( - input.substring(beginIndex, beginIndex + 64)) - + offset; - parameterOffsets.add(parameterOffset); - staticOffset += 64; + + if(classType.isAssignableFrom(DynamicStruct.class)) { + // We handle it dynamically. User has not constructed a class representing their struct but instead we have a DynamicStruct type reference with inner references + return decodeDynamicStructElementsWithInnerTypeRefs(classType, input, offset, typeReference, consumer); + } + else { + // we handle it the constructor way + return decodeDynamicStructElementsWithCtor(classType, input, offset, typeReference, consumer); + } + + } + + private static T decodeDynamicStructElementsWithInnerTypeRefs( + final Class classType, + final String input, + final int offset, + final TypeReference typeReference, + final BiFunction, String, T> consumer) throws ClassNotFoundException { + + final List> InnerTypes = typeReference.getInnerTypeReferences(); + final int length = InnerTypes.size(); + final Map parameters = new HashMap<>(); + int staticOffset = 0; + final List parameterOffsets = new ArrayList<>(); + + for (int i = 0; i < length; ++i) { + final Class declaredField = (Class) InnerTypes.get(i).getClassType(); + final T value; + final int beginIndex = offset + staticOffset; + if (isDynamic(declaredField)) { + final int parameterOffset = + decodeDynamicStructDynamicParameterOffset( + input.substring(beginIndex, beginIndex + 64)) + + offset; + parameterOffsets.add(parameterOffset); + staticOffset += 64; + } else { + if (StaticStruct.class.isAssignableFrom(declaredField)) { + value = + decodeStaticStruct( + input.substring(beginIndex), + 0, + TypeReference.create(declaredField)); + staticOffset += + staticStructNestedPublicFieldsFlatList((Class) declaredField) + .size() + * MAX_BYTE_LENGTH_FOR_HEX_STRING; } else { - if (StaticStruct.class.isAssignableFrom(declaredField)) { - value = - decodeStaticStruct( - input.substring(beginIndex), - 0, - TypeReference.create(declaredField)); - staticOffset += - staticStructNestedPublicFieldsFlatList((Class) declaredField) - .size() - * MAX_BYTE_LENGTH_FOR_HEX_STRING; - } else { - value = decode(input.substring(beginIndex), 0, declaredField); - staticOffset += value.bytes32PaddedLength() * 2; - } - parameters.put(i, value); + value = decode(input.substring(beginIndex), 0, declaredField); + staticOffset += value.bytes32PaddedLength() * 2; } + parameters.put(i, value); } - int dynamicParametersProcessed = 0; - int dynamicParametersToProcess = - getDynamicStructDynamicParametersCount(constructor.getParameterTypes()); - for (int i = 0; i < length; ++i) { - final Class declaredField = (Class) constructor.getParameterTypes()[i]; - if (isDynamic(declaredField)) { - final boolean isLastParameterInStruct = - dynamicParametersProcessed == (dynamicParametersToProcess - 1); - final int parameterLength = - isLastParameterInStruct - ? input.length() - - 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, - parameterFromAnnotation)); - dynamicParametersProcessed++; - } + } + int dynamicParametersProcessed = 0; + + List> classes = new ArrayList<>(); + for(var type : InnerTypes) { + classes.add(type.getClassType()); + } + + int dynamicParametersToProcess = getDynamicStructDynamicParametersCount(classes.toArray(new Class[0])); + for (int i = 0; i < length; ++i) { + var declaredField = InnerTypes.get(i); + if (isDynamic(declaredField.getClassType())) { + final boolean isLastParameterInStruct = + dynamicParametersProcessed == (dynamicParametersToProcess - 1); + final int parameterLength = + isLastParameterInStruct + ? input.length() + - parameterOffsets.get(dynamicParametersProcessed) + : parameterOffsets.get(dynamicParametersProcessed + 1) + - parameterOffsets.get(dynamicParametersProcessed); + + parameters.put( + i, + decodeDynamicParameterFromStruct( + input, + parameterOffsets.get(dynamicParametersProcessed), + parameterLength, + declaredField, + null)); + dynamicParametersProcessed++; } + } - String typeName = getSimpleTypeName(classType); + String typeName = getSimpleTypeName(classType); + + final List elements = new ArrayList<>(); + for (int i = 0; i < length; ++i) { + elements.add(parameters.get(i)); + } + + return consumer.apply(elements, typeName); - final List elements = new ArrayList<>(); - for (int i = 0; i < length; ++i) { - elements.add(parameters.get(i)); + } + + + private static T decodeDynamicStructElementsWithCtor( + final Class classType, + final String input, + final int offset, + final TypeReference typeReference, + final BiFunction, String, T> consumer) throws ClassNotFoundException { + Constructor constructor = Utils.findStructConstructor(classType); + final int length = constructor.getParameterCount(); + final Map parameters = new HashMap<>(); + int staticOffset = 0; + final List parameterOffsets = new ArrayList<>(); + for (int i = 0; i < length; ++i) { + final Class declaredField = (Class) constructor.getParameterTypes()[i]; + final T value; + final int beginIndex = offset + staticOffset; + if (isDynamic(declaredField)) { + final int parameterOffset = + decodeDynamicStructDynamicParameterOffset( + input.substring(beginIndex, beginIndex + 64)) + + offset; + parameterOffsets.add(parameterOffset); + staticOffset += 64; + } else { + if (StaticStruct.class.isAssignableFrom(declaredField)) { + value = + decodeStaticStruct( + input.substring(beginIndex), + 0, + TypeReference.create(declaredField)); + staticOffset += + staticStructNestedPublicFieldsFlatList((Class) declaredField) + .size() + * MAX_BYTE_LENGTH_FOR_HEX_STRING; + } else { + value = decode(input.substring(beginIndex), 0, declaredField); + staticOffset += value.bytes32PaddedLength() * 2; + } + parameters.put(i, value); } + } + int dynamicParametersProcessed = 0; + int dynamicParametersToProcess = + getDynamicStructDynamicParametersCount(constructor.getParameterTypes()); + for (int i = 0; i < length; ++i) { + final Class declaredField = (Class) constructor.getParameterTypes()[i]; + if (isDynamic(declaredField)) { + final boolean isLastParameterInStruct = + dynamicParametersProcessed == (dynamicParametersToProcess - 1); + final int parameterLength = + isLastParameterInStruct + ? input.length() + - 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, + parameterFromAnnotation)); + dynamicParametersProcessed++; + } + } - return consumer.apply(elements, typeName); - } catch (ClassNotFoundException e) { - throw new UnsupportedOperationException( - "Unable to access parameterized type " - + Utils.getTypeName(typeReference.getType()), - e); + String typeName = getSimpleTypeName(classType); + + final List elements = new ArrayList<>(); + for (int i = 0; i < length; ++i) { + elements.add(parameters.get(i)); } + + return consumer.apply(elements, typeName); + } + @SuppressWarnings("unchecked") private static int getDynamicStructDynamicParametersCount( final Class[] cls) { return (int) Arrays.stream(cls).filter(c -> isDynamic((Class) c)).count(); } + private static T decodeDynamicParameterFromStruct( + final String input, + final int parameterOffset, + final int parameterLength, + final TypeReference declaredField, + final Class parameter) throws ClassNotFoundException { + final String dynamicElementData = + input.substring(parameterOffset, parameterOffset + parameterLength); + + final T value; + if (DynamicStruct.class.isAssignableFrom(declaredField.getClassType())) { + value = decodeDynamicStruct(dynamicElementData, 0, (TypeReference) declaredField); + } else if (DynamicArray.class.isAssignableFrom(declaredField.getClassType())) { + if (parameter == null) { + throw new RuntimeException( + "parameter can not be null, try to use annotation @Parameterized to specify the parameter type"); + } + value = + (T) + decodeDynamicArray( + dynamicElementData, + 0, + Utils.getDynamicArrayTypeReference(parameter)); + } else { + value = decode(dynamicElementData, declaredField); + } + return value; + } + private static T decodeDynamicParameterFromStruct( final String input, final int parameterOffset, final int parameterLength, final Class declaredField, - final Class parameter) { + final Class parameter) throws ClassNotFoundException { final String dynamicElementData = input.substring(parameterOffset, parameterOffset + parameterLength); diff --git a/abi/src/main/java/org/web3j/abi/TypeReference.java b/abi/src/main/java/org/web3j/abi/TypeReference.java index 07d7e1249..c7162fc46 100644 --- a/abi/src/main/java/org/web3j/abi/TypeReference.java +++ b/abi/src/main/java/org/web3j/abi/TypeReference.java @@ -14,10 +14,13 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.web3j.abi.datatypes.AbiTypes; +import org.web3j.abi.datatypes.Array; import org.web3j.abi.datatypes.DynamicArray; import org.web3j.abi.datatypes.StaticArray; @@ -39,10 +42,17 @@ public abstract class TypeReference private final Type type; private final boolean indexed; + private List> innerTypes; + protected TypeReference() { this(false); } + protected TypeReference(List> innerTypeReferences) { + this(false); + innerTypes = innerTypeReferences; + } + protected TypeReference(boolean indexed) { Type superclass = getClass().getGenericSuperclass(); if (superclass instanceof Class) { @@ -63,6 +73,19 @@ TypeReference getSubTypeReference() { return null; } + public List> getInnerTypeReferences() { + return innerTypes; + } + + public List> addInnerTypeReferences(TypeReference typeReference) { + if(innerTypes == null) { + innerTypes = new ArrayList<>(); + } + + innerTypes.add(typeReference); + return innerTypes; + } + public int compareTo(TypeReference o) { // taken from the blog post comments - this results in an error if the // type parameter is left out. diff --git a/abi/src/main/java/org/web3j/abi/datatypes/DynamicStruct.java b/abi/src/main/java/org/web3j/abi/datatypes/DynamicStruct.java index dd59ebcd7..f1029d120 100644 --- a/abi/src/main/java/org/web3j/abi/datatypes/DynamicStruct.java +++ b/abi/src/main/java/org/web3j/abi/datatypes/DynamicStruct.java @@ -18,7 +18,7 @@ public class DynamicStruct extends DynamicArray implements StructType { - private final List> itemTypes = new ArrayList<>(); + public final List> itemTypes = new ArrayList<>(); public DynamicStruct(List values) { this(Type.class, values); diff --git a/abi/src/test/java/org/web3j/abi/DefaultFunctionEncoderTest.java b/abi/src/test/java/org/web3j/abi/DefaultFunctionEncoderTest.java index d5abf6edc..f41661da2 100644 --- a/abi/src/test/java/org/web3j/abi/DefaultFunctionEncoderTest.java +++ b/abi/src/test/java/org/web3j/abi/DefaultFunctionEncoderTest.java @@ -14,22 +14,11 @@ import java.lang.reflect.InvocationTargetException; import java.math.BigInteger; -import java.util.Arrays; -import java.util.Collections; -import java.util.Iterator; +import java.util.*; import org.junit.jupiter.api.Test; -import org.web3j.abi.datatypes.Address; -import org.web3j.abi.datatypes.Bool; -import org.web3j.abi.datatypes.DynamicArray; -import org.web3j.abi.datatypes.DynamicBytes; -import org.web3j.abi.datatypes.DynamicStruct; -import org.web3j.abi.datatypes.Function; -import org.web3j.abi.datatypes.StaticStruct; -import org.web3j.abi.datatypes.Type; -import org.web3j.abi.datatypes.Uint; -import org.web3j.abi.datatypes.Utf8String; +import org.web3j.abi.datatypes.*; import org.web3j.abi.datatypes.generated.Bytes10; import org.web3j.abi.datatypes.generated.Uint256; import org.web3j.abi.datatypes.generated.Uint32; @@ -70,6 +59,49 @@ public void testBuildMethodSignatureWithStructWithArray() { BigInteger.ONE, Collections.emptyList())))); } + @Test + public void testDynamicStructFix() throws ClassNotFoundException { + // Return data from 'testInputAndOutput' function of this contract https://sepolia.etherscan.io/address/0x009C10396226ECFE3E39b3f1AEFa072E37578e30#readContract + String returnedData = "0x000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000000b76616c75656265666f72650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000001320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004313233340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000063078313233340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a76616c7565616674657200000000000000000000000000000000000000000000"; + + List> MyStruct2Types = new ArrayList<>(); + List> MyStructTypes = new ArrayList<>(); + List> MyParamaters = 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(MyStruct2Types){}); + + MyParamaters.add(TypeReference.makeTypeReference("string")); + MyParamaters.add(new TypeReference(MyStructTypes){}); + + MyParamaters.add(TypeReference.makeTypeReference("string")); + + List decodedData = FunctionReturnDecoder.decode(returnedData, Utils.convert(MyParamaters)); + + 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 testBuildMessageSignatureWithComplexTuple() { AbiV2TestFixture.Nazz nazz = From 660c7ab54358879c68cd1151fb6b3521f7cea401 Mon Sep 17 00:00:00 2001 From: Antlion12 Date: Wed, 10 Jul 2024 01:08:34 -0700 Subject: [PATCH 02/13] Support decoding of DynamicArray of DynamicStruct (with corresponding unit test). Change visibility of TypeReference's innerTypes and getSubTypeReference() to protected/public to allow for overriding in anonymous classes. Redo naming of some innerType-specific functions in TypeDecoder to minimize code diffs. Signed-off-by: Antlion12 --- .../main/java/org/web3j/abi/TypeDecoder.java | 301 +++++++++--------- .../java/org/web3j/abi/TypeReference.java | 29 +- abi/src/main/java/org/web3j/abi/Utils.java | 2 +- .../web3j/abi/DefaultFunctionEncoderTest.java | 115 ++++++- 4 files changed, 266 insertions(+), 181 deletions(-) diff --git a/abi/src/main/java/org/web3j/abi/TypeDecoder.java b/abi/src/main/java/org/web3j/abi/TypeDecoder.java index ee8a9e708..f028bb711 100644 --- a/abi/src/main/java/org/web3j/abi/TypeDecoder.java +++ b/abi/src/main/java/org/web3j/abi/TypeDecoder.java @@ -23,6 +23,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.BiFunction; import org.web3j.abi.datatypes.AbiTypes; @@ -50,8 +51,10 @@ import org.web3j.abi.datatypes.primitive.Double; import org.web3j.abi.datatypes.primitive.Float; import org.web3j.utils.Numeric; + import static org.web3j.abi.DefaultFunctionReturnDecoder.getDataOffset; import static org.web3j.abi.TypeReference.makeTypeReference; +import static org.web3j.abi.Utils.findStructConstructor; import static org.web3j.abi.Utils.getSimpleTypeName; import static org.web3j.abi.Utils.staticStructNestedPublicFieldsFlatList; @@ -131,8 +134,9 @@ public 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 T decode(String input, TypeReference type) + throws ClassNotFoundException { + return decode(input, 0, ((TypeReference) type).getClassType()); } public static Address decodeAddress(String input) { @@ -384,7 +388,7 @@ private static T decodeStaticStructElement( final BiFunction, String, T> consumer) { try { Class classType = typeReference.getClassType(); - Constructor constructor = Utils.findStructConstructor(classType); + Constructor constructor = findStructConstructor(classType); final int length = constructor.getParameterCount(); List elements = new ArrayList<>(length); @@ -430,15 +434,13 @@ private static T instantiateStruct( final TypeReference typeReference, final List parameters) { try { Class classType = typeReference.getClassType(); - Constructor ctor; - if(!classType.isAssignableFrom(DynamicStruct.class)) { - ctor = Utils.findStructConstructor(classType); + if (classType.isAssignableFrom(DynamicStruct.class)) { + return (T) new DynamicStruct((List) parameters); + } else { + Constructor ctor = findStructConstructor(classType); ctor.setAccessible(true); - return (T) ctor.newInstance(parameters.toArray()); } - else - return (T)new DynamicStruct((List)parameters); } catch (ReflectiveOperationException e) { throw new UnsupportedOperationException( "Constructor cannot accept" + Arrays.toString(parameters.toArray()), e); @@ -460,7 +462,8 @@ public static T decodeDynamicArray( } public static T decodeDynamicStruct( - String input, int offset, TypeReference typeReference) throws ClassNotFoundException { + String input, int offset, TypeReference typeReference) + throws ClassNotFoundException { BiFunction, String, T> function = (elements, typeName) -> { @@ -472,53 +475,42 @@ public static T decodeDynamicStruct( } }; - return decodeDynamicStructElements(input, offset, typeReference, function); + if (typeReference.getClassType().isAssignableFrom(DynamicStruct.class) + && typeReference.getInnerTypes() != null) { + return decodeDynamicStructElementsFromInnerTypes( + input, offset, typeReference, function); + } else { + return decodeDynamicStructElements(input, offset, typeReference, function); + } } @SuppressWarnings("unchecked") - private static T decodeDynamicStructElements( + private static T decodeDynamicStructElementsFromInnerTypes( final String input, final int offset, final TypeReference typeReference, - final BiFunction, String, T> consumer) throws ClassNotFoundException { - - final Class classType = typeReference.getClassType(); - - if(classType.isAssignableFrom(DynamicStruct.class)) { - // We handle it dynamically. User has not constructed a class representing their struct but instead we have a DynamicStruct type reference with inner references - return decodeDynamicStructElementsWithInnerTypeRefs(classType, input, offset, typeReference, consumer); - } - else { - // we handle it the constructor way - return decodeDynamicStructElementsWithCtor(classType, input, offset, typeReference, consumer); - } - - } - - private static T decodeDynamicStructElementsWithInnerTypeRefs( - final Class classType, - final String input, - final int offset, - final TypeReference typeReference, - final BiFunction, String, T> consumer) throws ClassNotFoundException { - - final List> InnerTypes = typeReference.getInnerTypeReferences(); - final int length = InnerTypes.size(); + final BiFunction, String, T> consumer) + throws ClassNotFoundException { + final Class classType = typeReference.getClassType(); + final List> innerTypes = typeReference.getInnerTypes(); + final int length = innerTypes.size(); final Map parameters = new HashMap<>(); int staticOffset = 0; final List parameterOffsets = new ArrayList<>(); + int dynamicParametersToProcess = 0; for (int i = 0; i < length; ++i) { - final Class declaredField = (Class) InnerTypes.get(i).getClassType(); + final Class declaredField = (Class) innerTypes.get(i).getClassType(); final T value; final int beginIndex = offset + staticOffset; if (isDynamic(declaredField)) { final int parameterOffset = decodeDynamicStructDynamicParameterOffset( - input.substring(beginIndex, beginIndex + 64)) + input.substring(beginIndex, beginIndex + 64)) + offset; parameterOffsets.add(parameterOffset); staticOffset += 64; + dynamicParametersToProcess += 1; } else { if (StaticStruct.class.isAssignableFrom(declaredField)) { value = @@ -528,7 +520,7 @@ private static T decodeDynamicStructElementsWithInnerTypeRefs( TypeReference.create(declaredField)); staticOffset += staticStructNestedPublicFieldsFlatList((Class) declaredField) - .size() + .size() * MAX_BYTE_LENGTH_FOR_HEX_STRING; } else { value = decode(input.substring(beginIndex), 0, declaredField); @@ -538,33 +530,25 @@ private static T decodeDynamicStructElementsWithInnerTypeRefs( } } int dynamicParametersProcessed = 0; - - List> classes = new ArrayList<>(); - for(var type : InnerTypes) { - classes.add(type.getClassType()); - } - - int dynamicParametersToProcess = getDynamicStructDynamicParametersCount(classes.toArray(new Class[0])); for (int i = 0; i < length; ++i) { - var declaredField = InnerTypes.get(i); - if (isDynamic(declaredField.getClassType())) { + final TypeReference parameterTypeReference = (TypeReference) innerTypes.get(i); + final Class declaredField = parameterTypeReference.getClassType(); + if (isDynamic(declaredField)) { final boolean isLastParameterInStruct = dynamicParametersProcessed == (dynamicParametersToProcess - 1); final int parameterLength = isLastParameterInStruct - ? input.length() - - parameterOffsets.get(dynamicParametersProcessed) + ? input.length() - parameterOffsets.get(dynamicParametersProcessed) : parameterOffsets.get(dynamicParametersProcessed + 1) - - parameterOffsets.get(dynamicParametersProcessed); + - parameterOffsets.get(dynamicParametersProcessed); parameters.put( i, - decodeDynamicParameterFromStruct( + decodeDynamicParameterFromStructWithTypeReference( input, parameterOffsets.get(dynamicParametersProcessed), parameterLength, - declaredField, - null)); + parameterTypeReference)); dynamicParametersProcessed++; } } @@ -577,91 +561,95 @@ private static T decodeDynamicStructElementsWithInnerTypeRefs( } return consumer.apply(elements, typeName); - } - - private static T decodeDynamicStructElementsWithCtor( - final Class classType, + @SuppressWarnings("unchecked") + private static T decodeDynamicStructElements( final String input, final int offset, final TypeReference typeReference, - final BiFunction, String, T> consumer) throws ClassNotFoundException { - Constructor constructor = Utils.findStructConstructor(classType); - final int length = constructor.getParameterCount(); - final Map parameters = new HashMap<>(); - int staticOffset = 0; - final List parameterOffsets = new ArrayList<>(); - for (int i = 0; i < length; ++i) { - final Class declaredField = (Class) constructor.getParameterTypes()[i]; - final T value; - final int beginIndex = offset + staticOffset; - if (isDynamic(declaredField)) { - final int parameterOffset = - decodeDynamicStructDynamicParameterOffset( - input.substring(beginIndex, beginIndex + 64)) - + offset; - parameterOffsets.add(parameterOffset); - staticOffset += 64; - } else { - if (StaticStruct.class.isAssignableFrom(declaredField)) { - value = - decodeStaticStruct( - input.substring(beginIndex), - 0, - TypeReference.create(declaredField)); - staticOffset += - staticStructNestedPublicFieldsFlatList((Class) declaredField) - .size() - * MAX_BYTE_LENGTH_FOR_HEX_STRING; + final BiFunction, String, T> consumer) { + try { + final Class classType = typeReference.getClassType(); + Constructor constructor = findStructConstructor(classType); + final int length = constructor.getParameterCount(); + final Map parameters = new HashMap<>(); + int staticOffset = 0; + final List parameterOffsets = new ArrayList<>(); + for (int i = 0; i < length; ++i) { + final Class declaredField = (Class) constructor.getParameterTypes()[i]; + final T value; + final int beginIndex = offset + staticOffset; + if (isDynamic(declaredField)) { + final int parameterOffset = + decodeDynamicStructDynamicParameterOffset( + input.substring(beginIndex, beginIndex + 64)) + + offset; + parameterOffsets.add(parameterOffset); + staticOffset += 64; } else { - value = decode(input.substring(beginIndex), 0, declaredField); - staticOffset += value.bytes32PaddedLength() * 2; + if (StaticStruct.class.isAssignableFrom(declaredField)) { + value = + decodeStaticStruct( + input.substring(beginIndex), + 0, + TypeReference.create(declaredField)); + staticOffset += + staticStructNestedPublicFieldsFlatList((Class) declaredField) + .size() + * MAX_BYTE_LENGTH_FOR_HEX_STRING; + } else { + value = decode(input.substring(beginIndex), 0, declaredField); + staticOffset += value.bytes32PaddedLength() * 2; + } + parameters.put(i, value); } - parameters.put(i, value); } - } - int dynamicParametersProcessed = 0; - int dynamicParametersToProcess = - getDynamicStructDynamicParametersCount(constructor.getParameterTypes()); - for (int i = 0; i < length; ++i) { - final Class declaredField = (Class) constructor.getParameterTypes()[i]; - if (isDynamic(declaredField)) { - final boolean isLastParameterInStruct = - dynamicParametersProcessed == (dynamicParametersToProcess - 1); - final int parameterLength = - isLastParameterInStruct - ? input.length() - - 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, - parameterFromAnnotation)); - dynamicParametersProcessed++; + int dynamicParametersProcessed = 0; + int dynamicParametersToProcess = + getDynamicStructDynamicParametersCount(constructor.getParameterTypes()); + for (int i = 0; i < length; ++i) { + final Class declaredField = (Class) constructor.getParameterTypes()[i]; + if (isDynamic(declaredField)) { + final boolean isLastParameterInStruct = + dynamicParametersProcessed == (dynamicParametersToProcess - 1); + final int parameterLength = + isLastParameterInStruct + ? input.length() + - 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, + parameterFromAnnotation)); + dynamicParametersProcessed++; + } } - } - String typeName = getSimpleTypeName(classType); - - final List elements = new ArrayList<>(); - for (int i = 0; i < length; ++i) { - elements.add(parameters.get(i)); - } + String typeName = getSimpleTypeName(classType); - return consumer.apply(elements, typeName); + final List elements = new ArrayList<>(); + for (int i = 0; i < length; ++i) { + elements.add(parameters.get(i)); + } + return consumer.apply(elements, typeName); + } catch (ClassNotFoundException e) { + throw new UnsupportedOperationException( + "Unable to access parameterized type " + + Utils.getTypeName(typeReference.getType()), + e); + } } - @SuppressWarnings("unchecked") private static int getDynamicStructDynamicParametersCount( final Class[] cls) { @@ -672,15 +660,16 @@ private static T decodeDynamicParameterFromStruct( final String input, final int parameterOffset, final int parameterLength, - final TypeReference declaredField, - final Class parameter) throws ClassNotFoundException { + final Class declaredField, + final Class parameter) + throws ClassNotFoundException { final String dynamicElementData = input.substring(parameterOffset, parameterOffset + parameterLength); final T value; - if (DynamicStruct.class.isAssignableFrom(declaredField.getClassType())) { - value = decodeDynamicStruct(dynamicElementData, 0, (TypeReference) declaredField); - } else if (DynamicArray.class.isAssignableFrom(declaredField.getClassType())) { + 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"); @@ -697,29 +686,21 @@ private static T decodeDynamicParameterFromStruct( return value; } - private static T decodeDynamicParameterFromStruct( + private static T decodeDynamicParameterFromStructWithTypeReference( final String input, final int parameterOffset, final int parameterLength, - final Class declaredField, - final Class parameter) throws ClassNotFoundException { + 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, TypeReference.create(declaredField)); + value = decodeDynamicStruct(dynamicElementData, 0, parameterTypeReference); } 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 = - (T) - decodeDynamicArray( - dynamicElementData, - 0, - Utils.getDynamicArrayTypeReference(parameter)); + value = (T) decodeDynamicArray(dynamicElementData, 0, parameterTypeReference); } else { value = decode(dynamicElementData, declaredField); } @@ -796,11 +777,31 @@ private static T decodeArrayElements( * MAX_BYTE_LENGTH_FOR_HEX_STRING) { T value; if (DynamicStruct.class.isAssignableFrom(cls)) { - value = - TypeDecoder.decodeDynamicStruct( - input, - offset + getDataOffset(input, currOffset, typeReference), - TypeReference.create(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()) {}); + } else { + value = + TypeDecoder.decodeDynamicStruct( + input, + offset + + getDataOffset( + input, currOffset, typeReference), + TypeReference.create(cls)); + } } else { value = TypeDecoder.decodeStaticStruct( @@ -844,7 +845,7 @@ private static T decodeArrayElements( staticLength) { @Override - TypeReference getSubTypeReference() { + public TypeReference getSubTypeReference() { return innerType; } diff --git a/abi/src/main/java/org/web3j/abi/TypeReference.java b/abi/src/main/java/org/web3j/abi/TypeReference.java index c7162fc46..54945e3e2 100644 --- a/abi/src/main/java/org/web3j/abi/TypeReference.java +++ b/abi/src/main/java/org/web3j/abi/TypeReference.java @@ -14,13 +14,11 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; -import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.web3j.abi.datatypes.AbiTypes; -import org.web3j.abi.datatypes.Array; import org.web3j.abi.datatypes.DynamicArray; import org.web3j.abi.datatypes.StaticArray; @@ -42,15 +40,15 @@ public abstract class TypeReference private final Type type; private final boolean indexed; - private List> innerTypes; + protected List> innerTypes; protected TypeReference() { this(false); } - protected TypeReference(List> innerTypeReferences) { - this(false); - innerTypes = innerTypeReferences; + protected TypeReference(boolean indexed, List> innerTypesIn) { + this(indexed); + this.innerTypes = innerTypesIn; } protected TypeReference(boolean indexed) { @@ -69,21 +67,12 @@ 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> getInnerTypeReferences() { - return innerTypes; - } - - public List> addInnerTypeReferences(TypeReference typeReference) { - if(innerTypes == null) { - innerTypes = new ArrayList<>(); - } - - innerTypes.add(typeReference); - return innerTypes; + public List> getInnerTypes() { + return this.innerTypes; } public int compareTo(TypeReference o) { @@ -198,7 +187,7 @@ public static TypeReference makeTypeReference( arrayWrappedType = new TypeReference(indexed) { @Override - TypeReference getSubTypeReference() { + public TypeReference getSubTypeReference() { return baseTr; } @@ -236,7 +225,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/web3j/abi/Utils.java b/abi/src/main/java/org/web3j/abi/Utils.java index 099516ea3..d2fe345cd 100644 --- a/abi/src/main/java/org/web3j/abi/Utils.java +++ b/abi/src/main/java/org/web3j/abi/Utils.java @@ -92,7 +92,7 @@ public static String getStructType(Class type) { public static TypeReference getDynamicArrayTypeReference(Class parameter) { return new TypeReference() { @Override - TypeReference getSubTypeReference() { + public TypeReference getSubTypeReference() { return TypeReference.create(parameter); } }; diff --git a/abi/src/test/java/org/web3j/abi/DefaultFunctionEncoderTest.java b/abi/src/test/java/org/web3j/abi/DefaultFunctionEncoderTest.java index f41661da2..456864d83 100644 --- a/abi/src/test/java/org/web3j/abi/DefaultFunctionEncoderTest.java +++ b/abi/src/test/java/org/web3j/abi/DefaultFunctionEncoderTest.java @@ -61,12 +61,14 @@ public void testBuildMethodSignatureWithStructWithArray() { @Test public void testDynamicStructFix() throws ClassNotFoundException { - // Return data from 'testInputAndOutput' function of this contract https://sepolia.etherscan.io/address/0x009C10396226ECFE3E39b3f1AEFa072E37578e30#readContract - String returnedData = "0x000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000000b76616c75656265666f72650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000001320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004313233340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000063078313233340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a76616c7565616674657200000000000000000000000000000000000000000000"; + // Return data from 'testInputAndOutput' function of this contract + // https://sepolia.etherscan.io/address/0x009C10396226ECFE3E39b3f1AEFa072E37578e30#readContract + String returnedData = + "0x000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000000b76616c75656265666f72650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000001320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004313233340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000063078313233340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a76616c7565616674657200000000000000000000000000000000000000000000"; List> MyStruct2Types = new ArrayList<>(); List> MyStructTypes = new ArrayList<>(); - List> MyParamaters = new ArrayList<>(); + List> MyParameters = new ArrayList<>(); MyStruct2Types.add(TypeReference.makeTypeReference("string")); MyStruct2Types.add(TypeReference.makeTypeReference("string")); @@ -74,14 +76,15 @@ public void testDynamicStructFix() throws ClassNotFoundException { MyStructTypes.add(TypeReference.makeTypeReference("uint256")); MyStructTypes.add(TypeReference.makeTypeReference("string")); MyStructTypes.add(TypeReference.makeTypeReference("string")); - MyStructTypes.add(new TypeReference(MyStruct2Types){}); + MyStructTypes.add(new TypeReference(false, MyStruct2Types) {}); - MyParamaters.add(TypeReference.makeTypeReference("string")); - MyParamaters.add(new TypeReference(MyStructTypes){}); + MyParameters.add(TypeReference.makeTypeReference("string")); + MyParameters.add(new TypeReference(false, MyStructTypes) {}); - MyParamaters.add(TypeReference.makeTypeReference("string")); + MyParameters.add(TypeReference.makeTypeReference("string")); - List decodedData = FunctionReturnDecoder.decode(returnedData, Utils.convert(MyParamaters)); + List decodedData = + FunctionReturnDecoder.decode(returnedData, Utils.convert(MyParameters)); assertEquals(decodedData.get(0).getValue(), "valuebefore"); @@ -96,10 +99,102 @@ public void testDynamicStructFix() throws ClassNotFoundException { assertEquals(innerStructData.get(0).getValue(), "1234"); assertEquals(innerStructData.get(1).getValue(), "0x1234"); - assertEquals(decodedData.get(2).getValue(), "valueafter"); + } - + @Test + public void testArrayOfDynamicStruct() 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 = + "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000568656c6c6f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000015000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000002676d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000002676d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000017000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000002676d000000000000000000000000000000000000000000000000000000000000"; + + 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 From 67c81253f9849b458ada8101af2c9d4089a2e895 Mon Sep 17 00:00:00 2001 From: Antlion12 Date: Tue, 16 Jul 2024 00:57:48 -0700 Subject: [PATCH 03/13] Return extraneous 'else { return ... }' from decodeDynamicStruct(). Signed-off-by: Antlion12 --- abi/src/main/java/org/web3j/abi/TypeDecoder.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/abi/src/main/java/org/web3j/abi/TypeDecoder.java b/abi/src/main/java/org/web3j/abi/TypeDecoder.java index f028bb711..f3237d52f 100644 --- a/abi/src/main/java/org/web3j/abi/TypeDecoder.java +++ b/abi/src/main/java/org/web3j/abi/TypeDecoder.java @@ -470,18 +470,18 @@ public static T decodeDynamicStruct( 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); - } else { - return decodeDynamicStructElements(input, offset, typeReference, function); } + + return decodeDynamicStructElements(input, offset, typeReference, function); } @SuppressWarnings("unchecked") From de8fc626f0249d81a937dd4353c7c47611a0c48c Mon Sep 17 00:00:00 2001 From: Antlion12 Date: Tue, 16 Jul 2024 01:12:23 -0700 Subject: [PATCH 04/13] Run spotless. Signed-off-by: Antlion12 --- .../web3j/codegen/SolidityFunctionWrapperGeneratorTest.java | 5 ++++- .../web3j/protocol/core/methods/response/AbiDefinition.java | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/codegen/src/test/java/org/web3j/codegen/SolidityFunctionWrapperGeneratorTest.java b/codegen/src/test/java/org/web3j/codegen/SolidityFunctionWrapperGeneratorTest.java index ddbefe684..37fda3894 100644 --- a/codegen/src/test/java/org/web3j/codegen/SolidityFunctionWrapperGeneratorTest.java +++ b/codegen/src/test/java/org/web3j/codegen/SolidityFunctionWrapperGeneratorTest.java @@ -246,7 +246,10 @@ public void testArrayOfStructAndStructCompareJavaFile() throws Exception { @Test public void testStaticArrayOfStructsInStructGeneration() throws Exception { testCodeGeneration( - "staticarrayofstructsinstruct", "StaticArrayOfStructsInStruct", JAVA_TYPES_ARG, false); + "staticarrayofstructsinstruct", + "StaticArrayOfStructsInStruct", + JAVA_TYPES_ARG, + false); } @Test diff --git a/core/src/main/java/org/web3j/protocol/core/methods/response/AbiDefinition.java b/core/src/main/java/org/web3j/protocol/core/methods/response/AbiDefinition.java index e13d61295..aeda83955 100644 --- a/core/src/main/java/org/web3j/protocol/core/methods/response/AbiDefinition.java +++ b/core/src/main/java/org/web3j/protocol/core/methods/response/AbiDefinition.java @@ -277,9 +277,9 @@ public void setComponents(final List components) { public String structIdentifier() { return ((internalType == null ? type : internalType.isEmpty() ? type : internalType) - + components.stream() - .map(NamedType::structIdentifier) - .collect(Collectors.joining())); + + components.stream() + .map(NamedType::structIdentifier) + .collect(Collectors.joining())); } public int nestedness() { From 208e99552091ac9c8d44bf0af37622042911b9f5 Mon Sep 17 00:00:00 2001 From: Antlion12 Date: Tue, 16 Jul 2024 08:31:35 -0700 Subject: [PATCH 05/13] Factor out offset-computation and parameter-extraction from decodeDynamicStructElementsFromInnerTypes(). Signed-off-by: Antlion12 --- .../main/java/org/web3j/abi/TypeDecoder.java | 103 ++++++++++++------ 1 file changed, 69 insertions(+), 34 deletions(-) diff --git a/abi/src/main/java/org/web3j/abi/TypeDecoder.java b/abi/src/main/java/org/web3j/abi/TypeDecoder.java index f3237d52f..b30911bcf 100644 --- a/abi/src/main/java/org/web3j/abi/TypeDecoder.java +++ b/abi/src/main/java/org/web3j/abi/TypeDecoder.java @@ -484,33 +484,44 @@ public static T decodeDynamicStruct( return decodeDynamicStructElements(input, offset, typeReference, function); } - @SuppressWarnings("unchecked") - private static T decodeDynamicStructElementsFromInnerTypes( - final String input, - final int offset, - final TypeReference typeReference, - final BiFunction, String, T> consumer) - throws ClassNotFoundException { - final Class classType = typeReference.getClassType(); - final List> innerTypes = typeReference.getInnerTypes(); - final int length = innerTypes.size(); - final Map parameters = new HashMap<>(); - int staticOffset = 0; - final List parameterOffsets = new ArrayList<>(); + 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 = dynamicParametersToProcess; + } + } - int dynamicParametersToProcess = 0; - for (int i = 0; i < length; ++i) { + 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 Class declaredField = (Class) innerTypes.get(i).getClassType(); final T value; - final int beginIndex = offset + staticOffset; + final int beginIndex = offset + tracker.staticOffset; if (isDynamic(declaredField)) { final int parameterOffset = decodeDynamicStructDynamicParameterOffset( input.substring(beginIndex, beginIndex + 64)) + offset; - parameterOffsets.add(parameterOffset); - staticOffset += 64; - dynamicParametersToProcess += 1; + tracker.parameterOffsets.add(parameterOffset); + tracker.staticOffset += 64; + tracker.dynamicParametersToProcess += 1; } else { if (StaticStruct.class.isAssignableFrom(declaredField)) { value = @@ -518,49 +529,73 @@ private static T decodeDynamicStructElementsFromInnerTypes( input.substring(beginIndex), 0, TypeReference.create(declaredField)); - staticOffset += + tracker.staticOffset += staticStructNestedPublicFieldsFlatList((Class) declaredField) .size() * MAX_BYTE_LENGTH_FOR_HEX_STRING; } else { value = decode(input.substring(beginIndex), 0, declaredField); - staticOffset += value.bytes32PaddedLength() * 2; + tracker.staticOffset += value.bytes32PaddedLength() * 2; } - parameters.put(i, value); + 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 < length; ++i) { + for (int i = 0; i < innerTypes.size(); ++i) { final TypeReference parameterTypeReference = (TypeReference) innerTypes.get(i); final Class declaredField = parameterTypeReference.getClassType(); if (isDynamic(declaredField)) { final boolean isLastParameterInStruct = - dynamicParametersProcessed == (dynamicParametersToProcess - 1); + dynamicParametersProcessed == (tracker.dynamicParametersToProcess - 1); final int parameterLength = isLastParameterInStruct - ? input.length() - parameterOffsets.get(dynamicParametersProcessed) - : parameterOffsets.get(dynamicParametersProcessed + 1) - - parameterOffsets.get(dynamicParametersProcessed); + ? input.length() + - tracker.parameterOffsets.get(dynamicParametersProcessed) + : tracker.parameterOffsets.get(dynamicParametersProcessed + 1) + - tracker.parameterOffsets.get(dynamicParametersProcessed); - parameters.put( + tracker.parameters.put( i, decodeDynamicParameterFromStructWithTypeReference( input, - parameterOffsets.get(dynamicParametersProcessed), + tracker.parameterOffsets.get(dynamicParametersProcessed), parameterLength, parameterTypeReference)); dynamicParametersProcessed++; } } - String typeName = getSimpleTypeName(classType); - final List elements = new ArrayList<>(); - for (int i = 0; i < length; ++i) { - elements.add(parameters.get(i)); + for (int i = 0; i < innerTypes.size(); ++i) { + elements.add(tracker.parameters.get(i)); } - return consumer.apply(elements, typeName); + 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") From 4bcacb0ed2b806915a0a561905fd266b49f40353 Mon Sep 17 00:00:00 2001 From: gtebrean <99179176+gtebrean@users.noreply.github.com> Date: Thu, 1 Aug 2024 23:04:51 +0300 Subject: [PATCH 06/13] move test Signed-off-by: gtebrean <99179176+gtebrean@users.noreply.github.com> Signed-off-by: Antlion12 --- .../web3j/abi/DefaultFunctionEncoderTest.java | 43 ------- .../web3j/abi/FunctionReturnDecoderTest.java | 106 ++++++++++++++---- 2 files changed, 86 insertions(+), 63 deletions(-) diff --git a/abi/src/test/java/org/web3j/abi/DefaultFunctionEncoderTest.java b/abi/src/test/java/org/web3j/abi/DefaultFunctionEncoderTest.java index 456864d83..66a0c5296 100644 --- a/abi/src/test/java/org/web3j/abi/DefaultFunctionEncoderTest.java +++ b/abi/src/test/java/org/web3j/abi/DefaultFunctionEncoderTest.java @@ -59,49 +59,6 @@ public void testBuildMethodSignatureWithStructWithArray() { BigInteger.ONE, Collections.emptyList())))); } - @Test - public void testDynamicStructFix() throws ClassNotFoundException { - // Return data from 'testInputAndOutput' function of this contract - // https://sepolia.etherscan.io/address/0x009C10396226ECFE3E39b3f1AEFa072E37578e30#readContract - 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 testArrayOfDynamicStruct() throws ClassNotFoundException { // The full event signature is diff --git a/abi/src/test/java/org/web3j/abi/FunctionReturnDecoderTest.java b/abi/src/test/java/org/web3j/abi/FunctionReturnDecoderTest.java index 9cf79926e..8795d95fd 100644 --- a/abi/src/test/java/org/web3j/abi/FunctionReturnDecoderTest.java +++ b/abi/src/test/java/org/web3j/abi/FunctionReturnDecoderTest.java @@ -22,6 +22,7 @@ import org.web3j.abi.datatypes.DynamicArray; import org.web3j.abi.datatypes.DynamicBytes; +import org.web3j.abi.datatypes.DynamicStruct; import org.web3j.abi.datatypes.Function; import org.web3j.abi.datatypes.StaticArray; import org.web3j.abi.datatypes.Type; @@ -46,7 +47,8 @@ public void testSimpleFunctionDecode() { new Function( "test", Collections.emptyList(), - Collections.singletonList(new TypeReference() {})); + Collections.singletonList(new TypeReference() { + })); assertEquals( FunctionReturnDecoder.decode( @@ -61,7 +63,8 @@ public void testSimpleFunctionStringResultDecode() { new Function( "simple", Arrays.asList(), - Collections.singletonList(new TypeReference() {})); + Collections.singletonList(new TypeReference() { + })); List utf8Strings = FunctionReturnDecoder.decode( @@ -79,7 +82,8 @@ public void testFunctionEmptyStringResultDecode() { new Function( "test", Collections.emptyList(), - Collections.singletonList(new TypeReference() {})); + Collections.singletonList(new TypeReference() { + })); List utf8Strings = FunctionReturnDecoder.decode( @@ -96,7 +100,9 @@ public void testMultipleResultFunctionDecode() { new Function( "test", Collections.emptyList(), - Arrays.asList(new TypeReference() {}, new TypeReference() {})); + Arrays.asList(new TypeReference() { + }, new TypeReference() { + })); assertEquals( FunctionReturnDecoder.decode( @@ -113,10 +119,14 @@ public void testDecodeMultipleStringValues() { "function", Collections.emptyList(), Arrays.asList( - new TypeReference() {}, - new TypeReference() {}, - new TypeReference() {}, - new TypeReference() {})); + new TypeReference() { + }, + new TypeReference() { + }, + new TypeReference() { + }, + new TypeReference() { + })); assertEquals( FunctionReturnDecoder.decode( @@ -144,8 +154,10 @@ public void testDecodeStaticArrayValue() { List> outputParameters = new ArrayList<>(1); outputParameters.add( (TypeReference) - new TypeReference.StaticArrayTypeReference>(2) {}); - outputParameters.add((TypeReference) new TypeReference() {}); + new TypeReference.StaticArrayTypeReference>(2) { + }); + outputParameters.add((TypeReference) new TypeReference() { + }); List decoded = FunctionReturnDecoder.decode( @@ -177,7 +189,8 @@ public void testEmptyResultFunctionDecode() { new Function( "test", Collections.emptyList(), - Collections.singletonList(new TypeReference() {})); + Collections.singletonList(new TypeReference() { + })); assertEquals( FunctionReturnDecoder.decode("0x", function.getOutputParameters()), @@ -190,7 +203,8 @@ public void testDecodeIndexedUint256Value() { String encoded = TypeEncoder.encodeNumeric(value); assertEquals( - FunctionReturnDecoder.decodeIndexedValue(encoded, new TypeReference() {}), + FunctionReturnDecoder.decodeIndexedValue(encoded, new TypeReference() { + }), (value)); } @@ -201,7 +215,8 @@ public void testDecodeIndexedStringValue() { String hash = Hash.sha3(encoded); assertEquals( - FunctionReturnDecoder.decodeIndexedValue(hash, new TypeReference() {}), + FunctionReturnDecoder.decodeIndexedValue(hash, new TypeReference() { + }), (new Bytes32(Numeric.hexStringToByteArray(hash)))); } @@ -211,7 +226,8 @@ public void testDecodeIndexedBytes32Value() { byte[] rawInputBytes = Numeric.hexStringToByteArray(rawInput); assertEquals( - FunctionReturnDecoder.decodeIndexedValue(rawInput, new TypeReference() {}), + FunctionReturnDecoder.decodeIndexedValue(rawInput, new TypeReference() { + }), (new Bytes32(rawInputBytes))); } @@ -221,19 +237,21 @@ public void testDecodeIndexedBytes16Value() { byte[] rawInputBytes = Numeric.hexStringToByteArray(rawInput.substring(0, 34)); assertEquals( - FunctionReturnDecoder.decodeIndexedValue(rawInput, new TypeReference() {}), + FunctionReturnDecoder.decodeIndexedValue(rawInput, new TypeReference() { + }), (new Bytes16(rawInputBytes))); } @Test public void testDecodeIndexedDynamicBytesValue() { - DynamicBytes bytes = new DynamicBytes(new byte[] {1, 2, 3, 4, 5}); + DynamicBytes bytes = new DynamicBytes(new byte[]{1, 2, 3, 4, 5}); String encoded = TypeEncoder.encodeDynamicBytes(bytes); String hash = Hash.sha3(encoded); assertEquals( FunctionReturnDecoder.decodeIndexedValue( - hash, new TypeReference() {}), + hash, new TypeReference() { + }), (new Bytes32(Numeric.hexStringToByteArray(hash)))); } @@ -247,7 +265,8 @@ public void testDecodeIndexedDynamicArrayValue() { assertEquals( FunctionReturnDecoder.decodeIndexedValue( - hash, new TypeReference() {}), + hash, new TypeReference() { + }), (new Bytes32(Numeric.hexStringToByteArray(hash)))); } @@ -1262,8 +1281,10 @@ public void testDecodeTupleOfStaticArrays() { List outputParameters = new ArrayList>(); outputParameters.addAll( Arrays.asList( - new TypeReference>() {}, - new TypeReference>() {})); + new TypeReference>() { + }, + new TypeReference>() { + })); // tuple of (strings string[4]{"", "", "", ""}, ints int[4]{0, 0, 0, 0}) String rawInput = @@ -1316,4 +1337,49 @@ public void testDecodeDynamicStructWithStaticStruct() { new AbiV2TestFixture.Qux( new AbiV2TestFixture.Bar(BigInteger.ONE, BigInteger.TEN), "data"))); } + + @Test + public void testDynamicStructFix() throws ClassNotFoundException { + // Return data from 'testInputAndOutput' function of this contract + // https://sepolia.etherscan.io/address/0x009C10396226ECFE3E39b3f1AEFa072E37578e30#readContract + 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"); + } } From c74ae0263e615fd08b8f1c8bc47b328bc146167c Mon Sep 17 00:00:00 2001 From: gtebrean <99179176+gtebrean@users.noreply.github.com> Date: Thu, 1 Aug 2024 23:07:41 +0300 Subject: [PATCH 07/13] spotless Signed-off-by: gtebrean <99179176+gtebrean@users.noreply.github.com> Signed-off-by: Antlion12 --- .../web3j/abi/FunctionReturnDecoderTest.java | 66 +++++++------------ 1 file changed, 22 insertions(+), 44 deletions(-) diff --git a/abi/src/test/java/org/web3j/abi/FunctionReturnDecoderTest.java b/abi/src/test/java/org/web3j/abi/FunctionReturnDecoderTest.java index 8795d95fd..ac04ae63f 100644 --- a/abi/src/test/java/org/web3j/abi/FunctionReturnDecoderTest.java +++ b/abi/src/test/java/org/web3j/abi/FunctionReturnDecoderTest.java @@ -47,8 +47,7 @@ public void testSimpleFunctionDecode() { new Function( "test", Collections.emptyList(), - Collections.singletonList(new TypeReference() { - })); + Collections.singletonList(new TypeReference() {})); assertEquals( FunctionReturnDecoder.decode( @@ -63,8 +62,7 @@ public void testSimpleFunctionStringResultDecode() { new Function( "simple", Arrays.asList(), - Collections.singletonList(new TypeReference() { - })); + Collections.singletonList(new TypeReference() {})); List utf8Strings = FunctionReturnDecoder.decode( @@ -82,8 +80,7 @@ public void testFunctionEmptyStringResultDecode() { new Function( "test", Collections.emptyList(), - Collections.singletonList(new TypeReference() { - })); + Collections.singletonList(new TypeReference() {})); List utf8Strings = FunctionReturnDecoder.decode( @@ -100,9 +97,7 @@ public void testMultipleResultFunctionDecode() { new Function( "test", Collections.emptyList(), - Arrays.asList(new TypeReference() { - }, new TypeReference() { - })); + Arrays.asList(new TypeReference() {}, new TypeReference() {})); assertEquals( FunctionReturnDecoder.decode( @@ -119,14 +114,10 @@ public void testDecodeMultipleStringValues() { "function", Collections.emptyList(), Arrays.asList( - new TypeReference() { - }, - new TypeReference() { - }, - new TypeReference() { - }, - new TypeReference() { - })); + new TypeReference() {}, + new TypeReference() {}, + new TypeReference() {}, + new TypeReference() {})); assertEquals( FunctionReturnDecoder.decode( @@ -154,10 +145,8 @@ public void testDecodeStaticArrayValue() { List> outputParameters = new ArrayList<>(1); outputParameters.add( (TypeReference) - new TypeReference.StaticArrayTypeReference>(2) { - }); - outputParameters.add((TypeReference) new TypeReference() { - }); + new TypeReference.StaticArrayTypeReference>(2) {}); + outputParameters.add((TypeReference) new TypeReference() {}); List decoded = FunctionReturnDecoder.decode( @@ -189,8 +178,7 @@ public void testEmptyResultFunctionDecode() { new Function( "test", Collections.emptyList(), - Collections.singletonList(new TypeReference() { - })); + Collections.singletonList(new TypeReference() {})); assertEquals( FunctionReturnDecoder.decode("0x", function.getOutputParameters()), @@ -203,8 +191,7 @@ public void testDecodeIndexedUint256Value() { String encoded = TypeEncoder.encodeNumeric(value); assertEquals( - FunctionReturnDecoder.decodeIndexedValue(encoded, new TypeReference() { - }), + FunctionReturnDecoder.decodeIndexedValue(encoded, new TypeReference() {}), (value)); } @@ -215,8 +202,7 @@ public void testDecodeIndexedStringValue() { String hash = Hash.sha3(encoded); assertEquals( - FunctionReturnDecoder.decodeIndexedValue(hash, new TypeReference() { - }), + FunctionReturnDecoder.decodeIndexedValue(hash, new TypeReference() {}), (new Bytes32(Numeric.hexStringToByteArray(hash)))); } @@ -226,8 +212,7 @@ public void testDecodeIndexedBytes32Value() { byte[] rawInputBytes = Numeric.hexStringToByteArray(rawInput); assertEquals( - FunctionReturnDecoder.decodeIndexedValue(rawInput, new TypeReference() { - }), + FunctionReturnDecoder.decodeIndexedValue(rawInput, new TypeReference() {}), (new Bytes32(rawInputBytes))); } @@ -237,21 +222,19 @@ public void testDecodeIndexedBytes16Value() { byte[] rawInputBytes = Numeric.hexStringToByteArray(rawInput.substring(0, 34)); assertEquals( - FunctionReturnDecoder.decodeIndexedValue(rawInput, new TypeReference() { - }), + FunctionReturnDecoder.decodeIndexedValue(rawInput, new TypeReference() {}), (new Bytes16(rawInputBytes))); } @Test public void testDecodeIndexedDynamicBytesValue() { - DynamicBytes bytes = new DynamicBytes(new byte[]{1, 2, 3, 4, 5}); + DynamicBytes bytes = new DynamicBytes(new byte[] {1, 2, 3, 4, 5}); String encoded = TypeEncoder.encodeDynamicBytes(bytes); String hash = Hash.sha3(encoded); assertEquals( FunctionReturnDecoder.decodeIndexedValue( - hash, new TypeReference() { - }), + hash, new TypeReference() {}), (new Bytes32(Numeric.hexStringToByteArray(hash)))); } @@ -265,8 +248,7 @@ public void testDecodeIndexedDynamicArrayValue() { assertEquals( FunctionReturnDecoder.decodeIndexedValue( - hash, new TypeReference() { - }), + hash, new TypeReference() {}), (new Bytes32(Numeric.hexStringToByteArray(hash)))); } @@ -1281,10 +1263,8 @@ public void testDecodeTupleOfStaticArrays() { List outputParameters = new ArrayList>(); outputParameters.addAll( Arrays.asList( - new TypeReference>() { - }, - new TypeReference>() { - })); + new TypeReference>() {}, + new TypeReference>() {})); // tuple of (strings string[4]{"", "", "", ""}, ints int[4]{0, 0, 0, 0}) String rawInput = @@ -1355,12 +1335,10 @@ public void testDynamicStructFix() throws ClassNotFoundException { MyStructTypes.add(TypeReference.makeTypeReference("uint256")); MyStructTypes.add(TypeReference.makeTypeReference("string")); MyStructTypes.add(TypeReference.makeTypeReference("string")); - MyStructTypes.add(new TypeReference(false, MyStruct2Types) { - }); + MyStructTypes.add(new TypeReference(false, MyStruct2Types) {}); MyParameters.add(TypeReference.makeTypeReference("string")); - MyParameters.add(new TypeReference(false, MyStructTypes) { - }); + MyParameters.add(new TypeReference(false, MyStructTypes) {}); MyParameters.add(TypeReference.makeTypeReference("string")); From 3655a78e2dbdd8c0419d0322158e8aa77b7a1886 Mon Sep 17 00:00:00 2001 From: gtebrean <99179176+gtebrean@users.noreply.github.com> Date: Sun, 4 Aug 2024 16:01:11 +0300 Subject: [PATCH 08/13] fix naming Signed-off-by: gtebrean <99179176+gtebrean@users.noreply.github.com> Signed-off-by: Antlion12 --- .../web3j/abi/DefaultFunctionEncoderTest.java | 2 +- .../web3j/abi/FunctionReturnDecoderTest.java | 45 +++++++++++++------ 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/abi/src/test/java/org/web3j/abi/DefaultFunctionEncoderTest.java b/abi/src/test/java/org/web3j/abi/DefaultFunctionEncoderTest.java index 66a0c5296..487d8b8e6 100644 --- a/abi/src/test/java/org/web3j/abi/DefaultFunctionEncoderTest.java +++ b/abi/src/test/java/org/web3j/abi/DefaultFunctionEncoderTest.java @@ -60,7 +60,7 @@ public void testBuildMethodSignatureWithStructWithArray() { } @Test - public void testArrayOfDynamicStruct() throws ClassNotFoundException { + public void testBuildEventOfArrayOfDynamicStruct() throws ClassNotFoundException { // The full event signature is // // Stamp3(uint256 indexed stampId, address indexed caller, bool odd, diff --git a/abi/src/test/java/org/web3j/abi/FunctionReturnDecoderTest.java b/abi/src/test/java/org/web3j/abi/FunctionReturnDecoderTest.java index ac04ae63f..a45d43921 100644 --- a/abi/src/test/java/org/web3j/abi/FunctionReturnDecoderTest.java +++ b/abi/src/test/java/org/web3j/abi/FunctionReturnDecoderTest.java @@ -1319,31 +1319,48 @@ public void testDecodeDynamicStructWithStaticStruct() { } @Test - public void testDynamicStructFix() throws ClassNotFoundException { + public void testDynamicStructWithAdditionalParametersReturn() 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<>(); + List> myStruct2Types = new ArrayList<>(); + List> myStructTypes = new ArrayList<>(); + List> myParameters = new ArrayList<>(); - MyStruct2Types.add(TypeReference.makeTypeReference("string")); - MyStruct2Types.add(TypeReference.makeTypeReference("string")); + 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) {}); + 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")); + myParameters.add(new TypeReference(false, myStructTypes) {}); - MyParameters.add(TypeReference.makeTypeReference("string")); + myParameters.add(TypeReference.makeTypeReference("string")); List decodedData = - FunctionReturnDecoder.decode(returnedData, Utils.convert(MyParameters)); + FunctionReturnDecoder.decode(returnedData, Utils.convert(myParameters)); assertEquals(decodedData.get(0).getValue(), "valuebefore"); From 574ec57567779b87b65e61e64947898c2e028ea0 Mon Sep 17 00:00:00 2001 From: gtebrean <99179176+gtebrean@users.noreply.github.com> Date: Sun, 4 Aug 2024 22:28:19 +0300 Subject: [PATCH 09/13] add DynamicStructOfStaticStruct Signed-off-by: gtebrean <99179176+gtebrean@users.noreply.github.com> Signed-off-by: Antlion12 --- .../web3j/abi/DefaultFunctionEncoderTest.java | 110 ++------------- .../web3j/abi/FunctionReturnDecoderTest.java | 126 +++++++++++++++++- 2 files changed, 138 insertions(+), 98 deletions(-) diff --git a/abi/src/test/java/org/web3j/abi/DefaultFunctionEncoderTest.java b/abi/src/test/java/org/web3j/abi/DefaultFunctionEncoderTest.java index 487d8b8e6..d5abf6edc 100644 --- a/abi/src/test/java/org/web3j/abi/DefaultFunctionEncoderTest.java +++ b/abi/src/test/java/org/web3j/abi/DefaultFunctionEncoderTest.java @@ -14,11 +14,22 @@ import java.lang.reflect.InvocationTargetException; import java.math.BigInteger; -import java.util.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; import org.junit.jupiter.api.Test; -import org.web3j.abi.datatypes.*; +import org.web3j.abi.datatypes.Address; +import org.web3j.abi.datatypes.Bool; +import org.web3j.abi.datatypes.DynamicArray; +import org.web3j.abi.datatypes.DynamicBytes; +import org.web3j.abi.datatypes.DynamicStruct; +import org.web3j.abi.datatypes.Function; +import org.web3j.abi.datatypes.StaticStruct; +import org.web3j.abi.datatypes.Type; +import org.web3j.abi.datatypes.Uint; +import org.web3j.abi.datatypes.Utf8String; import org.web3j.abi.datatypes.generated.Bytes10; import org.web3j.abi.datatypes.generated.Uint256; import org.web3j.abi.datatypes.generated.Uint32; @@ -59,101 +70,6 @@ public void testBuildMethodSignatureWithStructWithArray() { BigInteger.ONE, Collections.emptyList())))); } - @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 = - "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000568656c6c6f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000015000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000002676d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000002676d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000017000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000002676d000000000000000000000000000000000000000000000000000000000000"; - - 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 testBuildMessageSignatureWithComplexTuple() { AbiV2TestFixture.Nazz nazz = diff --git a/abi/src/test/java/org/web3j/abi/FunctionReturnDecoderTest.java b/abi/src/test/java/org/web3j/abi/FunctionReturnDecoderTest.java index a45d43921..29469a77b 100644 --- a/abi/src/test/java/org/web3j/abi/FunctionReturnDecoderTest.java +++ b/abi/src/test/java/org/web3j/abi/FunctionReturnDecoderTest.java @@ -25,6 +25,7 @@ import org.web3j.abi.datatypes.DynamicStruct; import org.web3j.abi.datatypes.Function; import org.web3j.abi.datatypes.StaticArray; +import org.web3j.abi.datatypes.StaticStruct; import org.web3j.abi.datatypes.Type; import org.web3j.abi.datatypes.Uint; import org.web3j.abi.datatypes.Utf8String; @@ -1319,7 +1320,8 @@ public void testDecodeDynamicStructWithStaticStruct() { } @Test - public void testDynamicStructWithAdditionalParametersReturn() throws ClassNotFoundException { + public void testDynamicStructOfDynamicStructWithAdditionalParametersReturn() + throws ClassNotFoundException { // Return data from 'testInputAndOutput' function of this contract // https://sepolia.etherscan.io/address/0x009C10396226ECFE3E39b3f1AEFa072E37578e30#readContract // struct MyStruct { @@ -1377,4 +1379,126 @@ public void testDynamicStructWithAdditionalParametersReturn() throws ClassNotFou assertEquals(decodedData.get(2).getValue(), "valueafter"); } + + @Test + public void testDynamicStructOfStaticStructReturn() throws ClassNotFoundException { + String returnedData = + "0xf535d95e00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000004746573740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000047465737400000000000000000000000000000000000000000000000000000000"; + + 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) {}); + + List decodedData = + FunctionReturnDecoder.decode(returnedData, Utils.convert(myStructTypes)); + + assertEquals(decodedData.get(0).getValue(), BigInteger.valueOf(1)); + assertEquals(decodedData.get(1).getValue(), "test"); + assertEquals(decodedData.get(2).getValue(), "test"); + + List innerStructData = ((StaticStruct) decodedData.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 = + "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000568656c6c6f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000015000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000002676d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000002676d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000017000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000002676d000000000000000000000000000000000000000000000000000000000000"; + + 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"); + } } From d35b8fac1f506e1988e5aeb75c4baa18741d1ec9 Mon Sep 17 00:00:00 2001 From: Antlion12 Date: Sun, 4 Aug 2024 23:48:20 -0700 Subject: [PATCH 10/13] Support building StaticStruct using innerTypes. Add testBuildDynamicArrayOfStaticStruct() and testBuildDynamicStructOfStaticStruct() unit tests that use the innerTypes version. Signed-off-by: Antlion12 --- .../main/java/org/web3j/abi/TypeDecoder.java | 115 ++++++++++++++--- .../web3j/abi/FunctionReturnDecoderTest.java | 117 +++++++++++++++++- 2 files changed, 206 insertions(+), 26 deletions(-) diff --git a/abi/src/main/java/org/web3j/abi/TypeDecoder.java b/abi/src/main/java/org/web3j/abi/TypeDecoder.java index b30911bcf..f2df48514 100644 --- a/abi/src/main/java/org/web3j/abi/TypeDecoder.java +++ b/abi/src/main/java/org/web3j/abi/TypeDecoder.java @@ -377,9 +377,68 @@ public static T decodeStaticStruct( } }; + if (typeReference.getInnerTypes() != null) { + return decodeStaticStructElementFromInnerTypes(input, offset, typeReference, function); + } + return decodeStaticStructElement(input, offset, typeReference, function); } + // Counts the number of nested fields in a StaticStruct with inner types. + private static int numNestedFields(final TypeReference typeReference) { + try { + if (StaticStruct.class.isAssignableFrom(typeReference.getClassType())) { + return typeReference.getInnerTypes().stream() + .map((tr) -> numNestedFields(tr)) + .reduce(0, (a, b) -> a + b); + } + + return 1; + } catch (ClassNotFoundException e) { + throw new UnsupportedOperationException( + "numNestedFields failed for " + Utils.getTypeName(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)) { + final int nestedStructLength = numNestedFields(innerType) * 64; + value = + decodeStaticStruct( + input.substring(currOffset, currOffset + nestedStructLength), + 0, + innerType); + currOffset += nestedStructLength; + } else { + value = decode(input.substring(currOffset, currOffset + 64), 0, declaredField); + currOffset += 64; + } + 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, @@ -436,6 +495,8 @@ private static T instantiateStruct( 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); @@ -511,7 +572,8 @@ ParameterOffsetTracker getDynamicOffsetsAndNonDynamicParameters( final List> innerTypes = typeReference.getInnerTypes(); for (int i = 0; i < innerTypes.size(); ++i) { - final Class declaredField = (Class) innerTypes.get(i).getClassType(); + final TypeReference innerType = (TypeReference) innerTypes.get(i); + final Class declaredField = innerType.getClassType(); final T value; final int beginIndex = offset + tracker.staticOffset; if (isDynamic(declaredField)) { @@ -524,15 +586,8 @@ ParameterOffsetTracker getDynamicOffsetsAndNonDynamicParameters( tracker.dynamicParametersToProcess += 1; } else { if (StaticStruct.class.isAssignableFrom(declaredField)) { - value = - decodeStaticStruct( - input.substring(beginIndex), - 0, - TypeReference.create(declaredField)); - tracker.staticOffset += - staticStructNestedPublicFieldsFlatList((Class) declaredField) - .size() - * MAX_BYTE_LENGTH_FOR_HEX_STRING; + value = decodeStaticStruct(input.substring(beginIndex), 0, innerType); + tracker.staticOffset += numNestedFields(innerType) * 64; } else { value = decode(input.substring(beginIndex), 0, declaredField); tracker.staticOffset += value.bytes32PaddedLength() * 2; @@ -749,7 +804,8 @@ private static int decodeDynamicStructDynamicParameterOffset(final String input) 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); } static BigInteger asBigInteger(Object arg) { @@ -804,12 +860,8 @@ private static T decodeArrayElements( Class cls = Utils.getParameterizedTypeFromArray(typeReference); List elements = new ArrayList<>(length); if (StructType.class.isAssignableFrom(cls)) { - for (int i = 0, currOffset = offset; - i < length; - i++, - currOffset += - getSingleElementLength(input, currOffset, cls) - * MAX_BYTE_LENGTH_FOR_HEX_STRING) { + int currOffset = offset; + for (int i = 0; i < length; i++) { T value; if (DynamicStruct.class.isAssignableFrom(cls)) { if (Optional.ofNullable(typeReference) @@ -828,6 +880,9 @@ private static T decodeArrayElements( typeReference .getSubTypeReference() .getInnerTypes()) {}); + currOffset += + getSingleElementLength(input, currOffset, cls) + * MAX_BYTE_LENGTH_FOR_HEX_STRING; } else { value = TypeDecoder.decodeDynamicStruct( @@ -836,11 +891,31 @@ private static T decodeArrayElements( + getDataOffset( input, currOffset, typeReference), TypeReference.create(cls)); + currOffset += + getSingleElementLength(input, currOffset, cls) + * MAX_BYTE_LENGTH_FOR_HEX_STRING; } } else { - value = - TypeDecoder.decodeStaticStruct( - input, currOffset, TypeReference.create(cls)); + if (Optional.ofNullable(typeReference) + .map(x -> x.getSubTypeReference()) + .map(x -> x.getInnerTypes()) + .isPresent()) { + value = + TypeDecoder.decodeStaticStruct( + input, + currOffset, + (TypeReference) typeReference.getSubTypeReference()); + currOffset += + numNestedFields(typeReference.getSubTypeReference()) + * MAX_BYTE_LENGTH_FOR_HEX_STRING; + } else { + value = + TypeDecoder.decodeStaticStruct( + input, currOffset, TypeReference.create(cls)); + currOffset += + getSingleElementLength(input, currOffset, cls) + * MAX_BYTE_LENGTH_FOR_HEX_STRING; + } } elements.add(value); } diff --git a/abi/src/test/java/org/web3j/abi/FunctionReturnDecoderTest.java b/abi/src/test/java/org/web3j/abi/FunctionReturnDecoderTest.java index 29469a77b..639693e4a 100644 --- a/abi/src/test/java/org/web3j/abi/FunctionReturnDecoderTest.java +++ b/abi/src/test/java/org/web3j/abi/FunctionReturnDecoderTest.java @@ -1259,6 +1259,69 @@ public void testDecodeStaticStructDynamicArray() { 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 = + "0x0000000000000000000000000000000000000000000000000000000000000020" + + "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>(); @@ -1319,6 +1382,42 @@ public void testDecodeDynamicStructWithStaticStruct() { 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 { @@ -1383,7 +1482,7 @@ public void testDynamicStructOfDynamicStructWithAdditionalParametersReturn() @Test public void testDynamicStructOfStaticStructReturn() throws ClassNotFoundException { String returnedData = - "0xf535d95e00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000004746573740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000047465737400000000000000000000000000000000000000000000000000000000"; + "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000004746573740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000047465737400000000000000000000000000000000000000000000000000000000"; List> myStruct2Types = new ArrayList<>(); List> myStructTypes = new ArrayList<>(); @@ -1395,14 +1494,20 @@ public void testDynamicStructOfStaticStructReturn() throws ClassNotFoundExceptio myStructTypes.add(TypeReference.makeTypeReference("string")); myStructTypes.add(new TypeReference(false, myStruct2Types) {}); + TypeReference dynamicStruct = + new TypeReference(false, myStructTypes) {}; + List decodedData = - FunctionReturnDecoder.decode(returnedData, Utils.convert(myStructTypes)); + FunctionReturnDecoder.decode( + returnedData, Utils.convert(Arrays.asList(dynamicStruct))); + + List decodedStruct = ((DynamicStruct) decodedData.get(0)).getValue(); - assertEquals(decodedData.get(0).getValue(), BigInteger.valueOf(1)); - assertEquals(decodedData.get(1).getValue(), "test"); - assertEquals(decodedData.get(2).getValue(), "test"); + assertEquals(decodedStruct.get(0).getValue(), BigInteger.valueOf(1)); + assertEquals(decodedStruct.get(1).getValue(), "test"); + assertEquals(decodedStruct.get(2).getValue(), "test"); - List innerStructData = ((StaticStruct) decodedData.get(3)).getValue(); + List innerStructData = ((StaticStruct) decodedStruct.get(3)).getValue(); assertEquals(innerStructData.get(0).getValue(), BigInteger.valueOf(1)); } From 622214a3cb7967a7a9f181ce46aabe4db3d1ae58 Mon Sep 17 00:00:00 2001 From: Antlion12 Date: Thu, 8 Aug 2024 03:07:45 -0700 Subject: [PATCH 11/13] Rename numNestedFields() to countNestedFields(). Signed-off-by: Antlion12 --- abi/src/main/java/org/web3j/abi/TypeDecoder.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/abi/src/main/java/org/web3j/abi/TypeDecoder.java b/abi/src/main/java/org/web3j/abi/TypeDecoder.java index f2df48514..65257dc67 100644 --- a/abi/src/main/java/org/web3j/abi/TypeDecoder.java +++ b/abi/src/main/java/org/web3j/abi/TypeDecoder.java @@ -385,18 +385,18 @@ public static T decodeStaticStruct( } // Counts the number of nested fields in a StaticStruct with inner types. - private static int numNestedFields(final TypeReference typeReference) { + private static int countNestedFields(final TypeReference typeReference) { try { if (StaticStruct.class.isAssignableFrom(typeReference.getClassType())) { return typeReference.getInnerTypes().stream() - .map((tr) -> numNestedFields(tr)) + .map((tr) -> countNestedFields(tr)) .reduce(0, (a, b) -> a + b); } return 1; } catch (ClassNotFoundException e) { throw new UnsupportedOperationException( - "numNestedFields failed for " + Utils.getTypeName(typeReference.getType()), e); + "countNestedFields failed for " + Utils.getTypeName(typeReference.getType()), e); } } @@ -416,7 +416,7 @@ private static T decodeStaticStructElementFromInnerTypes( final Class declaredField = innerType.getClassType(); if (StaticStruct.class.isAssignableFrom(declaredField)) { - final int nestedStructLength = numNestedFields(innerType) * 64; + final int nestedStructLength = countNestedFields(innerType) * 64; value = decodeStaticStruct( input.substring(currOffset, currOffset + nestedStructLength), @@ -587,7 +587,7 @@ ParameterOffsetTracker getDynamicOffsetsAndNonDynamicParameters( } else { if (StaticStruct.class.isAssignableFrom(declaredField)) { value = decodeStaticStruct(input.substring(beginIndex), 0, innerType); - tracker.staticOffset += numNestedFields(innerType) * 64; + tracker.staticOffset += countNestedFields(innerType) * 64; } else { value = decode(input.substring(beginIndex), 0, declaredField); tracker.staticOffset += value.bytes32PaddedLength() * 2; @@ -906,7 +906,7 @@ private static T decodeArrayElements( currOffset, (TypeReference) typeReference.getSubTypeReference()); currOffset += - numNestedFields(typeReference.getSubTypeReference()) + countNestedFields(typeReference.getSubTypeReference()) * MAX_BYTE_LENGTH_FOR_HEX_STRING; } else { value = From 7a9d6f43e3cf63e142e54709c0eb38e33f638a73 Mon Sep 17 00:00:00 2001 From: Antlion12 Date: Thu, 8 Aug 2024 03:11:24 -0700 Subject: [PATCH 12/13] Run spotless. Signed-off-by: Antlion12 --- abi/src/main/java/org/web3j/abi/TypeDecoder.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/abi/src/main/java/org/web3j/abi/TypeDecoder.java b/abi/src/main/java/org/web3j/abi/TypeDecoder.java index 65257dc67..777adb38e 100644 --- a/abi/src/main/java/org/web3j/abi/TypeDecoder.java +++ b/abi/src/main/java/org/web3j/abi/TypeDecoder.java @@ -396,7 +396,8 @@ private static int countNestedFields(final TypeReference typ return 1; } catch (ClassNotFoundException e) { throw new UnsupportedOperationException( - "countNestedFields failed for " + Utils.getTypeName(typeReference.getType()), e); + "countNestedFields failed for " + Utils.getTypeName(typeReference.getType()), + e); } } From ca5905588857a83d0afaa3d0f81d960b37d94a3d Mon Sep 17 00:00:00 2001 From: Antlion12 Date: Thu, 8 Aug 2024 03:16:21 -0700 Subject: [PATCH 13/13] Update CHANGELOG.md. Signed-off-by: Antlion12 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b638f612..b8049f2f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline * bump snapshot version to 4.12.1 [#2058](https://github.com/hyperledger/web3j/pull/2058) * Update maintainer requirements status [#2064](https://github.com/hyperledger/web3j/pull/2064) +* Add struct support in java without the need of having a corresponding Java class [#2076](https://github.com/hyperledger/web3j/pull/2076) ### BREAKING CHANGES