Skip to content

Commit 87d2446

Browse files
authored
Structs arrays support (LFDT-web3j#1321)
* adds structs arrays fixtures to AbiV2TestFixture * Adds necessary AbiV2 functions for testing * Adds Abi Decoder tests * Adds Structs array + multiple structs returns decoder * fixes String padded length size * adds type name return for structs when getting simple name * Adds structs encoder tests * Adds structs encoder * adds struct type to AbiTypes * imports cleanup * spotless * Utils refactor * fix array of structs creation by the decoder * fix structs array encoding * imports fix * add support for array of structs wrapper generation * make the convert to native function support arrays of structs * add getTypeAsString java doc * fix getTypeAsString to support structs arrays * add encoder structs array tests * add codegen structs array wrapper generation test * cosmetics * spotless + cosmetics * suppress warnings + remove unnecessary sout * suppress warnings + remove unnecessary sout * update ComplexStorage.sol to use arrays of structs * cosmetics * fixes dynamic byte arrays prefixed their length encoding in structs. * structs dynamic values tweak + add tests for dynamic struct containing dynamic bytes array * fixes encoding of array strings + adds tests * fixes byte types padded length * cosmetics * spotless * fix isParametrizedTypeStaticStruct * remove unnecessary function * fixes dynamic type in static array + adds tests * adds more encoder tests * fixes static array with static struct decoder * adds static/dynamic array with static struct encoder test * suppress warnings + java docs + cosmetics * javadocs + spotless
1 parent b7a21ad commit 87d2446

22 files changed

+2758
-105
lines changed

abi/src/main/java/org/web3j/abi/DefaultFunctionEncoder.java

+18-1
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,15 @@
1515
import java.math.BigInteger;
1616
import java.util.List;
1717

18+
import org.web3j.abi.datatypes.DynamicStruct;
1819
import org.web3j.abi.datatypes.Function;
1920
import org.web3j.abi.datatypes.StaticArray;
21+
import org.web3j.abi.datatypes.StaticStruct;
2022
import org.web3j.abi.datatypes.Type;
2123
import org.web3j.abi.datatypes.Uint;
2224

25+
import static org.web3j.abi.Utils.staticStructNestedPublicFieldsFlatList;
26+
2327
public class DefaultFunctionEncoder extends FunctionEncoder {
2428

2529
@Override
@@ -64,10 +68,23 @@ private static String encodeParameters(
6468
return result.toString();
6569
}
6670

71+
@SuppressWarnings("unchecked")
6772
private static int getLength(final List<Type> parameters) {
6873
int count = 0;
6974
for (final Type type : parameters) {
70-
if (type instanceof StaticArray) {
75+
if (type instanceof StaticArray
76+
&& StaticStruct.class.isAssignableFrom(
77+
((StaticArray) type).getComponentType())) {
78+
count +=
79+
staticStructNestedPublicFieldsFlatList(
80+
((StaticArray) type).getComponentType())
81+
.size()
82+
* ((StaticArray) type).getValue().size();
83+
} else if (type instanceof StaticArray
84+
&& DynamicStruct.class.isAssignableFrom(
85+
((StaticArray) type).getComponentType())) {
86+
count++;
87+
} else if (type instanceof StaticArray) {
7188
count += ((StaticArray) type).getValue().size();
7289
} else {
7390
count++;

abi/src/main/java/org/web3j/abi/DefaultFunctionReturnDecoder.java

+51-11
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@
3131
import org.web3j.utils.Strings;
3232

3333
import static org.web3j.abi.TypeDecoder.MAX_BYTE_LENGTH_FOR_HEX_STRING;
34+
import static org.web3j.abi.TypeDecoder.isDynamic;
35+
import static org.web3j.abi.Utils.getParameterizedTypeFromArray;
36+
import static org.web3j.abi.Utils.staticStructNestedPublicFieldsFlatList;
3437

3538
/**
3639
* Ethereum Contract Application Binary Interface (ABI) encoding for functions. Further details are
@@ -80,17 +83,13 @@ private static List<Type> build(String input, List<TypeReference<Type>> outputPa
8083
int offset = 0;
8184
for (TypeReference<?> typeReference : outputParameters) {
8285
try {
86+
int hexStringDataOffset = getDataOffset(input, offset, typeReference);
87+
8388
@SuppressWarnings("unchecked")
8489
Class<Type> classType = (Class<Type>) typeReference.getClassType();
8590

86-
int hexStringDataOffset = getDataOffset(input, offset, classType);
87-
8891
Type result;
8992
if (DynamicStruct.class.isAssignableFrom(classType)) {
90-
if (outputParameters.size() != 1) {
91-
throw new UnsupportedOperationException(
92-
"Multiple return objects containing a struct is not supported");
93-
}
9493
result =
9594
TypeDecoder.decodeDynamicStruct(
9695
input, hexStringDataOffset, typeReference);
@@ -113,7 +112,9 @@ private static List<Type> build(String input, List<TypeReference<Type>> outputPa
113112
result =
114113
TypeDecoder.decodeStaticStruct(
115114
input, hexStringDataOffset, typeReference);
116-
offset += classType.getDeclaredFields().length * MAX_BYTE_LENGTH_FOR_HEX_STRING;
115+
offset +=
116+
staticStructNestedPublicFieldsFlatList(classType).size()
117+
* MAX_BYTE_LENGTH_FOR_HEX_STRING;
117118
} else if (StaticArray.class.isAssignableFrom(classType)) {
118119
int length =
119120
Integer.parseInt(
@@ -123,8 +124,21 @@ private static List<Type> build(String input, List<TypeReference<Type>> outputPa
123124
result =
124125
TypeDecoder.decodeStaticArray(
125126
input, hexStringDataOffset, typeReference, length);
126-
offset += length * MAX_BYTE_LENGTH_FOR_HEX_STRING;
127-
127+
if (DynamicStruct.class.isAssignableFrom(
128+
getParameterizedTypeFromArray(typeReference))) {
129+
offset += MAX_BYTE_LENGTH_FOR_HEX_STRING;
130+
} else if (StaticStruct.class.isAssignableFrom(
131+
getParameterizedTypeFromArray(typeReference))) {
132+
offset +=
133+
staticStructNestedPublicFieldsFlatList(
134+
getParameterizedTypeFromArray(
135+
typeReference))
136+
.size()
137+
* length
138+
* MAX_BYTE_LENGTH_FOR_HEX_STRING;
139+
} else {
140+
offset += length * MAX_BYTE_LENGTH_FOR_HEX_STRING;
141+
}
128142
} else {
129143
result = TypeDecoder.decode(input, hexStringDataOffset, classType);
130144
offset += MAX_BYTE_LENGTH_FOR_HEX_STRING;
@@ -138,13 +152,39 @@ private static List<Type> build(String input, List<TypeReference<Type>> outputPa
138152
return results;
139153
}
140154

141-
private static <T extends Type> int getDataOffset(String input, int offset, Class<T> type) {
155+
public static <T extends Type> int getDataOffset(
156+
String input, int offset, TypeReference<?> typeReference)
157+
throws ClassNotFoundException {
158+
@SuppressWarnings("unchecked")
159+
Class<Type> type = (Class<Type>) typeReference.getClassType();
142160
if (DynamicBytes.class.isAssignableFrom(type)
143161
|| Utf8String.class.isAssignableFrom(type)
144-
|| DynamicArray.class.isAssignableFrom(type)) {
162+
|| DynamicArray.class.isAssignableFrom(type)
163+
|| hasDynamicOffsetInStaticArray(typeReference, offset)) {
145164
return TypeDecoder.decodeUintAsInt(input, offset) << 1;
146165
} else {
147166
return offset;
148167
}
149168
}
169+
170+
/**
171+
* Checks if the parametrized type is offsetted in case of static array containing structs.
172+
*
173+
* @param typeReference of static array
174+
* @return true, if static array elements have dynamic offsets
175+
* @throws ClassNotFoundException if class type cannot be determined
176+
*/
177+
private static boolean hasDynamicOffsetInStaticArray(TypeReference<?> typeReference, int offset)
178+
throws ClassNotFoundException {
179+
@SuppressWarnings("unchecked")
180+
Class<Type> type = (Class<Type>) typeReference.getClassType();
181+
try {
182+
return StaticArray.class.isAssignableFrom(type)
183+
&& (DynamicStruct.class.isAssignableFrom(
184+
getParameterizedTypeFromArray(typeReference))
185+
|| isDynamic(getParameterizedTypeFromArray(typeReference)));
186+
} catch (ClassCastException e) {
187+
return false;
188+
}
189+
}
150190
}

abi/src/main/java/org/web3j/abi/TypeDecoder.java

+63-20
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import org.web3j.abi.datatypes.NumericType;
4242
import org.web3j.abi.datatypes.StaticArray;
4343
import org.web3j.abi.datatypes.StaticStruct;
44+
import org.web3j.abi.datatypes.StructType;
4445
import org.web3j.abi.datatypes.Type;
4546
import org.web3j.abi.datatypes.Ufixed;
4647
import org.web3j.abi.datatypes.Uint;
@@ -50,7 +51,10 @@
5051
import org.web3j.abi.datatypes.primitive.Float;
5152
import org.web3j.utils.Numeric;
5253

54+
import static org.web3j.abi.DefaultFunctionReturnDecoder.getDataOffset;
5355
import static org.web3j.abi.TypeReference.makeTypeReference;
56+
import static org.web3j.abi.Utils.getSimpleTypeName;
57+
import static org.web3j.abi.Utils.staticStructNestedPublicFieldsFlatList;
5458

5559
/**
5660
* Ethereum Contract Application Binary Interface (ABI) decoding for types. Decoding is not
@@ -252,13 +256,16 @@ static Type instantiateAtomicType(Class<?> referenceClass, Object value)
252256
return (Type) cons.newInstance(constructorArg);
253257
}
254258

259+
@SuppressWarnings("unchecked")
255260
static <T extends Type> int getSingleElementLength(String input, int offset, Class<T> type) {
256261
if (input.length() == offset) {
257262
return 0;
258263
} else if (DynamicBytes.class.isAssignableFrom(type)
259264
|| Utf8String.class.isAssignableFrom(type)) {
260265
// length field + data value
261266
return (decodeUintAsInt(input, offset) / Type.MAX_BYTE_LENGTH) + 2;
267+
} else if (StaticStruct.class.isAssignableFrom(type)) {
268+
return staticStructNestedPublicFieldsFlatList((Class<Type>) type).size();
262269
} else {
263270
return 1;
264271
}
@@ -331,7 +338,7 @@ static <T extends Type> T decodeStaticArray(
331338
throw new UnsupportedOperationException(
332339
"Zero length fixed array is invalid type");
333340
} else {
334-
return instantiateStaticArray(typeReference, elements, length);
341+
return instantiateStaticArray(elements, length);
335342
}
336343
};
337344

@@ -353,6 +360,7 @@ public static <T extends Type> T decodeStaticStruct(
353360
return decodeStaticStructElement(input, offset, typeReference, function);
354361
}
355362

363+
@SuppressWarnings("unchecked")
356364
private static <T extends Type> T decodeStaticStructElement(
357365
final String input,
358366
final int offset,
@@ -374,11 +382,10 @@ private static <T extends Type> T decodeStaticStructElement(
374382
final int length = constructor.getParameterCount();
375383
List<T> elements = new ArrayList<>(length);
376384

377-
for (int i = 0, currOffset = 0; i < length; i++) {
385+
for (int i = 0, currOffset = offset; i < length; i++) {
378386
T value;
379387
final Class<T> declaredField = (Class<T>) constructor.getParameterTypes()[i];
380388

381-
System.out.println(currOffset);
382389
if (StaticStruct.class.isAssignableFrom(declaredField)) {
383390
final int nestedStructLength =
384391
classType
@@ -401,7 +408,7 @@ private static <T extends Type> T decodeStaticStructElement(
401408
elements.add(value);
402409
}
403410

404-
String typeName = Utils.getSimpleTypeName(classType);
411+
String typeName = getSimpleTypeName(classType);
405412

406413
return consumer.apply(elements, typeName);
407414
} catch (ClassNotFoundException e) {
@@ -411,6 +418,7 @@ private static <T extends Type> T decodeStaticStructElement(
411418
}
412419
}
413420

421+
@SuppressWarnings("unchecked")
414422
private static <T extends Type> T instantiateStruct(
415423
final TypeReference<T> typeReference, final List<T> parameters) {
416424
try {
@@ -464,6 +472,7 @@ static <T extends Type> T decodeDynamicStruct(
464472
return decodeDynamicStructElements(input, offset, typeReference, function);
465473
}
466474

475+
@SuppressWarnings("unchecked")
467476
private static <T extends Type> T decodeDynamicStructElements(
468477
final String input,
469478
final int offset,
@@ -495,8 +504,9 @@ private static <T extends Type> T decodeDynamicStructElements(
495504
final int parameterOffset =
496505
isOnlyParameterInStruct
497506
? offset
498-
: decodeDynamicStructDynamicParameterOffset(
499-
input.substring(beginIndex, beginIndex + 64));
507+
: (decodeDynamicStructDynamicParameterOffset(
508+
input.substring(beginIndex, beginIndex + 64)))
509+
+ offset;
500510
parameterOffsets.add(parameterOffset);
501511
staticOffset += 64;
502512
} else {
@@ -506,11 +516,15 @@ private static <T extends Type> T decodeDynamicStructElements(
506516
input.substring(beginIndex),
507517
0,
508518
TypeReference.create(declaredField));
519+
staticOffset +=
520+
staticStructNestedPublicFieldsFlatList((Class<Type>) classType)
521+
.size()
522+
* MAX_BYTE_LENGTH_FOR_HEX_STRING;
509523
} else {
510524
value = decode(input.substring(beginIndex), 0, declaredField);
525+
staticOffset += value.bytes32PaddedLength() * 2;
511526
}
512527
parameters.put(i, value);
513-
staticOffset += value.bytes32PaddedLength() * 2;
514528
}
515529
}
516530
int dynamicParametersProcessed = 0;
@@ -538,7 +552,7 @@ private static <T extends Type> T decodeDynamicStructElements(
538552
}
539553
}
540554

541-
String typeName = Utils.getSimpleTypeName(classType);
555+
String typeName = getSimpleTypeName(classType);
542556

543557
final List<T> elements = new ArrayList<>();
544558
for (int i = 0; i < length; ++i) {
@@ -579,7 +593,7 @@ private static <T extends Type> T decodeDynamicParameterFromStruct(
579593
}
580594

581595
private static int decodeDynamicStructDynamicParameterOffset(final String input) {
582-
return (decodeUintAsInt(input, 0) * 2) + 64;
596+
return (decodeUintAsInt(input, 0) * 2);
583597
}
584598

585599
static <T extends Type> boolean isDynamic(Class<T> parameter) {
@@ -618,13 +632,11 @@ static List arrayToList(Object array) {
618632
}
619633

620634
@SuppressWarnings("unchecked")
621-
private static <T extends Type> T instantiateStaticArray(
622-
TypeReference<T> typeReference, List<T> elements, int length) {
635+
private static <T extends Type> T instantiateStaticArray(List<T> elements, int length) {
623636
try {
624637
Class<? extends StaticArray> arrayClass =
625638
(Class<? extends StaticArray>)
626639
Class.forName("org.web3j.abi.datatypes.generated.StaticArray" + length);
627-
628640
return (T) arrayClass.getConstructor(List.class).newInstance(elements);
629641
} catch (ReflectiveOperationException e) {
630642
throw new UnsupportedOperationException(e);
@@ -640,24 +652,55 @@ private static <T extends Type> T decodeArrayElements(
640652

641653
try {
642654
Class<T> cls = Utils.getParameterizedTypeFromArray(typeReference);
643-
if (Array.class.isAssignableFrom(cls)) {
644-
throw new UnsupportedOperationException(
645-
"Arrays of arrays are not currently supported for external functions, see"
646-
+ "http://solidity.readthedocs.io/en/develop/types.html#members");
647-
} else {
655+
if (StructType.class.isAssignableFrom(cls)) {
648656
List<T> elements = new ArrayList<>(length);
649-
650657
for (int i = 0, currOffset = offset;
651658
i < length;
652659
i++,
653660
currOffset +=
654661
getSingleElementLength(input, currOffset, cls)
655662
* MAX_BYTE_LENGTH_FOR_HEX_STRING) {
656-
T value = decode(input, currOffset, cls);
663+
T value;
664+
if (DynamicStruct.class.isAssignableFrom(cls)) {
665+
value =
666+
TypeDecoder.decodeDynamicStruct(
667+
input,
668+
offset + getDataOffset(input, currOffset, typeReference),
669+
TypeReference.create(cls));
670+
} else {
671+
value =
672+
TypeDecoder.decodeStaticStruct(
673+
input, currOffset, TypeReference.create(cls));
674+
}
675+
elements.add(value);
676+
}
677+
678+
String typeName = getSimpleTypeName(cls);
679+
680+
return consumer.apply(elements, typeName);
681+
} else if (Array.class.isAssignableFrom(cls)) {
682+
throw new UnsupportedOperationException(
683+
"Arrays of arrays are not currently supported for external functions, see"
684+
+ "http://solidity.readthedocs.io/en/develop/types.html#members");
685+
} else {
686+
List<T> elements = new ArrayList<>(length);
687+
int currOffset = offset;
688+
for (int i = 0; i < length; i++) {
689+
T value;
690+
if (isDynamic(cls)) {
691+
int hexStringDataOffset = getDataOffset(input, currOffset, typeReference);
692+
value = decode(input, offset + hexStringDataOffset, cls);
693+
currOffset += MAX_BYTE_LENGTH_FOR_HEX_STRING;
694+
} else {
695+
value = decode(input, currOffset, cls);
696+
currOffset +=
697+
getSingleElementLength(input, currOffset, cls)
698+
* MAX_BYTE_LENGTH_FOR_HEX_STRING;
699+
}
657700
elements.add(value);
658701
}
659702

660-
String typeName = Utils.getSimpleTypeName(cls);
703+
String typeName = getSimpleTypeName(cls);
661704

662705
return consumer.apply(elements, typeName);
663706
}

0 commit comments

Comments
 (0)