Skip to content

Commit

Permalink
replaces #922 (#997)
Browse files Browse the repository at this point in the history
* added makeTypeReference(), instantiateType(), encodeFunction(), tests

* style formatting cleanup

* multi-dimensional array test

* instantiateType("address", value) accepts BigInteger and Uint160

* formatting

* removed try/catch from tests, formatting changes, encodeFunction renamed makeFunction

* removed extra newline

* SolidityFunctionWrapper uses makeTypeReference(), makeTypeReference returns StaticArray.class when array size is greater than max

* trivial log line improvement

* PR minor changes

* rewrote makeTypeReference without recursion

* test for 3D array support, introduced getSubTypeReference for Array subtype, refactor using getSubTypeReference

* spotless
  • Loading branch information
iikirilov committed Aug 18, 2019
1 parent e6b58d6 commit 75bd317
Show file tree
Hide file tree
Showing 9 changed files with 476 additions and 109 deletions.
25 changes: 25 additions & 0 deletions abi/src/main/java/org/web3j/abi/FunctionEncoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@
*/
package org.web3j.abi;

import java.lang.reflect.InvocationTargetException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;

Expand All @@ -23,6 +26,9 @@
import org.web3j.crypto.Hash;
import org.web3j.utils.Numeric;

import static org.web3j.abi.TypeDecoder.instantiateType;
import static org.web3j.abi.TypeReference.makeTypeReference;

/**
* Ethereum Contract Application Binary Interface (ABI) encoding for functions. Further details are
* available <a href="https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI">here</a>.
Expand All @@ -47,6 +53,25 @@ public static String encodeConstructor(List<Type> parameters) {
return encodeParameters(parameters, new StringBuilder());
}

public static Function makeFunction(
String fnname,
List<String> solidityInputTypes,
List<Object> arguments,
List<String> solidityOutputTypes)
throws ClassNotFoundException, NoSuchMethodException, InstantiationException,
IllegalAccessException, InvocationTargetException {
List<Type> encodedInput = new ArrayList<>();
Iterator argit = arguments.iterator();
for (String st : solidityInputTypes) {
encodedInput.add(instantiateType(st, argit.next()));
}
List<TypeReference<?>> encodedOutput = new ArrayList<>();
for (String st : solidityOutputTypes) {
encodedOutput.add(makeTypeReference(st));
}
return new Function(fnname, encodedInput, encodedOutput);
}

private static String encodeParameters(List<Type> parameters, StringBuilder result) {
int dynamicDataOffset = getLength(parameters) * Type.MAX_BYTE_LENGTH;
StringBuilder dynamicData = new StringBuilder();
Expand Down
197 changes: 160 additions & 37 deletions abi/src/main/java/org/web3j/abi/TypeDecoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
*/
package org.web3j.abi;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
Expand All @@ -24,6 +26,7 @@
import org.web3j.abi.datatypes.Array;
import org.web3j.abi.datatypes.Bool;
import org.web3j.abi.datatypes.Bytes;
import org.web3j.abi.datatypes.BytesType;
import org.web3j.abi.datatypes.DynamicArray;
import org.web3j.abi.datatypes.DynamicBytes;
import org.web3j.abi.datatypes.Fixed;
Expand All @@ -39,6 +42,8 @@
import org.web3j.abi.datatypes.generated.Uint160;
import org.web3j.utils.Numeric;

import static org.web3j.abi.TypeReference.makeTypeReference;

