Created
April 21, 2020 05:30
-
-
Save 3rdstage/87b67eccd9b9e2441f863a9eb1d0dac0 to your computer and use it in GitHub Desktop.
Solidity Function Builder class prototype - builder pattern and method chaining style applied
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.math.BigInteger; | |
import java.util.ArrayList; | |
import java.util.Collections; | |
import java.util.List; | |
import javax.annotation.Nonnull; | |
import javax.annotation.Nullable; | |
import javax.annotation.concurrent.NotThreadSafe; | |
import javax.validation.constraints.Max; | |
import javax.validation.constraints.Min; | |
import javax.validation.constraints.NotBlank; | |
import javax.validation.constraints.Pattern; | |
import javax.validation.constraints.PositiveOrZero; | |
import org.web3j.abi.FunctionEncoder; | |
import org.web3j.abi.TypeReference; | |
import org.web3j.abi.datatypes.Address; | |
import org.web3j.abi.datatypes.Bool; | |
import org.web3j.abi.datatypes.DynamicBytes; | |
import org.web3j.abi.datatypes.Function; | |
import org.web3j.abi.datatypes.Int; | |
import org.web3j.abi.datatypes.Type; | |
import org.web3j.abi.datatypes.Uint; | |
import org.web3j.abi.datatypes.Utf8String; | |
import org.web3j.abi.datatypes.primitive.Byte; | |
/** | |
* | |
* Note that all Solidity type names are lower case only. For all the types | |
* refer <a href= | |
* 'https://solidity.readthedocs.io/en/v0.5.3/abi-spec.html#types'>contract ABI | |
* specification</a> | |
* | |
* Note that this class is <b>NOT</b> thread-safe and so is expected to be | |
* instanciated inside function. | |
* | |
* @author Sangmoon Oh | |
* | |
* @see <a href='https://solidity.readthedocs.io/en/v0.5.15/abi-spec.html#types'>Contract ABI Specification / Types</a> | |
*/ | |
@NotThreadSafe | |
public class FunctionBuilder{ | |
// Referenced materials | |
// - https://docs.web3j.io/transactions/#transacting-with-a-smart-contract | |
// - | |
// https://docs.web3j.io/transactions/#querying-the-state-of-a-smart-contract | |
// -https://kauri.io/interacting-with-an-ethereum-smart-contract-in-jav/14dc434d11ef4ee18bf7d57f079e246e/a | |
private String name = null; | |
final private List<Type> inputParams = new ArrayList<>(); | |
final private List<TypeReference<?>> outputParamTypes = new ArrayList<>(); | |
/** | |
* Set the name of function to call | |
* | |
* @param name | |
* the function name | |
* @return current builder instance | |
* @throws IllegalArgumentException | |
* when {@code name} is {@code null}, empty or blank | |
*/ | |
public FunctionBuilder setName(@NotBlank final String name){ | |
if(name == null || name.trim().length() == 0){ | |
throw new IllegalArgumentException("The function name is not expected to be null, empty or blank."); | |
} | |
this.name = name; | |
return this; | |
} | |
/** | |
* Build {@code Function} instance using current name, input parameters and | |
* output parameter types. | |
* | |
* @return {@code Function} instance | |
* @throws IllegalStateException | |
* when function name is not specified. | |
* | |
* @see org.web3j.abi.datatypes.Function | |
*/ | |
public Function build(){ | |
final String name = this.name; | |
if(name == null || name.trim().length() == 0){ | |
throw new IllegalArgumentException("Function name should be specified."); | |
} | |
final List<Type> input = | |
(this.inputParams.size() == 0) ? Collections.emptyList(): this.inputParams; | |
final List<TypeReference<?>> outputTypes = | |
(this.outputParamTypes.size() == 0) ? Collections.emptyList(): this.outputParamTypes; | |
return new Function(this.name, input, outputTypes); | |
} | |
/** | |
* Returns ABI encoded function using current function name, input parameters | |
* and output parameter types | |
* | |
* @return ABI encoded function | |
* @throws IllegalStateException | |
* when function name is not specified. | |
* | |
* @see org.web3j.abi.FunctionEncoder#encode(Function) | |
*/ | |
public String buildAndEncode(){ | |
return FunctionEncoder.encode(this.build()); | |
} | |
// Methods for input parameters | |
/** | |
* Append {@code uint<M>} ABI type input parameter. | |
* | |
* Bit size(M) is supposed be specified as a 1st parameter. | |
* | |
* @param n | |
* bit size of the type: should be multiples of 8 between 1 and 256 | |
* @param value | |
* the value for the parameter which should be in proper range | |
* according to the bit size. | |
* @return current builder instance | |
* @throws IllegalArgumentException | |
* | |
* @see org.web3j.abi.datatypes.generated | |
* @see #addUintInputParam(int, long) | |
*/ | |
public FunctionBuilder addUintInputParam(@Min(8) @Max(256) final int n, | |
@Nonnull @PositiveOrZero final BigInteger value){ | |
if(n < 1 || n > 256 || n % 8 != 0){ | |
throw new IllegalArgumentException("The bit size('n') should be one of 8, 16, 24, 32, ..., 256 (multiple of 8 between 1 and 256)"); | |
} | |
try{ | |
final Uint param = Class.forName("org.web3j.abi.datatypes.generated.Uint" + n) | |
.asSubclass(Uint.class) | |
.getConstructor(BigInteger.class) | |
.newInstance(value); | |
this.inputParams.add(param); | |
return this; | |
} catch(RuntimeException ex){ | |
throw ex; | |
} catch(Throwable ex){ | |
throw new RuntimeException(ex); | |
} | |
} | |
/** | |
* @see #addUintInputParam(int, BigInteger) | |
*/ | |
public FunctionBuilder addUintInputParam(@Min(8) @Max(256) final int n, | |
@PositiveOrZero final long value){ | |
return this.addUintInputParam(n, BigInteger.valueOf(value)); | |
} | |
/** | |
* Append {@code uint} ABI type (which is synonyms for {@code uint256}) input parameter | |
* | |
* @param value | |
* @return current builder instance | |
* @throws IllegalArgumentException | |
* | |
* @see org.web3j.abi.datatypes.Uint | |
* @see #addUintInputParam(long) | |
*/ | |
public FunctionBuilder addUintInputParam(@Nonnull @PositiveOrZero final BigInteger value){ | |
if(value == null || value.compareTo(BigInteger.ZERO) < 0){ | |
throw new IllegalArgumentException("The value should be equal to or larger than zero."); | |
} | |
// @TODO Uint256 or Uint ? | |
this.inputParams.add(new Uint(value)); | |
return this; | |
} | |
/** | |
* Append {@code uint} ABI type (which is synonyms for {@code uint256}) input parameter | |
* | |
* @param value | |
* @return current builder instance | |
* @throws IllegalArgumentException | |
* | |
* @see org.web3j.abi.datatypes.Uint | |
* @see #addUintInputParam(BigInteger) | |
*/ | |
public FunctionBuilder addUintInputParam(@PositiveOrZero final long value){ | |
return this.addUintInputParam(BigInteger.valueOf(value)); | |
} | |
/** | |
* Append {@code int<M>} ABI type input parameter. | |
* | |
* Bit size({@code M}) is supposed be specified as a 1st parameter. | |
* | |
* @param n | |
* bit size of the type: should be multiples of 8 between 1 and 256 | |
* @param value | |
* the value for the parameter which should be in proper range | |
* according to the bit size. | |
* @return current builder instance | |
* @throws IllegalArgumentException | |
* | |
* @see org.web3j.abi.datatypes.generated | |
* @see #addIntInputParam(int, long) | |
*/ | |
public FunctionBuilder addIntInputParam(@Min(8) @Max(256) final int n, | |
@Nonnull final BigInteger value){ | |
if(n < 1 || n > 256 || n % 8 != 0){ | |
throw new IllegalArgumentException("The bit size('n') should be one of 8, 16, 24, 32, ..., 256 (multiple of 8 between 1 and 256)"); | |
} | |
try{ | |
final Int param = Class.forName("org.web3j.abi.datatypes.generated.Int" + n) | |
.asSubclass(Int.class) | |
.getConstructor(BigInteger.class) | |
.newInstance(value); | |
this.inputParams.add(param); | |
return this; | |
} catch(RuntimeException ex){ | |
throw ex; | |
} catch(Throwable ex){ | |
throw new RuntimeException(ex); | |
} | |
} | |
/** | |
* @see #addIntInputParam(int, BigInteger) | |
*/ | |
public FunctionBuilder addIntInputParam(@Min(8) @Max(256) final int n, final long value){ | |
return this.addIntInputParam(n, BigInteger.valueOf(value)); | |
} | |
/** | |
* Append {@code int} ABI type (which is synonyms for {@code int256}) input parameter | |
* | |
* @param value | |
* @return current builder instance | |
* | |
* @see #addInputParam(long) | |
* @see org.web3j.abi.datatypes.Int | |
*/ | |
public FunctionBuilder addIntInputParam(@Nonnull final BigInteger value){ | |
this.inputParams.add(new Int(value)); | |
return this; | |
} | |
/** | |
* Append {@code int} ABI type (which is synonyms for {@code int256}) input parameter | |
* | |
* @param value | |
* @return current builder instance | |
* | |
* @see #addIntInputParam(BigInteger) | |
* @see org.web3j.abi.datatypes.Int | |
*/ | |
public FunctionBuilder addIntInputParam(final long value){ | |
return this.addIntInputParam(BigInteger.valueOf(value)); | |
} | |
/** | |
* Append {@code address} ABI type input parameter. | |
* | |
* The {@code value} is expected to be hex string that starts with prefix '0x'. | |
* | |
* @param value | |
* the hex string starting with '0x' for the address | |
* @return current builder instance | |
* @thows IllegalArgumentException when {@code value} doesn't start with '0x' | |
* or doesn't have length between 3 and 42 | |
* | |
* @see org.web3j.abi.datatypes.Address | |
*/ | |
public FunctionBuilder addAddressInputParam(@NotBlank @Pattern(regexp = "0x[0-9a-fA-F]{1,40}") final String value){ | |
if(value == null || !value.startsWith("0x")){ | |
throw new IllegalArgumentException("The address value is expected to start with '0x'."); | |
} | |
// @TODO more validations | |
this.inputParams.add(new Address(value)); | |
return this; | |
} | |
/** | |
* Append {@code bool} ABI type input parameter | |
* | |
* @param value | |
* @return current builder instance | |
* | |
* @see org.web3j.abi.datatypes.Bool | |
*/ | |
public FunctionBuilder addBoolInputParam(final boolean value){ | |
this.inputParams.add(new Bool(value)); | |
return this; | |
} | |
/** | |
* Append {@code byte} Solidity type (which is synonym for {@code bytes1}) input parameter | |
* | |
* @param value | |
* @return current builder instance | |
* | |
* @see org.web3j.abi.datatypes.primitive.Byte | |
*/ | |
public FunctionBuilder addByteInputParam(final byte value){ | |
this.inputParams.add(new Byte(value)); | |
return this; | |
} | |
/** | |
* Append {@code bytes<M>} ABI type (fixed-sized byte array) input parameter. | |
* | |
* @param n | |
* @param value | |
* @return current builder instance | |
* | |
* @see org.web3j.abi.datatypes.generated | |
*/ | |
public FunctionBuilder addBytesInputParam(@Min(1) @Max(32) final int n, final byte[] value) { | |
//@TODO | |
return this; | |
} | |
/** | |
* Append {@code bytes} ABI type (dynamic sized byte sequence) input parameter | |
* | |
* @param value | |
* {@code byte[]} Java type value for the parameter | |
* @return current builder instance | |
* | |
* @see org.web3j.abi.datatypes.DynamicBytes | |
*/ | |
public FunctionBuilder addBytesInputParam(@Nullable final byte[] value){ | |
this.inputParams.add(new DynamicBytes(value)); | |
return this; | |
} | |
/** | |
* Append {@code string} ABI type input parameter. | |
* | |
* @param value | |
* parameter value, which can be {@code null} | |
* @return current builder instance | |
* | |
* @see org.web3j.abi.datatypes.Utf8String | |
*/ | |
public FunctionBuilder addStringInputParam(@Nullable final String value){ | |
this.inputParams.add(new Utf8String(value)); | |
return this; | |
} | |
// @TODO Methods for array type input parameters | |
// Methods for output parameter types | |
/** | |
* Append {@code uint<M>} ABI type output parameter | |
* | |
* @param n bit size of the type: should be multiples of 8 between 1 and 256 | |
* @return current builder instance | |
* @throws IllegalArgumentException | |
* | |
*/ | |
public FunctionBuilder addUintOutputParamType(@Min(8) @Max(256) final int n){ | |
if(n < 1 || n > 256 || n % 8 != 0){ | |
throw new IllegalArgumentException("The bit size('n') should be one of 8, 16, 24, 32, ..., 256 (multiple of 8 between 1 and 256)"); | |
} | |
try{ | |
final Class<? extends Uint> clazz = Class.forName("org.web3j.abi.datatypes.generated.Uint" + n).asSubclass(Uint.class); | |
this.outputParamTypes.add(TypeReference.create(clazz)); | |
return this; | |
} catch(RuntimeException ex){ | |
throw ex; | |
} catch(Throwable ex){ | |
throw new RuntimeException(ex); | |
} | |
} | |
/** | |
* Append {@code uint} ABI type output parameter. | |
* | |
* @return | |
*/ | |
public FunctionBuilder addUintOutputParamType(){ | |
this.outputParamTypes.add(new TypeReference<Uint>(){}); | |
return this; | |
} | |
/** | |
* Append {@code int<M>} ABI type output parameter. | |
* | |
* @param n bit size of the type: should be multiples of 8 between 1 and 256 | |
* @return | |
* @throws IllegalArgumentException | |
*/ | |
public FunctionBuilder addIntOutputParamType(@Min(8) @Max(256) final int n){ | |
if(n < 1 || n > 256 || n % 8 != 0){ | |
throw new IllegalArgumentException("The bit size('n') should be one of 8, 16, 24, 32, ..., 256 (multiple of 8 between 1 and 256)"); | |
} | |
try{ | |
final Class<? extends Int> clazz = Class.forName("org.web3j.abi.datatypes.generated.Int" + n).asSubclass(Int.class); | |
this.outputParamTypes.add(TypeReference.create(clazz)); | |
return this; | |
} catch(RuntimeException ex){ | |
throw ex; | |
} catch(Throwable ex){ | |
throw new RuntimeException(ex); | |
} | |
} | |
public FunctionBuilder addIntOutputParamType(){ | |
this.outputParamTypes.add(new TypeReference<Int>(){}); | |
return this; | |
} | |
public FunctionBuilder addAddressOutputParamType(){ | |
this.outputParamTypes.add(new TypeReference<Address>(){}); | |
return this; | |
} | |
public FunctionBuilder addBoolOutputParamType(){ | |
this.outputParamTypes.add(new TypeReference<Bool>(){}); | |
return this; | |
} | |
public FunctionBuilder addByteOutputParamType(){ | |
this.outputParamTypes.add(new TypeReference<Byte>(){}); | |
return this; | |
} | |
public FunctionBuilder addBytesOutputParamType(@Min(1) @Max(32) final int n){ | |
// @TODO | |
return this; | |
} | |
public FunctionBuilder addBytesOutputParamType(){ | |
this.outputParamTypes.add(new TypeReference<DynamicBytes>(){}); | |
return this; | |
} | |
public FunctionBuilder addStringOutputParamType(){ | |
this.outputParamTypes.add(new TypeReference<Utf8String>(){}); | |
return this; | |
} | |
// @TODO Methods for array type output parameters | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment