Skip to content

Instantly share code, notes, and snippets.

@3rdstage
Created April 21, 2020 05:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 3rdstage/87b67eccd9b9e2441f863a9eb1d0dac0 to your computer and use it in GitHub Desktop.
Save 3rdstage/87b67eccd9b9e2441f863a9eb1d0dac0 to your computer and use it in GitHub Desktop.
Solidity Function Builder class prototype - builder pattern and method chaining style applied
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