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 diff --git a/abi/src/main/java/org/web3j/abi/TypeDecoder.java b/abi/src/main/java/org/web3j/abi/TypeDecoder.java index 695b66455..662b21e7d 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; @@ -133,6 +134,11 @@ 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)); } @@ -371,9 +377,69 @@ 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 countNestedFields(final TypeReference typeReference) { + try { + if (StaticStruct.class.isAssignableFrom(typeReference.getClassType())) { + return typeReference.getInnerTypes().stream() + .map((tr) -> countNestedFields(tr)) + .reduce(0, (a, b) -> a + b); + } + + return 1; + } catch (ClassNotFoundException e) { + throw new UnsupportedOperationException( + "countNestedFields 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 = countNestedFields(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, @@ -428,9 +494,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()); + if (classType.isAssignableFrom(DynamicStruct.class)) { + return (T) new DynamicStruct((List) parameters); + } else if (classType.isAssignableFrom(StaticStruct.class)) { + return (T) new StaticStruct((List) parameters); + } else { + Constructor ctor = findStructConstructor(classType); + ctor.setAccessible(true); + return (T) ctor.newInstance(parameters.toArray()); + } } catch (ReflectiveOperationException e) { throw new UnsupportedOperationException( "Constructor cannot accept" + Arrays.toString(parameters.toArray()), e); @@ -452,21 +524,136 @@ public static T decodeDynamicArray( } public static T decodeDynamicStruct( - String input, int offset, TypeReference typeReference) { + String input, int offset, TypeReference typeReference) + throws ClassNotFoundException { BiFunction, String, T> function = (elements, typeName) -> { if (elements.isEmpty()) { throw new UnsupportedOperationException( "Zero length fixed array is invalid type"); - } else { - return instantiateStruct(typeReference, elements); } + + return instantiateStruct(typeReference, elements); }; + if (typeReference.getClassType().isAssignableFrom(DynamicStruct.class) + && typeReference.getInnerTypes() != null) { + return decodeDynamicStructElementsFromInnerTypes( + input, offset, typeReference, function); + } + return decodeDynamicStructElements(input, offset, typeReference, function); } + private static class ParameterOffsetTracker { + public final Map parameters; + public final List parameterOffsets; + public int staticOffset; + public int dynamicParametersToProcess; + + ParameterOffsetTracker( + final Map parametersIn, + final List parameterOffsetsIn, + int staticOffsetIn, + int dynamicParametersToProcessIn) { + this.parameters = parametersIn; + this.parameterOffsets = parameterOffsetsIn; + this.staticOffset = staticOffsetIn; + this.dynamicParametersToProcess = dynamicParametersToProcess; + } + } + + private static + ParameterOffsetTracker getDynamicOffsetsAndNonDynamicParameters( + final String input, final int offset, final TypeReference typeReference) + throws ClassNotFoundException { + ParameterOffsetTracker tracker = + new ParameterOffsetTracker(new HashMap<>(), new ArrayList<>(), 0, 0); + + final List> innerTypes = typeReference.getInnerTypes(); + for (int i = 0; i < innerTypes.size(); ++i) { + final TypeReference innerType = (TypeReference) innerTypes.get(i); + final Class declaredField = innerType.getClassType(); + final T value; + final int beginIndex = offset + tracker.staticOffset; + if (isDynamic(declaredField)) { + final int parameterOffset = + decodeDynamicStructDynamicParameterOffset( + input.substring(beginIndex, beginIndex + 64)) + + offset; + tracker.parameterOffsets.add(parameterOffset); + tracker.staticOffset += 64; + tracker.dynamicParametersToProcess += 1; + } else { + if (StaticStruct.class.isAssignableFrom(declaredField)) { + value = decodeStaticStruct(input.substring(beginIndex), 0, innerType); + tracker.staticOffset += countNestedFields(innerType) * 64; + } else { + value = decode(input.substring(beginIndex), 0, declaredField); + tracker.staticOffset += value.bytes32PaddedLength() * 2; + } + tracker.parameters.put(i, value); + } + } + + return tracker; + } + + private static List getDynamicParametersWithTracker( + final String input, + final TypeReference typeReference, + final ParameterOffsetTracker tracker) + throws ClassNotFoundException { + + final List> innerTypes = typeReference.getInnerTypes(); + int dynamicParametersProcessed = 0; + for (int i = 0; i < innerTypes.size(); ++i) { + final TypeReference parameterTypeReference = (TypeReference) innerTypes.get(i); + final Class declaredField = parameterTypeReference.getClassType(); + if (isDynamic(declaredField)) { + final boolean isLastParameterInStruct = + dynamicParametersProcessed == (tracker.dynamicParametersToProcess - 1); + final int parameterLength = + isLastParameterInStruct + ? input.length() + - tracker.parameterOffsets.get(dynamicParametersProcessed) + : tracker.parameterOffsets.get(dynamicParametersProcessed + 1) + - tracker.parameterOffsets.get(dynamicParametersProcessed); + + tracker.parameters.put( + i, + decodeDynamicParameterFromStructWithTypeReference( + input, + tracker.parameterOffsets.get(dynamicParametersProcessed), + parameterLength, + parameterTypeReference)); + dynamicParametersProcessed++; + } + } + + final List elements = new ArrayList<>(); + for (int i = 0; i < innerTypes.size(); ++i) { + elements.add(tracker.parameters.get(i)); + } + + return elements; + } + + @SuppressWarnings("unchecked") + private static T decodeDynamicStructElementsFromInnerTypes( + final String input, + final int offset, + final TypeReference typeReference, + final BiFunction, String, T> consumer) + throws ClassNotFoundException { + ParameterOffsetTracker tracker = + getDynamicOffsetsAndNonDynamicParameters(input, offset, typeReference); + final List parameters = getDynamicParametersWithTracker(input, typeReference, tracker); + String typeName = getSimpleTypeName(typeReference.getClassType()); + return consumer.apply(parameters, typeName); + } + @SuppressWarnings("unchecked") private static T decodeDynamicStructElements( final String input, @@ -565,7 +752,8 @@ private static T decodeDynamicParameterFromStruct( 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); @@ -589,6 +777,27 @@ private static T decodeDynamicParameterFromStruct( return value; } + private static T decodeDynamicParameterFromStructWithTypeReference( + final String input, + final int parameterOffset, + final int parameterLength, + final TypeReference parameterTypeReference) + throws ClassNotFoundException { + final String dynamicElementData = + input.substring(parameterOffset, parameterOffset + parameterLength); + final Class declaredField = parameterTypeReference.getClassType(); + + final T value; + if (DynamicStruct.class.isAssignableFrom(declaredField)) { + value = decodeDynamicStruct(dynamicElementData, 0, parameterTypeReference); + } else if (DynamicArray.class.isAssignableFrom(declaredField)) { + value = (T) decodeDynamicArray(dynamicElementData, 0, parameterTypeReference); + } else { + value = decode(dynamicElementData, declaredField); + } + return value; + } + private static int decodeDynamicStructDynamicParameterOffset(final String input) { return (decodeUintAsInt(input, 0) * 2); } @@ -596,7 +805,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) { @@ -651,23 +861,62 @@ 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)) { - 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()) {}); + currOffset += + getSingleElementLength(input, currOffset, cls) + * MAX_BYTE_LENGTH_FOR_HEX_STRING; + } else { + value = + TypeDecoder.decodeDynamicStruct( + input, + offset + + getDataOffset( + input, currOffset, typeReference), + TypeReference.create(cls)); + currOffset += + getSingleElementLength(input, currOffset, cls) + * MAX_BYTE_LENGTH_FOR_HEX_STRING; + } } else { - 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 += + countNestedFields(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); } @@ -707,7 +956,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 07d7e1249..54945e3e2 100644 --- a/abi/src/main/java/org/web3j/abi/TypeReference.java +++ b/abi/src/main/java/org/web3j/abi/TypeReference.java @@ -14,6 +14,7 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -39,10 +40,17 @@ public abstract class TypeReference private final Type type; private final boolean indexed; + protected List> innerTypes; + protected TypeReference() { this(false); } + protected TypeReference(boolean indexed, List> innerTypesIn) { + this(indexed); + this.innerTypes = innerTypesIn; + } + protected TypeReference(boolean indexed) { Type superclass = getClass().getGenericSuperclass(); if (superclass instanceof Class) { @@ -59,10 +67,14 @@ protected TypeReference(boolean indexed) { * * @return the type wrapped by this Array TypeReference, or null if not Array */ - TypeReference getSubTypeReference() { + public TypeReference getSubTypeReference() { return null; } + public List> getInnerTypes() { + return this.innerTypes; + } + public int compareTo(TypeReference o) { // taken from the blog post comments - this results in an error if the // type parameter is left out. @@ -175,7 +187,7 @@ public static TypeReference makeTypeReference( arrayWrappedType = new TypeReference(indexed) { @Override - TypeReference getSubTypeReference() { + public TypeReference getSubTypeReference() { return baseTr; } @@ -213,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/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/FunctionReturnDecoderTest.java b/abi/src/test/java/org/web3j/abi/FunctionReturnDecoderTest.java index 9cf79926e..639693e4a 100644 --- a/abi/src/test/java/org/web3j/abi/FunctionReturnDecoderTest.java +++ b/abi/src/test/java/org/web3j/abi/FunctionReturnDecoderTest.java @@ -22,8 +22,10 @@ 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.StaticStruct; import org.web3j.abi.datatypes.Type; import org.web3j.abi.datatypes.Uint; import org.web3j.abi.datatypes.Utf8String; @@ -1257,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>(); @@ -1316,4 +1381,229 @@ public void testDecodeDynamicStructWithStaticStruct() { new AbiV2TestFixture.Qux( new AbiV2TestFixture.Bar(BigInteger.ONE, BigInteger.TEN), "data"))); } + + @Test + public void testBuildDynamicStructWithStaticStruct() throws ClassNotFoundException { + // This is a version of testDecodeDynamicStructWithStaticStruct() that builds + // the decoding TypeReferences using inner types. + String rawInput = + "0x0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "000000000000000000000000000000000000000000000000000000000000000a" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "6461746100000000000000000000000000000000000000000000000000000000"; + // (uint256, uint256) static struct. + TypeReference staticStruct = + new TypeReference( + false, + Arrays.asList( + TypeReference.makeTypeReference("uint256"), + TypeReference.makeTypeReference("uint256"))) {}; + + // ((uint256, uint256), string) dynamic struct containing static struct. + TypeReference dynamicStruct = + new TypeReference( + false, + Arrays.asList(staticStruct, TypeReference.makeTypeReference("string"))) {}; + + List decodedData = + FunctionReturnDecoder.decode(rawInput, Utils.convert(Arrays.asList(dynamicStruct))); + List decodedDynamicStruct = ((DynamicStruct) decodedData.get(0)).getValue(); + + List decodedStaticStruct = ((StaticStruct) decodedDynamicStruct.get(0)).getValue(); + assertEquals(decodedStaticStruct.get(0).getValue(), BigInteger.ONE); + assertEquals(decodedStaticStruct.get(1).getValue(), BigInteger.TEN); + + assertEquals(decodedDynamicStruct.get(1).getValue(), "data"); + } + + @Test + public void testDynamicStructOfDynamicStructWithAdditionalParametersReturn() + throws ClassNotFoundException { + // Return data from 'testInputAndOutput' function of this contract + // https://sepolia.etherscan.io/address/0x009C10396226ECFE3E39b3f1AEFa072E37578e30#readContract + // struct MyStruct { + // uint256 value1; + // string value2; + // string value3; + // MyStruct2 value4; + // } + // + // struct MyStruct2 { + // string value1; + // string value2; + // } + // function testInputAndOutput(MyStruct memory struc) external pure + // returns(string memory valueBefore, MyStruct memory, string memory valueAfter) { + // + // return ("valuebefore", mystruc, "valueafter"); + // + // } + String returnedData = + "0x000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000000b76616c75656265666f72650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000001320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004313233340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000063078313233340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a76616c7565616674657200000000000000000000000000000000000000000000"; + + List> myStruct2Types = new ArrayList<>(); + List> myStructTypes = new ArrayList<>(); + List> myParameters = new ArrayList<>(); + + myStruct2Types.add(TypeReference.makeTypeReference("string")); + myStruct2Types.add(TypeReference.makeTypeReference("string")); + + myStructTypes.add(TypeReference.makeTypeReference("uint256")); + myStructTypes.add(TypeReference.makeTypeReference("string")); + myStructTypes.add(TypeReference.makeTypeReference("string")); + myStructTypes.add(new TypeReference(false, myStruct2Types) {}); + + myParameters.add(TypeReference.makeTypeReference("string")); + myParameters.add(new TypeReference(false, myStructTypes) {}); + + myParameters.add(TypeReference.makeTypeReference("string")); + + List decodedData = + FunctionReturnDecoder.decode(returnedData, Utils.convert(myParameters)); + + assertEquals(decodedData.get(0).getValue(), "valuebefore"); + + List structData = ((DynamicStruct) decodedData.get(1)).getValue(); + + assertEquals(structData.get(0).getValue(), BigInteger.valueOf(1)); + assertEquals(structData.get(1).getValue(), "2"); + assertEquals(structData.get(2).getValue(), "3"); + + List innerStructData = ((DynamicStruct) structData.get(3)).getValue(); + + assertEquals(innerStructData.get(0).getValue(), "1234"); + assertEquals(innerStructData.get(1).getValue(), "0x1234"); + + assertEquals(decodedData.get(2).getValue(), "valueafter"); + } + + @Test + public void testDynamicStructOfStaticStructReturn() throws ClassNotFoundException { + String returnedData = + "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000004746573740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000047465737400000000000000000000000000000000000000000000000000000000"; + + List> myStruct2Types = new ArrayList<>(); + List> myStructTypes = new ArrayList<>(); + + myStruct2Types.add(TypeReference.makeTypeReference("uint256")); + + myStructTypes.add(TypeReference.makeTypeReference("uint256")); + myStructTypes.add(TypeReference.makeTypeReference("string")); + myStructTypes.add(TypeReference.makeTypeReference("string")); + myStructTypes.add(new TypeReference(false, myStruct2Types) {}); + + TypeReference dynamicStruct = + new TypeReference(false, myStructTypes) {}; + + List decodedData = + FunctionReturnDecoder.decode( + returnedData, Utils.convert(Arrays.asList(dynamicStruct))); + + List decodedStruct = ((DynamicStruct) decodedData.get(0)).getValue(); + + assertEquals(decodedStruct.get(0).getValue(), BigInteger.valueOf(1)); + assertEquals(decodedStruct.get(1).getValue(), "test"); + assertEquals(decodedStruct.get(2).getValue(), "test"); + + List innerStructData = ((StaticStruct) decodedStruct.get(3)).getValue(); + + assertEquals(innerStructData.get(0).getValue(), BigInteger.valueOf(1)); + } + + @Test + public void testBuildEventOfArrayOfDynamicStruct() throws ClassNotFoundException { + // The full event signature is + // + // Stamp3(uint256 indexed stampId, address indexed caller, bool odd, + // (uint256,bool,string) topMessage, (uint256,bool,string)[] messages), + // + // but we are only decoding the non-indexed data portion of it represented by + // 'bool odd, (uint256,bool,string) topMessage, (uint256,bool,string)[] messages'. + // + // Transaction: + // https://testnet.treasurescan.io/tx/0x041e53e7571283d462df99a95b2c21324279657f26a3adef907095d2d9c5ed85?tab=logs + // Contract: + // https://testnet.treasurescan.io/address/0x5167E9A422aCEd95C2D0b62bF05a7847a9a942B2 + String data = + "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"); + } } diff --git a/codegen/src/test/java/org/web3j/codegen/SolidityFunctionWrapperGeneratorTest.java b/codegen/src/test/java/org/web3j/codegen/SolidityFunctionWrapperGeneratorTest.java index 7b8d15ab2..abf7c2ae3 100644 --- a/codegen/src/test/java/org/web3j/codegen/SolidityFunctionWrapperGeneratorTest.java +++ b/codegen/src/test/java/org/web3j/codegen/SolidityFunctionWrapperGeneratorTest.java @@ -257,7 +257,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() {