/**
* Ethereum Contract Application Binary Interface (ABI) decoding for types. Decoding is not
* documented, but is the reverse of the encoding details located <a
Expand All @@ -48,15 +53,34 @@ public class TypeDecoder {

static final int MAX_BYTE_LENGTH_FOR_HEX_STRING = Type.MAX_BYTE_LENGTH << 1;

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;
public static Type instantiateType(String solidityType, Object value)
throws InvocationTargetException, NoSuchMethodException, InstantiationException,
IllegalAccessException, ClassNotFoundException {
return instantiateType(makeTypeReference(solidityType), value);
}

public static Type instantiateType(TypeReference ref, Object value)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
InstantiationException, ClassNotFoundException {
Class rc = ref.getClassType();
if (Array.class.isAssignableFrom(rc)) {
return instantiateArrayType(ref, value);
}
return instantiateAtomicType(rc, value);
}

public static <T extends Array> T decode(
String input, int offset, TypeReference<T> typeReference) {
Class cls = ((ParameterizedType) typeReference.getType()).getRawType().getClass();
if (StaticArray.class.isAssignableFrom(cls)) {
return decodeStaticArray(input, offset, typeReference, 1);
} else if (DynamicArray.class.isAssignableFrom(cls)) {
return decodeDynamicArray(input, offset, typeReference);
} else {
return 1;
throw new UnsupportedOperationException(
"Unsupported TypeReference: "
+ cls.getName()
+ ", only Array types can be passed as TypeReferences");
}
}

Expand All @@ -82,21 +106,6 @@ static <T extends Type> T decode(String input, int offset, Class<T> type) {
}
}

public static <T extends Array> T decode(
String input, int offset, TypeReference<T> typeReference) {
Class cls = ((ParameterizedType) typeReference.getType()).getRawType().getClass();
if (StaticArray.class.isAssignableFrom(cls)) {
return decodeStaticArray(input, offset, typeReference, 1);
} else if (DynamicArray.class.isAssignableFrom(cls)) {
return decodeDynamicArray(input, offset, typeReference);
} else {
throw new UnsupportedOperationException(
"Unsupported TypeReference: "
+ cls.getName()
+ ", only Array types can be passed as TypeReferences");
}
}

static <T extends Type> T decode(String input, Class<T> type) {
return decode(input, 0, type);
}
Expand Down Expand Up @@ -156,6 +165,96 @@ static <T extends NumericType> int getTypeLength(Class<T> type) {
return Type.MAX_BIT_LENGTH;
}

static Type instantiateArrayType(TypeReference ref, Object value)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
InstantiationException, ClassNotFoundException {
List values;
if (value instanceof List) {
values = (List) value;
} else if (value.getClass().isArray()) {
values = arrayToList(value);
} else {
throw new ClassCastException(
"Arg of type "
+ value.getClass()
+ " should be a list to instantiate web3j Array");
}
Constructor listcons;
int arraySize =
ref instanceof TypeReference.StaticArrayTypeReference
? ((TypeReference.StaticArrayTypeReference) ref).getSize()
: -1;
if (arraySize <= 0) {
listcons = DynamicArray.class.getConstructor(new Class[] {Class.class, List.class});
} else {
Class arrayClass =
Class.forName("org.web3j.abi.datatypes.generated.StaticArray" + arraySize);
listcons = arrayClass.getConstructor(new Class[] {Class.class, List.class});
}
// create a list of arguments coerced to the correct type of sub-TypeReference
ArrayList transformedList = new ArrayList(values.size());
TypeReference subTypeReference = ref.getSubTypeReference();
for (Object o : values) {
transformedList.add(instantiateType(subTypeReference, o));
}
return (Type) listcons.newInstance(ref.getClassType(), transformedList);
}

static Type instantiateAtomicType(Class referenceClass, Object value)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
InstantiationException, ClassNotFoundException {
Object constructorArg = null;
if (NumericType.class.isAssignableFrom(referenceClass)) {
constructorArg = asBigInteger(value);
} else if (BytesType.class.isAssignableFrom(referenceClass)) {
if (value instanceof byte[]) {
constructorArg = value;
} else if (value instanceof BigInteger) {
constructorArg = ((BigInteger) value).toByteArray();
} else if (value instanceof String) {
constructorArg = Numeric.hexStringToByteArray((String) value);
}
} else if (Utf8String.class.isAssignableFrom(referenceClass)) {
constructorArg = value.toString();
} else if (Address.class.isAssignableFrom(referenceClass)) {
if (value instanceof BigInteger || value instanceof Uint160) {
constructorArg = value;
} else {
constructorArg = value.toString();
}
} else if (Bool.class.isAssignableFrom(referenceClass)) {
if (value instanceof Boolean) {
constructorArg = value;
} else {
BigInteger bival = asBigInteger(value);
constructorArg = bival == null ? null : !bival.equals(BigInteger.ZERO);
}
}
if (constructorArg == null) {
throw new InstantiationException(
"Could not create type "
+ referenceClass
+ " from arg "
+ value.toString()
+ " of type "
+ value.getClass());
}
Constructor cons = referenceClass.getConstructor(new Class[] {constructorArg.getClass()});
return (Type) cons.newInstance(constructorArg);
}

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 {
return 1;
}
}

static int decodeUintAsInt(String rawInput, int offset) {
String input = rawInput.substring(offset, offset + MAX_BYTE_LENGTH_FOR_HEX_STRING);
return decode(input, 0, Uint.class).getValue().intValue();
Expand Down Expand Up @@ -230,20 +329,6 @@ static <T extends Type> T decodeStaticArray(
return decodeArrayElements(input, offset, typeReference, length, function);
}

@SuppressWarnings("unchecked")
private static <T extends Type> T instantiateStaticArray(
TypeReference<T> typeReference, 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);
}
}

@SuppressWarnings("unchecked")
static <T extends Type> T decodeDynamicArray(
String input, int offset, TypeReference<T> typeReference) {
Expand All @@ -264,6 +349,44 @@ static <T extends Type> T decodeDynamicArray(
return decodeArrayElements(input, valueOffset, typeReference, length, function);
}

static BigInteger asBigInteger(Object arg) {
if (arg instanceof BigInteger) {
return (BigInteger) arg;
} else if (arg instanceof BigDecimal) {
return ((BigDecimal) arg).toBigInteger();
} else if (arg instanceof String) {
return Numeric.toBigInt((String) arg);
} else if (arg instanceof byte[]) {
return Numeric.toBigInt((byte[]) arg);
} else if (arg instanceof Number) {
return BigInteger.valueOf(((Number) arg).longValue());
}
return null;
}

static List arrayToList(Object array) {
int len = java.lang.reflect.Array.getLength(array);
ArrayList rslt = new ArrayList(len);
for (int i = 0; i < len; i++) {
rslt.add(java.lang.reflect.Array.get(array, i));
}
return rslt;
}

@SuppressWarnings("unchecked")
private static <T extends Type> T instantiateStaticArray(
TypeReference<T> typeReference, 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);
}
}

private static <T extends Type> T decodeArrayElements(
String input,
int offset,
Expand Down
Loading

0 comments on commit 75bd317

Please sign in to comment.