Skip to content

Commit

Permalink
Support building StaticStruct using innerTypes. Add testBuildDynamicA…
Browse files Browse the repository at this point in the history
…rrayOfStaticStruct() and testBuildDynamicStructOfStaticStruct() unit tests that use the innerTypes version.

Signed-off-by: Antlion12 <[email protected]>
  • Loading branch information
Antlion12 committed Aug 5, 2024
1 parent 574ec57 commit d35b8fa
Show file tree
Hide file tree
Showing 2 changed files with 206 additions and 26 deletions.
115 changes: 95 additions & 20 deletions abi/src/main/java/org/web3j/abi/TypeDecoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -377,9 +377,68 @@ public static <T extends Type> 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 <T extends Type> int numNestedFields(final TypeReference<T> 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 extends Type> T decodeStaticStructElementFromInnerTypes(
final String input,
final int offset,
final TypeReference<T> typeReference,
final BiFunction<List<T>, String, T> consumer) {
try {
final List<TypeReference<?>> innerTypes = typeReference.getInnerTypes();
List<T> elements = new ArrayList<>(innerTypes.size());

for (int i = 0, currOffset = offset; i < innerTypes.size(); i++) {
T value;
final TypeReference<T> innerType = (TypeReference<T>) innerTypes.get(i);
final Class<T> 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 extends Type> T decodeStaticStructElement(
final String input,
Expand Down Expand Up @@ -436,6 +495,8 @@ private static <T extends Type> T instantiateStruct(
Class<T> classType = typeReference.getClassType();
if (classType.isAssignableFrom(DynamicStruct.class)) {
return (T) new DynamicStruct((List<Type>) parameters);
} else if (classType.isAssignableFrom(StaticStruct.class)) {
return (T) new StaticStruct((List<Type>) parameters);
} else {
Constructor ctor = findStructConstructor(classType);
ctor.setAccessible(true);
Expand Down Expand Up @@ -511,7 +572,8 @@ ParameterOffsetTracker<T> getDynamicOffsetsAndNonDynamicParameters(

final List<TypeReference<?>> innerTypes = typeReference.getInnerTypes();
for (int i = 0; i < innerTypes.size(); ++i) {
final Class<T> declaredField = (Class<T>) innerTypes.get(i).getClassType();
final TypeReference<T> innerType = (TypeReference<T>) innerTypes.get(i);
final Class<T> declaredField = innerType.getClassType();
final T value;
final int beginIndex = offset + tracker.staticOffset;
if (isDynamic(declaredField)) {
Expand All @@ -524,15 +586,8 @@ ParameterOffsetTracker<T> getDynamicOffsetsAndNonDynamicParameters(
tracker.dynamicParametersToProcess += 1;
} else {
if (StaticStruct.class.isAssignableFrom(declaredField)) {
value =
decodeStaticStruct(
input.substring(beginIndex),
0,
TypeReference.create(declaredField));
tracker.staticOffset +=
staticStructNestedPublicFieldsFlatList((Class<Type>) 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;
Expand Down Expand Up @@ -749,7 +804,8 @@ private static int decodeDynamicStructDynamicParameterOffset(final String input)
static <T extends Type> boolean isDynamic(Class<T> 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) {
Expand Down Expand Up @@ -804,12 +860,8 @@ private static <T extends Type> T decodeArrayElements(
Class<T> cls = Utils.getParameterizedTypeFromArray(typeReference);
List<T> 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)
Expand All @@ -828,6 +880,9 @@ private static <T extends Type> T decodeArrayElements(
typeReference
.getSubTypeReference()
.getInnerTypes()) {});
currOffset +=
getSingleElementLength(input, currOffset, cls)
* MAX_BYTE_LENGTH_FOR_HEX_STRING;
} else {
value =
TypeDecoder.decodeDynamicStruct(
Expand All @@ -836,11 +891,31 @@ private static <T extends Type> 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<T>) 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);
}
Expand Down
117 changes: 111 additions & 6 deletions abi/src/test/java/org/web3j/abi/FunctionReturnDecoderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<StaticStruct> staticStructTr =
new TypeReference<StaticStruct>(
false,
Arrays.asList(
TypeReference.makeTypeReference("uint256"),
TypeReference.makeTypeReference("uint256"))) {};

// (uint256, uint256)[] dynamic array of static struct.
TypeReference<DynamicArray> dynamicArray =
new TypeReference<DynamicArray>(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<Type> decodedData =
FunctionReturnDecoder.decode(rawInput, Utils.convert(Arrays.asList(dynamicArray)));

List<Type> decodedArray = ((DynamicArray) decodedData.get(0)).getValue();

List<Type> decodedStaticStruct0 = ((StaticStruct) decodedArray.get(0)).getValue();
assertEquals(decodedStaticStruct0.get(0).getValue(), BigInteger.valueOf(123));
assertEquals(decodedStaticStruct0.get(1).getValue(), BigInteger.valueOf(123));

List<Type> 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<TypeReference<Type>>();
Expand Down Expand Up @@ -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> staticStruct =
new TypeReference<StaticStruct>(
false,
Arrays.asList(
TypeReference.makeTypeReference("uint256"),
TypeReference.makeTypeReference("uint256"))) {};

// ((uint256, uint256), string) dynamic struct containing static struct.
TypeReference<DynamicStruct> dynamicStruct =
new TypeReference<DynamicStruct>(
false,
Arrays.asList(staticStruct, TypeReference.makeTypeReference("string"))) {};

List<Type> decodedData =
FunctionReturnDecoder.decode(rawInput, Utils.convert(Arrays.asList(dynamicStruct)));
List<Type> decodedDynamicStruct = ((DynamicStruct) decodedData.get(0)).getValue();

List<Type> 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 {
Expand Down Expand Up @@ -1383,7 +1482,7 @@ public void testDynamicStructOfDynamicStructWithAdditionalParametersReturn()
@Test
public void testDynamicStructOfStaticStructReturn() throws ClassNotFoundException {
String returnedData =
"0xf535d95e00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000004746573740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000047465737400000000000000000000000000000000000000000000000000000000";
"0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000004746573740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000047465737400000000000000000000000000000000000000000000000000000000";

List<TypeReference<?>> myStruct2Types = new ArrayList<>();
List<TypeReference<?>> myStructTypes = new ArrayList<>();
Expand All @@ -1395,14 +1494,20 @@ public void testDynamicStructOfStaticStructReturn() throws ClassNotFoundExceptio
myStructTypes.add(TypeReference.makeTypeReference("string"));
myStructTypes.add(new TypeReference<StaticStruct>(false, myStruct2Types) {});

TypeReference<DynamicStruct> dynamicStruct =
new TypeReference<DynamicStruct>(false, myStructTypes) {};

List<Type> decodedData =
FunctionReturnDecoder.decode(returnedData, Utils.convert(myStructTypes));
FunctionReturnDecoder.decode(
returnedData, Utils.convert(Arrays.asList(dynamicStruct)));

List<Type> 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<Type> innerStructData = ((StaticStruct) decodedData.get(3)).getValue();
List<Type> innerStructData = ((StaticStruct) decodedStruct.get(3)).getValue();

assertEquals(innerStructData.get(0).getValue(), BigInteger.valueOf(1));
}
Expand Down

0 comments on commit d35b8fa

Please sign in to comment.