Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Structs arrays support #1321

Merged
merged 41 commits into from
Jan 8, 2021
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
e6ed967
adds structs arrays fixtures to AbiV2TestFixture
rach-id Dec 18, 2020
b37456b
Adds necessary AbiV2 functions for testing
rach-id Dec 31, 2020
6243000
Adds Abi Decoder tests
rach-id Dec 31, 2020
79c32fb
Adds Structs array + multiple structs returns decoder
rach-id Dec 31, 2020
3f51e1c
fixes String padded length size
rach-id Dec 31, 2020
f5b57b5
adds type name return for structs when getting simple name
rach-id Dec 31, 2020
aa22797
Adds structs encoder tests
rach-id Dec 31, 2020
c63e25e
Adds structs encoder
rach-id Dec 31, 2020
1d88507
adds struct type to AbiTypes
rach-id Dec 31, 2020
6be499c
imports cleanup
rach-id Dec 31, 2020
ef2741b
spotless
rach-id Dec 31, 2020
07f5e4e
Utils refactor
rach-id Jan 2, 2021
6dcff6a
fix array of structs creation by the decoder
rach-id Jan 2, 2021
7ce940c
fix structs array encoding
rach-id Jan 2, 2021
92c690c
imports fix
rach-id Jan 2, 2021
c350b79
add support for array of structs wrapper generation
rach-id Jan 2, 2021
9d403f0
make the convert to native function support arrays of structs
rach-id Jan 2, 2021
514bc93
add getTypeAsString java doc
rach-id Jan 2, 2021
cd43340
fix getTypeAsString to support structs arrays
rach-id Jan 2, 2021
b218a5c
add encoder structs array tests
rach-id Jan 2, 2021
ab8405e
add codegen structs array wrapper generation test
rach-id Jan 2, 2021
43853b0
cosmetics
rach-id Jan 2, 2021
d66b23d
spotless + cosmetics
rach-id Jan 2, 2021
0d049fc
suppress warnings + remove unnecessary sout
rach-id Jan 2, 2021
562aa2a
suppress warnings + remove unnecessary sout
rach-id Jan 2, 2021
db737e7
update ComplexStorage.sol to use arrays of structs
rach-id Jan 2, 2021
5dee583
cosmetics
rach-id Jan 3, 2021
273a556
fixes dynamic byte arrays prefixed their length encoding in structs.
rach-id Jan 4, 2021
6f7ea43
structs dynamic values tweak + add tests for dynamic struct containin…
rach-id Jan 4, 2021
8f7a675
fixes encoding of array strings + adds tests
rach-id Jan 4, 2021
7988f97
fixes byte types padded length
rach-id Jan 4, 2021
a23f9c6
cosmetics
rach-id Jan 4, 2021
d287a7f
spotless
rach-id Jan 4, 2021
06f60e5
fix isParametrizedTypeStaticStruct
rach-id Jan 6, 2021
a7d4bfe
remove unnecessary function
rach-id Jan 6, 2021
3c7e3dc
fixes dynamic type in static array + adds tests
rach-id Jan 6, 2021
2ed0b2d
adds more encoder tests
rach-id Jan 8, 2021
cb600f3
fixes static array with static struct decoder
rach-id Jan 8, 2021
a3216b2
adds static/dynamic array with static struct encoder test
rach-id Jan 8, 2021
39c3b1f
suppress warnings + java docs + cosmetics
rach-id Jan 8, 2021
e21005d
javadocs + spotless
rach-id Jan 8, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion abi/src/main/java/org/web3j/abi/DefaultFunctionEncoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,15 @@
import java.math.BigInteger;
import java.util.List;

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 static org.web3j.abi.Utils.staticStructCanonicalFieldsCount;

