Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.klaytn.caver.contract.ContractMethod Maven / Gradle / Ivy
/*
* Copyright 2020 The caver-java Authors
*
* Licensed under the Apache License, Version 2.0 (the “License”);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an “AS IS” BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.klaytn.caver.contract;
import com.klaytn.caver.Caver;
import com.klaytn.caver.abi.ABI;
import com.klaytn.caver.abi.datatypes.Type;
import com.klaytn.caver.methods.request.CallObject;
import com.klaytn.caver.methods.response.Bytes;
import com.klaytn.caver.methods.response.Bytes32;
import com.klaytn.caver.methods.response.Quantity;
import com.klaytn.caver.methods.response.TransactionReceipt;
import com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction;
import com.klaytn.caver.transaction.AbstractTransaction;
import com.klaytn.caver.transaction.TxPropertyBuilder;
import com.klaytn.caver.transaction.response.PollingTransactionReceiptProcessor;
import com.klaytn.caver.transaction.response.TransactionReceiptProcessor;
import com.klaytn.caver.utils.Utils;
import com.klaytn.caver.wallet.IWallet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.web3j.protocol.exceptions.TransactionException;
import org.web3j.utils.Numeric;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
/**
* Representing a Contract's method information.
*/
public class ContractMethod {
static final String TYPE_FUNCTION = "function";
static final String TYPE_CONSTRUCTOR = "constructor";
/**
* A caver instance.
*/
Caver caver;
/**
* The input type. It may set "function" or "constructor".
*/
String type;
/**
* The function name.
*/
String name;
/**
* The list of ContractIOType contains to function parameter information.
*/
List inputs;
/**
* The list of ContractIOType contains to function return value information.
*/
List outputs;
/**
* The function signature string. ex) foo(uint256,uin256)
*/
String signature;
/**
* The contract address.
*/
String contractAddress;
/**
* The default send option. When you execute call() or send() without SendOptions, defaultSendOptions will be used.
*/
SendOptions defaultSendOptions;
/**
* The class instance implemented IWallet interface to sign transaction.
*/
IWallet wallet;
List nextContractMethods = new ArrayList<>();
private static final Logger LOGGER = LoggerFactory.getLogger(ContractMethod.class);
/**
* Creates a ContractMethod instance.
*/
public ContractMethod() {
}
/**
* Creates a ContractMethod instance.
* @param caver A Caver instance.
* @param type The input type. It always set "function"
* @param name The function name
* @param inputs The list of ContractIOType contains to function parameter information.
* @param outputs The list of ContractIOType
* @param signature The function signature string.
* @param contractAddress The contract address
*/
public ContractMethod(Caver caver, String type, String name, List inputs, List outputs, String signature, String contractAddress) {
this.caver = caver;
this.type = type;
this.name = name;
this.inputs = inputs;
this.outputs = outputs;
this.signature = signature;
this.contractAddress = contractAddress;
}
/**
* Execute smart contract method in the EVM without sending any transaction.
* @param arguments A List of parameter to call smart contract method.
* @return List
* @throws IOException
* @throws ClassNotFoundException
* @throws NoSuchMethodException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws InvocationTargetException
*/
public List call(List arguments) throws IOException, ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
return call(arguments, CallObject.createCallObject());
}
/**
* Execute smart contract method in the EVM without sending any transaction.
* When creating CallObject, it need not to fill 'data', 'to' fields.
* The 'data', 'to' fields automatically filled in call() method.
* @param arguments A List of parameter to call smart contract method.
* @param callObject A CallObject instance to 'call' smart contract method.
* @return List
* @throws IOException
* @throws ClassNotFoundException
* @throws NoSuchMethodException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws InvocationTargetException
*/
public List call(List arguments, CallObject callObject) throws IOException, ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
List functionParams = new ArrayList<>();
if(arguments != null) {
functionParams.addAll(arguments);
}
ContractMethod matchedMethod = findMatchedInstance(functionParams);
String encodedFunction = ABI.encodeFunctionCall(matchedMethod, functionParams);
return callFunction(matchedMethod, encodedFunction, callObject);
}
/**
* Send a transaction to deploy smart contract or execute smart contract's method.
*
* If the 'type' field is a "constructor", the arguments parsed as follow.
* - arguments[0] : Smart contract's bytecode.
* - others : The constructor arguments to deploy smart contract.
*
* Caver caver = new Caver(Caver.DEFAULT_URL);
* String abi = "abi";
* String bytecode = "Contract bytecode";
*
* String sender = "0x{sender address}";
* Contract contract = caver.contract.create(abi);
*
* SendOptions sendOptions = new SendOptions();
* sendOptions.setFrom("0x{from}");
* sendOptions.setGas(BigInteger.valueOf(100000000));
* contract.setDefaultSendOptions(sendOptions);
*
* TransactionReceipt.TransactionReceiptData deployed = contract.getMethod("constructor").send(Arrays.asList(bytecode, constructor_param1, constructor_param2...));
*
* It is used defaultSendOption field to sendOptions.
* It sets TransactionReceiptProcessor to PollingTransactionReceiptProcessor.
*
* @param arguments A List of parameter to call smart contract method.
* @return TransactionReceiptData
* @throws IOException
* @throws TransactionException
* @throws ClassNotFoundException
* @throws NoSuchMethodException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws InvocationTargetException
*/
public TransactionReceipt.TransactionReceiptData send(List arguments) throws IOException, TransactionException, ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
return send(arguments, null, new PollingTransactionReceiptProcessor(caver, 1000, 15));
}
/**
* Send a transaction to deploy smart contract or execute smart contract's method.
*
* If the 'type' field is a "constructor", the arguments parsed as follow.
* - arguments[0] : Smart contract's bytecode.
* - others : The constructor arguments to deploy smart contract.
*
* Caver caver = new Caver(Caver.DEFAULT_URL);
* String abi = "abi";
* String bytecode = "Contract bytecode";
*
* String sender = "0x{sender address}";
* Contract contract = caver.contract.create(abi);
*
* SendOptions sendOptions = new SendOptions();
* sendOptions.setFrom("0x{from}");
* sendOptions.setGas(BigInteger.valueOf(100000000));
*
* TransactionReceipt.TransactionReceiptData deployed = contract.getMethod("constructor").send(Arrays.asList(bytecode, constructor_param1, constructor_param2...), sendOptions);
*
* It sets TransactionReceiptProcessor to PollingTransactionReceiptProcessor.
*
* @param arguments A List of parameter to call smart contract method.
* @param options An option to deploy or execute smart contract method.
* @return TransactionReceiptData
* @throws IOException
* @throws TransactionException
* @throws ClassNotFoundException
* @throws NoSuchMethodException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws InvocationTargetException
*/
public TransactionReceipt.TransactionReceiptData send(List arguments, SendOptions options) throws IOException, TransactionException, ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
return send(arguments, options, new PollingTransactionReceiptProcessor(caver, 1000, 15));
}
/**
* Send a transaction to deploy smart contract or execute smart contract's method.
*
* If the 'type' field is a "constructor", the arguments parsed as follow.
* - arguments[0] : Smart contract's bytecode.
* - others : The constructor arguments to deploy smart contract.
*
* Caver caver = new Caver(Caver.DEFAULT_URL);
* String abi = "abi";
* String bytecode = "Contract bytecode";
*
* String sender = "0x{sender address}";
* Contract contract = caver.contract.create(abi);
*
* SendOptions sendOptions = new SendOptions();
* sendOptions.setFrom("0x{from}");
* sendOptions.setGas(BigInteger.valueOf(100000000));
*
* TransactionReceiptProcessor receiptProcessor = new PollingTransactionReceiptProcessor(caver, 1000, 15);
* TransactionReceipt.TransactionReceiptData deployed = contract.getMethod("constructor").send(Arrays.asList(bytecode, constructor_param1, constructor_param2...), sendOptions, receiptProcessor);
*
*
* @param arguments A List of parameter to call smart contract method.
* @param options An option to deploy or execute smart contract method.
* @param processor A TransactionReceiptProcessor to get receipt.
* @return TransactionReceiptData
* @throws IOException
* @throws TransactionException
* @throws ClassNotFoundException
* @throws NoSuchMethodException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws InvocationTargetException
*/
public TransactionReceipt.TransactionReceiptData send(List arguments, SendOptions options, TransactionReceiptProcessor processor) throws IOException, TransactionException, ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
SendOptions determinedOption = makeSendOption(options);
AbstractTransaction transaction = sign(arguments, determinedOption);
if(determinedOption.getFeeDelegation() != null && determinedOption.getFeeDelegation()) {
if(determinedOption.getFeePayer() == null || !Utils.isAddress(determinedOption.getFeePayer())) {
throw new IllegalArgumentException("The fee payer value is not valid. feePayer address - " + determinedOption.getFeePayer());
}
transaction = this.wallet.signAsFeePayer(determinedOption.getFeePayer(), (AbstractFeeDelegatedTransaction)transaction);
}
return sendTransaction(transaction, processor);
}
/**
* Create and sign a transaction with the input data generated by the passed argument.
*
* If the 'type' field is a "constructor", the arguments parsed as follow.
* - arguments[0] : Smart contract's bytecode.
* - others : The constructor arguments to deploy smart contract.
*
* Caver caver = new Caver(Caver.DEFAULT_URL);
*
* String abi = "abi";
* String bytecode = "Contract bytecode";
* String sender = "0x{sender address}";
*
* SendOptions sendOptions = new SendOptions();
* sendOptions.setFrom("0x{from}");
* sendOptions.setGas(BigInteger.valueOf(100000000));
*
* Contract contract = caver.contract.create(abi);
* contract.setDefaultSendOptions(sendOptions);
*
* TransactionReceipt.TransactionReceiptData deployed = contract.getMethod("constructor").sign(Arrays.asList(bytecode, constructor_param1, constructor_param2...));
*
* It is used defaultSendOption field to sendOptions.
*
* @param arguments The list of arguments to deploy or execute a smart contract.
* @return AbstractTransaction
* @throws ClassNotFoundException
* @throws InvocationTargetException
* @throws NoSuchMethodException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws IOException
*/
public AbstractTransaction sign(List arguments) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, IOException {
return sign(arguments, null);
}
/**
* Create and sign a transaction with the input data generated by the passed argument.
*
* If the 'type' field is a "constructor", the arguments parsed as follow.
* - arguments[0] : Smart contract's bytecode.
* - others : The constructor arguments to deploy smart contract.
*
* Caver caver = new Caver(Caver.DEFAULT_URL);
*
* String abi = "abi";
* String bytecode = "Contract bytecode";
* String sender = "0x{sender address}";
*
* SendOptions sendOptions = new SendOptions();
* sendOptions.setFrom("0x{from}");
* sendOptions.setGas(BigInteger.valueOf(100000000));
*
* Contract contract = caver.contract.create(abi);
* TransactionReceipt.TransactionReceiptData deployed = contract.getMethod("constructor").sign(Arrays.asList(bytecode, constructor_param1, constructor_param2...), sendOptions);
*
*
* @param arguments The list of arguments to deploy or execute a smart contract.
* @param sendOptions An option to deploy or execute smart contract method.
* @return AbstractTransaction
* @throws ClassNotFoundException
* @throws InvocationTargetException
* @throws NoSuchMethodException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws IOException
*/
public AbstractTransaction sign(List arguments, SendOptions sendOptions) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, IOException {
String encoded = encodeABI(arguments);
//Make SendOptions instance by comparing with defaultSendOption and passed parameter "options"
//Passed parameter "options" has higher priority than "defaultSendOption" field.
SendOptions determinedOption = makeSendOption(sendOptions);
AbstractTransaction transaction = createTransaction(determinedOption, encoded);
return this.wallet.sign(determinedOption.getFrom(), transaction);
}
/**
* Create and sign a transaction as a fee payer with the input data generated by the passed argument.
*
* If the 'type' field is a "constructor", the arguments parsed as follow.
* - arguments[0] : Smart contract's bytecode.
* - others : The constructor arguments to deploy smart contract.
*
* Caver caver = new Caver(Caver.DEFAULT_URL);
*
* String abi = "abi";
* String bytecode = "Contract bytecode";
* String sender = "0x{sender address}";
*
* SendOptions sendOptions = new SendOptions();
* sendOptions.setFrom("0x{from}");
* sendOptions.setGas(BigInteger.valueOf(100000000));
* sendOptions.setFeeDelegation(true);
* sendOptions.setFeePayer("0x{feePayer}");
*
* Contract contract = caver.contract.create(abi);
* contract.setDefaultSendOptions(sendOptions);
* AbstractTransaction transaction = contract.getMethod("constructor").signAsFeePayer(Arrays.asList(bytecode, constructor_param1, constructor_param2....));
*
* It is used defaultSendOption field to sendOptions.
*
* @param arguments The list of arguments to deploy or execute a smart contract.
* @return AbstractTransaction
* @throws ClassNotFoundException
* @throws InvocationTargetException
* @throws NoSuchMethodException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws IOException
*/
public AbstractFeeDelegatedTransaction signAsFeePayer(List arguments) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, IOException {
return signAsFeePayer(arguments, null);
}
/**
* Create and sign a transaction as a fee payer with the input data generated by the passed argument.
*
* If the 'type' field is a "constructor", the arguments parsed as follow.
* - arguments[0] : Smart contract's bytecode.
* - others : The constructor arguments to deploy smart contract.
*
* Caver caver = new Caver(Caver.DEFAULT_URL);
*
* String abi = "abi";
* String bytecode = "Contract bytecode";
* String sender = "0x{sender address}";
*
* SendOptions sendOptions = new SendOptions();
* sendOptions.setFrom("0x{from}");
* sendOptions.setGas(BigInteger.valueOf(100000000));
* sendOptions.setFeeDelegation(true);
* sendOptions.setFeePayer("0x{feePayer}");
*
* Contract contract = caver.contract.create(abi);
* AbstractTransaction transaction = contract.getMethod("constructor").signAsFeePayer(Arrays.asList(bytecode, constructor_param1, constructor_param2....), sendOptions);
*
*
* @param arguments The list of arguments to deploy or execute a smart contract.
* @param sendOptions An option to deploy or execute smart contract method.
* @return AbstractTransaction
* @throws ClassNotFoundException
* @throws InvocationTargetException
* @throws NoSuchMethodException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws IOException
*/
public AbstractFeeDelegatedTransaction signAsFeePayer(List arguments, SendOptions sendOptions) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, IOException {
// Make SendOptions instance by comparing with defaultSendOption and passed parameter "options"
// Passed parameter "options" has higher priority than "defaultSendOption" field.
SendOptions determinedOption = makeSendOption(sendOptions);
if(determinedOption == null || !determinedOption.getFeeDelegation()) {
throw new RuntimeException("'feeDelegation' field in SendOptions must set a true.");
}
checkSendOption(determinedOption);
String encoded = encodeABI(arguments);
AbstractFeeDelegatedTransaction transaction = (AbstractFeeDelegatedTransaction)createTransaction(determinedOption, encoded);
return this.wallet.signAsFeePayer(determinedOption.getFeePayer(), transaction);
}
/**
* Encodes the ABI for this method. It returns 32-bit function signature hash plus the encoded passed parameters.
* If the 'type' field is a "constructor", it encodes a constructor params to deploy.
*
* When the 'type' field is a "constructor", the arguments parsed as follow.
* - arguments[0] : Smart contract's bytecode.
* - others : The constructor arguments to deploy smart contract.
*
* @param arguments A List of parameter to encode function signature and parameters
* @return The encoded ABI byte code to send via a transaction or call.
* @throws ClassNotFoundException
* @throws NoSuchMethodException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws InvocationTargetException
*/
public String encodeABI(List arguments) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
List functionParams = new ArrayList<>();
if(arguments != null) {
functionParams.addAll(arguments);
}
if(getType().equals(TYPE_CONSTRUCTOR)) {
// If type is a "constructor",
// - argument[0] is smart contract's byte code,
// - and others are constructor parameter
String byteCode = (String)functionParams.get(0);
List constructParams = functionParams.subList(1, arguments.size());
return ABI.encodeContractDeploy(this, byteCode, constructParams);
} else {
ContractMethod matchedMethod = findMatchedInstance(functionParams);
return ABI.encodeFunctionCall(matchedMethod, arguments);
}
}
/**
* Estimate the gas to execute the contract's method.
* @param arguments A List of parameter that solidity wrapper type
* @param callObject An option to execute smart contract method.
* @return String
* @throws IOException
* @throws ClassNotFoundException
* @throws NoSuchMethodException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws InvocationTargetException
*/
public String estimateGas(List arguments, CallObject callObject) throws IOException, ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
String encodedFunctionCall = encodeABI(arguments);
return estimateGas(encodedFunctionCall, callObject);
}
/**
* Execute smart contract method in the EVM without sending any transaction.
* It is recommended to use this function when you want to execute one of the functions with the same number of parameters.
* @param arguments A List of parameter that solidity wrapper type to call smart contract method.
* @return List
* @throws IOException
* @throws ClassNotFoundException
*/
public List callWithSolidityWrapper(List arguments) throws IOException, ClassNotFoundException {
return callWithSolidityWrapper(arguments, CallObject.createCallObject());
}
/**
* Execute smart contract method in the EVM without sending any transaction.
* When creating CallObject, it need not to fill 'data', 'to' fields.
* The 'data', 'to' fields automatically filled in call() method.
* It is recommended to use this function when you want to execute one of the functions with the same number of parameters.
* @param arguments A List of parameter that solidity wrapper type to call smart contract method.
* @param callObject A CallObject instance to 'call' smart contract method.
* @return List
* @throws IOException
* @throws ClassNotFoundException
*/
public List callWithSolidityWrapper(List arguments, CallObject callObject) throws IOException, ClassNotFoundException {
List functionParams = new ArrayList<>();
if(arguments != null) {
functionParams.addAll(arguments);
}
ContractMethod matchedMethod = findMatchedInstanceWithSolidityWrapper(functionParams);
String encodedFunction = ABI.encodeFunctionCallWithSolidityWrapper(matchedMethod, functionParams);
return callFunction(matchedMethod, encodedFunction, callObject);
}
/**
* Send a transaction to smart contract and execute its method using solidity type wrapper class.
* It is used defaultSendOption field to sendOptions.
* It sets TransactionReceiptProcessor to PollingTransactionReceiptProcessor.
* It is recommended to use this function when you want to execute one of the functions with the same number of parameters.
* @param wrapperArguments A List of parameter that wrapped solidity wrapper class.
* @return TransactionReceiptData
* @throws IOException
* @throws TransactionException
*/
public TransactionReceipt.TransactionReceiptData sendWithSolidityWrapper(List wrapperArguments) throws IOException, TransactionException {
return sendWithSolidityWrapper(wrapperArguments, null, new PollingTransactionReceiptProcessor(caver, 1000, 15));
}
/**
* Send a transaction to smart contract and execute its method using solidity type wrapper class.
* It sets TransactionReceiptProcessor to PollingTransactionReceiptProcessor.
* It is recommended to use this function when you want to execute one of the functions with the same number of parameters.
* @param wrapperArguments A List of parameter that wrapped solidity wrapper class.
* @param options An option to execute smart contract method.
* @return TransactionReceiptData
* @throws IOException
* @throws TransactionException
*/
public TransactionReceipt.TransactionReceiptData sendWithSolidityWrapper(List wrapperArguments, SendOptions options) throws IOException, TransactionException {
return sendWithSolidityWrapper(wrapperArguments, options, new PollingTransactionReceiptProcessor(caver, 1000, 15));
}
/**
* Send a transaction to smart contract and execute its method using solidity type wrapper class.
* It is recommended to use this function when you want to execute one of the functions with the same number of parameters.
* @param wrapperArguments A List of parameter that wrapped solidity wrapper class.
* @param options An option to execute smart contract method.
* @param processor A TransactionReceiptProcessor to get receipt.
* @return TransactionReceiptData
* @throws IOException
* @throws TransactionException
*/
public TransactionReceipt.TransactionReceiptData sendWithSolidityWrapper(List wrapperArguments, SendOptions options, TransactionReceiptProcessor processor) throws IOException, TransactionException {
if(!getType().equals(TYPE_FUNCTION)) {
throw new RuntimeException("This method can be used only to encode function with passed argument.");
}
SendOptions determinedOption = makeSendOption(options);
AbstractTransaction transaction = signWithSolidityWrapper(wrapperArguments, determinedOption);
if((determinedOption.getFeeDelegation() != null && determinedOption.getFeeDelegation()) && determinedOption.getFeePayer() != null) {
transaction = this.wallet.signAsFeePayer(determinedOption.getFeePayer(), (AbstractFeeDelegatedTransaction)transaction);
}
return sendTransaction(transaction, processor);
}
/**
* Create and sign a transaction with the input data generated by the passed argument that wrapped by solidity type class.
* It creates a transaction related to SmartContractExecution to execute smart contract method.
* It is used defaultSendOption field to sendOptions.
* It is recommended to use this function when you want to execute one of the functions with the same number of parameters.
* @param wrapperArguments A List of parameter that wrapped by solidity type wrapper class.
* @return AbstractTransaction
* @throws IOException
*/
public AbstractTransaction signWithSolidityWrapper(List wrapperArguments) throws IOException {
return signWithSolidityWrapper(wrapperArguments, null);
}
/**
* Create and sign a transaction with the input data generated by the passed argument that wrapped by solidity type class.
* It creates a transaction related to SmartContractExecution to execute smart contract method.
* It is recommended to use this function when you want to execute one of the functions with the same number of parameters.
* @param wrapperArguments A List of parameter that wrapped by solidity type wrapper class.
* @param sendOptions An option to execute smart contract method.
* @return AbstractTransaction
* @throws IOException
*/
public AbstractTransaction signWithSolidityWrapper(List wrapperArguments, SendOptions sendOptions) throws IOException {
if(!getType().equals(TYPE_FUNCTION)) {
throw new RuntimeException("This method can be used only to encode function with passed argument.");
}
//Make SendOptions instance by comparing with defaultSendOption and passed parameter "options"
//Passed parameter "options" has higher priority than "defaultSendOption" field.
SendOptions determinedOption = makeSendOption(sendOptions);
checkSendOption(determinedOption);
String encoded = encodeABIWithSolidityWrapper(wrapperArguments);
AbstractTransaction transaction = createTransaction(determinedOption, encoded);
return this.wallet.sign(determinedOption.getFrom(), transaction);
}
/**
* Create and sign a transaction as a fee payer with the input data generated by the passed argument that wrapped by solidity type class.
* It creates a transaction related to FeeDelegatedSmartContractExecution to execute smart contract method.
* It is used defaultSendOption field to sendOptions.
* It is recommended to use this function when you want to execute one of the functions with the same number of parameters.
* @param wrapperArguments A List of parameter that wrapped by solidity type wrapper class.
* @return AbstractTransaction
* @throws IOException
*/
public AbstractFeeDelegatedTransaction signAsFeePayerWithSolidityWrapper(List wrapperArguments) throws IOException {
return signAsFeePayerWithSolidityWrapper(wrapperArguments, null);
}
/**
* Create and sign a transaction with the input data generated by the passed argument that wrapped by solidity type class.
* It creates a transaction related to FeeDelegatedSmartContractExecution to execute smart contract method.
* It is recommended to use this function when you want to execute one of the functions with the same number of parameters.
* @param wrapperArguments A List of parameter that wrapped by solidity type wrapper class.
* @param sendOptions An option to execute smart contract method.
* @return AbstractTransaction
* @throws IOException
*/
public AbstractFeeDelegatedTransaction signAsFeePayerWithSolidityWrapper(List wrapperArguments, SendOptions sendOptions) throws IOException {
if(!getType().equals(TYPE_FUNCTION)) {
throw new RuntimeException("This method can be used only to encode function with passed argument.");
}
// Make SendOptions instance by comparing with defaultSendOption and passed parameter "options"
// Passed parameter "options" has higher priority than "defaultSendOption" field.
SendOptions determinedOption = makeSendOption(sendOptions);
if(determinedOption == null || !determinedOption.getFeeDelegation()) {
throw new RuntimeException("'feeDelegation' field in SendOptions must set a true.");
}
checkSendOption(determinedOption);
String encoded = encodeABIWithSolidityWrapper(wrapperArguments);
AbstractFeeDelegatedTransaction transaction = (AbstractFeeDelegatedTransaction)createTransaction(determinedOption, encoded);
return this.wallet.signAsFeePayer(determinedOption.getFeePayer(), transaction);
}
/**
* Encodes the ABI for this method. It returns 32-bit function signature hash plus the encoded passed parameters.
* It is recommended to use this function when you want to execute one of the functions with the same number of parameters.
* @param wrapperArguments A List of parameter that solidity wrapper class
* @return The encoded ABI byte code to send via a transaction or call.
*/
public String encodeABIWithSolidityWrapper(List wrapperArguments) {
if(!getType().equals(TYPE_FUNCTION)) {
throw new RuntimeException("This method can be used only to encode function with passed argument.");
}
List functionParams = new ArrayList<>();
if(wrapperArguments != null) {
functionParams.addAll(wrapperArguments);
}
ContractMethod matchedMethod = this.findMatchedInstanceWithSolidityWrapper(functionParams);
return ABI.encodeFunctionCallWithSolidityWrapper(matchedMethod, functionParams);
}
/**
* Estimate the gas to execute the Contract's method using Solidity type wrapper class.
* It is recommended to use this function when you want to execute one of the functions with the same number of parameters.
* @param arguments The arguments that need to execute smart contract method.
* @param callObject An option to execute smart contract method.
* @return String
* @throws IOException
*/
public String estimateGasWithSolidityWrapper(List arguments, CallObject callObject) throws IOException {
String encodedFunctionCall = encodeABIWithSolidityWrapper(arguments);
return estimateGas(encodedFunctionCall, callObject);
}
/**
* Check that passed parameter is valid to execute smart contract method.
* - check parameter count.
* - check defined parameter solidity type and parameter solidity wrapper type.
* @param types A List of parameter that solidity wrapper type
*/
public void checkTypeValid(List types) {
if(types.size() != inputs.size()) {
throw new IllegalArgumentException("Not matched passed parameter count.");
}
}
/**
* Getter function for Caver.
* @return Caver
*/
public Caver getCaver() {
return caver;
}
/**
* Getter function for type.
* @return String
*/
public String getType() {
return type;
}
/**
* Getter function for name.
* @return String
*/
public String getName() {
return name;
}
/**
* Getter function for input.
* @return List
*/
public List getInputs() {
return inputs;
}
/**
* Getter function for output.
* @return List
*/
public List getOutputs() {
return outputs;
}
/**
* Getter function for signature.
* @return String
*/
public String getSignature() {
return signature;
}
/**
* Getter function for contract address
* @return String
*/
public String getContractAddress() {
return contractAddress;
}
/**
* Getter function for DefaultSendOptions
* @return SendOptions
*/
public SendOptions getDefaultSendOptions() {
return defaultSendOptions;
}
public List getNextContractMethods() {
return nextContractMethods;
}
/**
* Setter function for Caver.
* @param caver The Caver instance.
*/
void setCaver(Caver caver) {
this.caver = caver;
}
/**
* Setter function for type.
* @param type The input type. It always set "function".
*/
void setType(String type) {
this.type = type;
}
/**
* Setter function for name.
* @param name A function name.
*/
void setName(String name) {
this.name = name;
}
/**
* Setter function for inputs
* @param inputs The list of ContractIOType contains to function parameter information.
*/
void setInputs(List inputs) {
this.inputs = inputs;
}
/**
* Setter function for outputs
* @param outputs The list of ContractIOTYpe contains to function return value information.
*/
void setOutputs(List outputs) {
this.outputs = outputs;
}
/**
* Setter function for function signature.
* @param signature A function signature
*/
public void setSignature(String signature) {
this.signature = signature;
}
/**
* Setter function for contract address
* @param contractAddress A contract address.
*/
void setContractAddress(String contractAddress) {
this.contractAddress = contractAddress;
if(this.getNextContractMethods() != null && this.getNextContractMethods().size() != 0) {
this.getNextContractMethods().stream().forEach(contractMethod -> {
contractMethod.contractAddress = contractAddress;
});
}
}
/**
* Setter function for defaultSendOption
* @param defaultSendOptions The sendOptions to set DefaultSendOptions field.
*/
void setDefaultSendOptions(SendOptions defaultSendOptions) {
this.defaultSendOptions = defaultSendOptions;
}
/**
* Setter function for wallet
* @param wallet The class instance implemented IWallet interface to sign transaction.
*/
public void setWallet(IWallet wallet) {
this.wallet = wallet;
}
void setNextContractMethods(List nextContractMethods) {
this.nextContractMethods = nextContractMethods;
}
/**
* Make SendOptions instance by comparing with defaultSendOption and passed parameter "options"
* Passed parameter "options" has higher priority than "defaultSendOption" field.
* @param sendOption SendOptions instance
* @return SendOption
*/
public SendOptions makeSendOption(SendOptions sendOption) {
SendOptions defaultSendOption = this.getDefaultSendOptions();
String from = defaultSendOption.getFrom();
String gas = defaultSendOption.getGas();
String value = defaultSendOption.getValue();
Boolean isFeeDelegation = defaultSendOption.getFeeDelegation();
String feePayer = defaultSendOption.getFeePayer();
String feeRatio = defaultSendOption.getFeeRatio();
if(sendOption != null) {
if(sendOption.getFrom() != null) {
from = sendOption.getFrom();
}
if(sendOption.getGas() != null) {
gas = sendOption.getGas();
}
if(!sendOption.getValue().equals("0x0")) {
value = sendOption.getValue();
}
if(sendOption.getFeeDelegation() != null) {
isFeeDelegation = sendOption.getFeeDelegation();
}
if(sendOption.getFeePayer() != null) {
feePayer = sendOption.getFeePayer();
}
if(sendOption.getFeeRatio() != null) {
feeRatio = sendOption.getFeeRatio();
}
}
SendOptions options = new SendOptions(from, gas, value);
options.setFeeDelegation(isFeeDelegation);
if(options.getFeeDelegation() != null && options.getFeeDelegation()) {
options.setFeePayer(feePayer);
options.setFeeRatio(feeRatio);
}
return options;
}
/**
* Find a ContractMethod instance that has the function signature same as passed as a parameter.
* @param functionSignature The function signature to find a ContractMethod instance.
* @return ContractMethod
*/
public ContractMethod findMethodBySignature(String functionSignature) {
if(this.getType().equals(TYPE_CONSTRUCTOR)) {
return null;
}
ContractMethod findMethod = null;
List methodList = getAllMethod();
for(ContractMethod contractMethod : methodList) {
String signature = Utils.stripHexPrefix(contractMethod.getSignature());
if(signature.equals(functionSignature)) {
findMethod = contractMethod;
}
}
return findMethod;
}
private List getAllMethod() {
List methodList = new ArrayList<>();
methodList.add(this);
if(this.getNextContractMethods().size() > 0) {
methodList.addAll(this.getNextContractMethods());
}
return methodList;
}
/**
* Before executing SmartContractExecution transaction, check SendOptions field is valid.
* @param options SendOption instance.
*/
private void checkSendOption(SendOptions options) {
if(options.getFrom() == null || !Utils.isAddress(options.getFrom())) {
throw new IllegalArgumentException("Invalid 'from' parameter : " + options.getFrom());
}
if(options.getGas() == null || !Utils.isNumber(options.getGas())) {
throw new IllegalArgumentException("Invalid 'gas' parameter : " + options.getGas());
}
if(options.getValue() == null || !Utils.isNumber(options.getValue())) {
throw new IllegalArgumentException("Invalid 'value' parameter : " + options.getValue());
}
if(options.getFeeDelegation() != null && options.getFeeDelegation()) {
if(options.getFeePayer() == null || options.getFeePayer().equals("0x")) {
options.setFeePayer(Utils.DEFAULT_ZERO_ADDRESS);
}
if(!Utils.isAddress(options.getFeePayer())) {
throw new IllegalArgumentException("Invalid 'feePayer' parameter : " + options.getFeePayer());
}
if(options.getFeeRatio() != null) {
if(!Utils.isNumber(options.getFeeRatio()) && !Utils.isHex(options.getFeeRatio())) {
throw new IllegalArgumentException("Invalid type of feeRatio: feeRatio should be number type or hex number string");
}
int feeRatioVal = Numeric.toBigInt(options.getFeeRatio()).intValue();
if(feeRatioVal <= 0 || feeRatioVal >= 100) {
throw new IllegalArgumentException("Invalid feeRatio: feeRatio is out of range. [1,99]");
}
}
}
}
private ContractMethod findMatchedInstance(List arguments) {
// Check the parameter type defined in function and the parameter type passed are the same.
List matchedMethod = new ArrayList<>();
if(this.getInputs().size() != arguments.size()) {
for(ContractMethod method : this.getNextContractMethods()) {
if(method.getInputs().size() == arguments.size()) {
matchedMethod.add(method);
}
}
} else {
matchedMethod.add(this);
}
if(matchedMethod.size() == 0) {
throw new IllegalArgumentException("Cannot find method with passed parameters.");
}
if(matchedMethod.size() != 1) {
LOGGER.warn("It found a two or more overloaded function that has same parameter counts. It may be abnormally executed. Please use *withSolidityWrapper().");
}
return matchedMethod.get(0);
}
private ContractMethod findMatchedInstanceWithSolidityWrapper(List arguments) {
ContractMethod matchedMethod = null;
if(this.checkParamsTypeMatched(arguments)) {
matchedMethod = this;
} else {
for(ContractMethod method : this.getNextContractMethods()) {
if(method.checkParamsTypeMatched(arguments)) {
matchedMethod = method;
}
}
}
if(matchedMethod == null) {
throw new IllegalArgumentException("Cannot find method with passed parameters.");
}
return matchedMethod;
}
private boolean checkParamsTypeMatched(List arguments) {
if(this.getInputs().size() != arguments.size()) {
return false;
}
for(int i = 0; i < this.getInputs().size(); i++) {
ContractIOType ioType = this.getInputs().get(i);
if(!ioType.getTypeAsString().equals(arguments.get(i).getTypeAsString())) {
return false;
}
}
return true;
}
private TransactionReceipt.TransactionReceiptData sendTransaction(AbstractTransaction transaction, TransactionReceiptProcessor processor) throws IOException, TransactionException {
Bytes32 response = caver.rpc.klay.sendRawTransaction(transaction).send();
if(response.hasError()) {
throw new IOException(response.getError().getMessage());
}
return processor.waitForTransactionReceipt(response.getResult());
}
private List callFunction(ContractMethod method, String encodedInput, CallObject callObject) throws IOException, ClassNotFoundException {
if(callObject.getData() != null || callObject.getTo() != null) {
LOGGER.warn("'to' and 'data' field in CallObject will overwrite.");
}
callObject.setData(encodedInput);
callObject.setTo(method.getContractAddress());
Bytes response = caver.rpc.klay.call(callObject).send();
if(response.hasError()) {
throw new IOException(response.getError().getMessage());
}
String encodedResult = response.getResult();
return ABI.decodeParameters(method, encodedResult);
}
private String estimateGas(String encodedFunctionCall, CallObject callObject) throws IOException {
if(callObject.getData() != null || callObject.getTo() != null) {
LOGGER.warn("The 'to' and 'data' fields of the CallObject will be overwritten.");
}
callObject.setData(encodedFunctionCall);
callObject.setTo(this.getContractAddress());
Quantity estimateGas = caver.rpc.klay.estimateGas(callObject).send();
if(estimateGas.hasError()) {
throw new IOException(estimateGas.getError().getMessage());
}
return estimateGas.getResult();
}
private AbstractTransaction createTransaction(SendOptions sendOptions, String encoded) {
checkSendOption(sendOptions);
if(getType().equals("constructor")) { // contract deploy
if(sendOptions.getFeeDelegation() != null && sendOptions.getFeeDelegation()) { // fee delegation transaction
if(sendOptions.getFeeRatio() == null) {
return caver.transaction.feeDelegatedSmartContractDeploy.create(
TxPropertyBuilder.feeDelegatedSmartContractDeploy()
.setFrom(sendOptions.getFrom())
.setGas(sendOptions.getGas())
.setValue(sendOptions.getValue())
.setInput(encoded)
.setFeePayer(sendOptions.getFeePayer())
);
} else {
return caver.transaction.feeDelegatedSmartContractDeployWithRatio.create(
TxPropertyBuilder.feeDelegatedSmartContractDeployWithRatio()
.setFrom(sendOptions.getFrom())
.setGas(sendOptions.getGas())
.setValue(sendOptions.getValue())
.setInput(encoded)
.setFeePayer(sendOptions.getFeePayer())
.setFeeRatio(sendOptions.getFeeRatio())
);
}
} else { // basic transaction
return caver.transaction.smartContractDeploy.create(
TxPropertyBuilder.smartContractDeploy()
.setFrom(sendOptions.getFrom())
.setGas(sendOptions.getGas())
.setValue(sendOptions.getValue())
.setInput(encoded)
);
}
} else { // contract execution
if(sendOptions.getFeeDelegation() != null && sendOptions.getFeeDelegation()) { // fee delegation transaction
if(sendOptions.getFeeRatio() == null) {
return caver.transaction.feeDelegatedSmartContractExecution.create(
TxPropertyBuilder.feeDelegatedSmartContractExecution()
.setFrom(sendOptions.getFrom())
.setTo(contractAddress)
.setGas(sendOptions.getGas())
.setValue(sendOptions.getValue())
.setInput(encoded)
.setFeePayer(sendOptions.getFeePayer())
);
} else {
return caver.transaction.feeDelegatedSmartContractExecutionWithRatio.create(
TxPropertyBuilder.feeDelegatedSmartContractExecutionWithRatio()
.setFrom(sendOptions.getFrom())
.setTo(contractAddress)
.setGas(sendOptions.getGas())
.setValue(sendOptions.getValue())
.setInput(encoded)
.setFeePayer(sendOptions.getFeePayer())
.setFeeRatio(sendOptions.getFeeRatio())
);
}
} else { // basic transaction
return caver.transaction.smartContractExecution.create(
TxPropertyBuilder.smartContractExecution()
.setFrom(sendOptions.getFrom())
.setTo(contractAddress)
.setGas(sendOptions.getGas())
.setValue(sendOptions.getValue())
.setInput(encoded)
);
}
}
}
}