public class DefaultFunctionEncoder extends FunctionEncoder {

@Override
Expand Down Expand Up @@ -64,10 +68,21 @@ private static String encodeParameters(
return result.toString();
}

@SuppressWarnings("unchecked")
private static int getLength(final List<Type> parameters) {
int count = 0;
for (final Type type : parameters) {
if (type instanceof StaticArray) {
if (type instanceof StaticArray
&& StaticStruct.class.isAssignableFrom(
((StaticArray) type).getComponentType())) {
count +=
staticStructCanonicalFieldsCount(((StaticArray) type).getComponentType())
rach-id marked this conversation as resolved.
Show resolved Hide resolved
* ((StaticArray) type).getValue().size();
} else if (type instanceof StaticArray
&& DynamicStruct.class.isAssignableFrom(
((StaticArray) type).getComponentType())) {
count++;
} else if (type instanceof StaticArray) {
count += ((StaticArray) type).getValue().size();
} else {
count++;
Expand Down
64 changes: 53 additions & 11 deletions abi/src/main/java/org/web3j/abi/DefaultFunctionReturnDecoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
import org.web3j.utils.Strings;

import static org.web3j.abi.TypeDecoder.MAX_BYTE_LENGTH_FOR_HEX_STRING;
import static org.web3j.abi.TypeDecoder.isDynamic;
import static org.web3j.abi.Utils.getParameterizedTypeFromArray;

/**
* Ethereum Contract Application Binary Interface (ABI) encoding for functions. Further details are
Expand Down Expand Up @@ -80,17 +82,13 @@ private static List<Type> build(String input, List<TypeReference<Type>> outputPa
int offset = 0;
for (TypeReference<?> typeReference : outputParameters) {
try {
int hexStringDataOffset = getDataOffset(input, offset, typeReference);

@SuppressWarnings("unchecked")
Class<Type> classType = (Class<Type>) typeReference.getClassType();

int hexStringDataOffset = getDataOffset(input, offset, classType);

Type result;
if (DynamicStruct.class.isAssignableFrom(classType)) {
if (outputParameters.size() != 1) {
throw new UnsupportedOperationException(
"Multiple return objects containing a struct is not supported");
}
result =
TypeDecoder.decodeDynamicStruct(
input, hexStringDataOffset, typeReference);
Expand All @@ -113,7 +111,9 @@ private static List<Type> build(String input, List<TypeReference<Type>> outputPa
result =
TypeDecoder.decodeStaticStruct(
input, hexStringDataOffset, typeReference);
offset += classType.getDeclaredFields().length * MAX_BYTE_LENGTH_FOR_HEX_STRING;
offset +=
Utils.staticStructCanonicalFieldsCount(classType)
* MAX_BYTE_LENGTH_FOR_HEX_STRING;
} else if (StaticArray.class.isAssignableFrom(classType)) {
int length =
Integer.parseInt(
Expand All @@ -123,8 +123,21 @@ private static List<Type> build(String input, List<TypeReference<Type>> outputPa
result =
TypeDecoder.decodeStaticArray(
input, hexStringDataOffset, typeReference, length);
offset += length * MAX_BYTE_LENGTH_FOR_HEX_STRING;

if (DynamicStruct.class.isAssignableFrom(
getParameterizedTypeFromArray(typeReference))) {
offset += MAX_BYTE_LENGTH_FOR_HEX_STRING;
} else if (StaticStruct.class.isAssignableFrom(
getParameterizedTypeFromArray(typeReference))) {
offset +=
(int)
Utils.staticStructCanonicalFieldsCount(
getParameterizedTypeFromArray(
typeReference))
* length
* MAX_BYTE_LENGTH_FOR_HEX_STRING;
} else {
offset += length * MAX_BYTE_LENGTH_FOR_HEX_STRING;
}
} else {
result = TypeDecoder.decode(input, hexStringDataOffset, classType);
offset += MAX_BYTE_LENGTH_FOR_HEX_STRING;
Expand All @@ -138,13 +151,42 @@ private static List<Type> build(String input, List<TypeReference<Type>> outputPa
return results;
}

private static <T extends Type> int getDataOffset(String input, int offset, Class<T> type) {
public static <T extends Type> int getDataOffset(
String input, int offset, TypeReference<?> typeReference)
throws ClassNotFoundException {
@SuppressWarnings("unchecked")
Class<Type> type = (Class<Type>) typeReference.getClassType();
if (DynamicBytes.class.isAssignableFrom(type)
|| Utf8String.class.isAssignableFrom(type)
|| DynamicArray.class.isAssignableFrom(type)) {
|| DynamicArray.class.isAssignableFrom(type)
|| hasDynamicOffsetInStaticArray(typeReference, offset)) {
return TypeDecoder.decodeUintAsInt(input, offset) << 1;
} else {
return offset;
}
}

/**
* Checks if the parametrized type is offsetted in case of static array containing structs.
*
* @param typeReference
* @return
* @throws ClassNotFoundException
*/
private static boolean hasDynamicOffsetInStaticArray(TypeReference<?> typeReference, int offset)
throws ClassNotFoundException {
@SuppressWarnings("unchecked")
Class<Type> type = (Class<Type>) typeReference.getClassType();
try {
return StaticArray.class.isAssignableFrom(type)
&& (DynamicStruct.class.isAssignableFrom(
getParameterizedTypeFromArray(typeReference))
|| (StaticStruct.class.isAssignableFrom(
getParameterizedTypeFromArray(typeReference))
&& offset == 0)
|| isDynamic(getParameterizedTypeFromArray(typeReference)));
} catch (ClassCastException e) {
return false;
}
}
}
82 changes: 62 additions & 20 deletions abi/src/main/java/org/web3j/abi/TypeDecoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import org.web3j.abi.datatypes.NumericType;
import org.web3j.abi.datatypes.StaticArray;
import org.web3j.abi.datatypes.StaticStruct;
import org.web3j.abi.datatypes.StructType;
import org.web3j.abi.datatypes.Type;
import org.web3j.abi.datatypes.Ufixed;
import org.web3j.abi.datatypes.Uint;
Expand All @@ -50,7 +51,10 @@
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.getSimpleTypeName;
import static org.web3j.abi.Utils.staticStructCanonicalFieldsCount;

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

@SuppressWarnings("unchecked")
static <T extends Type> int getSingleElementLength(String input, int offset, Class<T> type) {
if (input.length() == offset) {
return 0;
} else if (DynamicBytes.class.isAssignableFrom(type)
|| Utf8String.class.isAssignableFrom(type)) {
// length field + data value
return (decodeUintAsInt(input, offset) / Type.MAX_BYTE_LENGTH) + 2;
} else if (StaticStruct.class.isAssignableFrom(type)) {
return (int) staticStructCanonicalFieldsCount((Class<Type>) type);
} else {
return 1;
}
Expand Down Expand Up @@ -331,7 +338,7 @@ static <T extends Type> T decodeStaticArray(
throw new UnsupportedOperationException(
"Zero length fixed array is invalid type");
} else {
return instantiateStaticArray(typeReference, elements, length);
return instantiateStaticArray(elements, length);
}
};

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

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

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

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

String typeName = Utils.getSimpleTypeName(classType);
String typeName = getSimpleTypeName(classType);

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

@SuppressWarnings("unchecked")
private static <T extends Type> T instantiateStruct(
final TypeReference<T> typeReference, final List<T> parameters) {
try {
Expand Down Expand Up @@ -464,6 +472,7 @@ static <T extends Type> T decodeDynamicStruct(
return decodeDynamicStructElements(input, offset, typeReference, function);
}

@SuppressWarnings("unchecked")
private static <T extends Type> T decodeDynamicStructElements(
final String input,
final int offset,
Expand Down Expand Up @@ -495,8 +504,9 @@ private static <T extends Type> T decodeDynamicStructElements(
final int parameterOffset =
isOnlyParameterInStruct
? offset
: decodeDynamicStructDynamicParameterOffset(
input.substring(beginIndex, beginIndex + 64));
: (decodeDynamicStructDynamicParameterOffset(
input.substring(beginIndex, beginIndex + 64)))
+ offset;
parameterOffsets.add(parameterOffset);
staticOffset += 64;
} else {
Expand All @@ -506,11 +516,14 @@ private static <T extends Type> T decodeDynamicStructElements(
input.substring(beginIndex),
0,
TypeReference.create(declaredField));
staticOffset +=
staticStructCanonicalFieldsCount((Class<Type>) classType)
* MAX_BYTE_LENGTH_FOR_HEX_STRING;
} else {
value = decode(input.substring(beginIndex), 0, declaredField);
staticOffset += value.bytes32PaddedLength() * 2;
}
parameters.put(i, value);
staticOffset += value.bytes32PaddedLength() * 2;
}
}
int dynamicParametersProcessed = 0;
Expand Down Expand Up @@ -538,7 +551,7 @@ private static <T extends Type> T decodeDynamicStructElements(
}
}

String typeName = Utils.getSimpleTypeName(classType);
String typeName = getSimpleTypeName(classType);

final List<T> elements = new ArrayList<>();
for (int i = 0; i < length; ++i) {
Expand Down Expand Up @@ -579,7 +592,7 @@ private static <T extends Type> T decodeDynamicParameterFromStruct(
}

private static int decodeDynamicStructDynamicParameterOffset(final String input) {
return (decodeUintAsInt(input, 0) * 2) + 64;
return (decodeUintAsInt(input, 0) * 2);
}

static <T extends Type> boolean isDynamic(Class<T> parameter) {
Expand Down Expand Up @@ -618,13 +631,11 @@ static List arrayToList(Object array) {
}

@SuppressWarnings("unchecked")
private static <T extends Type> T instantiateStaticArray(
TypeReference<T> typeReference, List<T> elements, int length) {
private static <T extends Type> T instantiateStaticArray(List<T> elements, int length) {
try {
Class<? extends StaticArray> arrayClass =
(Class<? extends StaticArray>)
Class.forName("org.web3j.abi.datatypes.generated.StaticArray" + length);

return (T) arrayClass.getConstructor(List.class).newInstance(elements);
} catch (ReflectiveOperationException e) {
throw new UnsupportedOperationException(e);
Expand All @@ -640,24 +651,55 @@ private static <T extends Type> T decodeArrayElements(

try {
Class<T> cls = Utils.getParameterizedTypeFromArray(typeReference);
if (Array.class.isAssignableFrom(cls)) {
throw new UnsupportedOperationException(
"Arrays of arrays are not currently supported for external functions, see"
+ "http://solidity.readthedocs.io/en/develop/types.html#members");
} else {
if (StructType.class.isAssignableFrom(cls)) {
List<T> elements = new ArrayList<>(length);

for (int i = 0, currOffset = offset;
i < length;
i++,
currOffset +=
getSingleElementLength(input, currOffset, cls)
* MAX_BYTE_LENGTH_FOR_HEX_STRING) {
T value = decode(input, currOffset, cls);
T value;
if (DynamicStruct.class.isAssignableFrom(cls)) {
value =
TypeDecoder.decodeDynamicStruct(
input,
offset + getDataOffset(input, currOffset, typeReference),
TypeReference.create(cls));
} else {
value =
TypeDecoder.decodeStaticStruct(
input, currOffset, TypeReference.create(cls));
}
elements.add(value);
}

String typeName = getSimpleTypeName(cls);

return consumer.apply(elements, typeName);
} else if (Array.class.isAssignableFrom(cls)) {
throw new UnsupportedOperationException(
"Arrays of arrays are not currently supported for external functions, see"
+ "http://solidity.readthedocs.io/en/develop/types.html#members");
} else {
List<T> elements = new ArrayList<>(length);
int currOffset = offset;
for (int i = 0; i < length; i++) {
T value;
if (isDynamic(cls)) {
int hexStringDataOffset = getDataOffset(input, currOffset, typeReference);
value = decode(input, offset + hexStringDataOffset, cls);
currOffset += MAX_BYTE_LENGTH_FOR_HEX_STRING;
} else {
value = decode(input, currOffset, cls);
currOffset +=
getSingleElementLength(input, currOffset, cls)
* MAX_BYTE_LENGTH_FOR_HEX_STRING;
}
elements.add(value);
}

String typeName = Utils.getSimpleTypeName(cls);
String typeName = getSimpleTypeName(cls);

return consumer.apply(elements, typeName);
}
Expand Down
Loading