All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.klaytn.caver.abi.ABI Maven / Gradle / Ivy

There is a newer version: 1.12.2-android
Show newest version
/*
 * 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.abi;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.klaytn.caver.abi.datatypes.*;
import com.klaytn.caver.contract.ContractEvent;
import com.klaytn.caver.contract.ContractIOType;
import com.klaytn.caver.contract.ContractMethod;
import com.klaytn.caver.utils.Utils;
import org.web3j.crypto.Hash;
import org.web3j.protocol.ObjectMapperFactory;
import org.web3j.utils.Numeric;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import static com.klaytn.caver.abi.TypeEncoder.isDynamic;

/**
 * Representing a ABI type encode / decode.
 */
public class ABI {

    /**
     * Encodes a function call.
     * @param method A ContractMethod instance.
     * @param params A List of method parameter.
     * @return String
     */
    public static String encodeFunctionCall(ContractMethod method, List params) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        String functionSignature = ABI.buildFunctionString(method);

        List solTypeList = new ArrayList();

        for (ContractIOType contractIOType : method.getInputs()) {
            solTypeList.add(contractIOType.getTypeAsString());
        }

        return encodeFunctionCall(functionSignature, solTypeList, params);
    }

    /**
     * Encodes a function call.
     * @param functionSig A function signature string.
     * @param params A List of method parameter.
     * @return String
     */
    public static String encodeFunctionCall(String functionSig, List solTypeList, List params) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        String methodId = encodeFunctionSignature(functionSig);
        String encodedParams = encodeParameters(solTypeList, params);

        return methodId + encodedParams;
    }

    /**
     * Encodes a function call
     * @param method A ContractMethod instance.
     * @param params A List of method parameter wrapped solidity wrapper class.
     * @return String
     */
    public static String encodeFunctionCallWithSolidityWrapper(ContractMethod method, List params) {
        String methodId = encodeFunctionSignature(method);
        String encodedArguments = ABI.encodeParameters(params);

        return methodId + encodedArguments;
    }

    /**
     * Encodes a function signature.
     * @param method A ContractMethod instance.
     * @return String
     */
    public static String encodeFunctionSignature(ContractMethod method) {
        return encodeFunctionSignature(buildFunctionString(method));
    }

    /**
     * Encodes a function signature.
     * @param functionName A function name string.
     * @return String
     */
    public static String encodeFunctionSignature(String functionName) {
        byte[] input = functionName.getBytes();
        byte[] hash = Hash.sha3(input);
        return Numeric.toHexString(hash).substring(0, 10);
    }

    /**
     * Encodes a data related contract deployment.
     * @param constructor A ContractMethod instance that contains constructor info.
     * @param byteCode A smart contract bytecode.
     * @param constructorParams A list of parameter that need to execute Constructor
     * @return String
     * @throws ClassNotFoundException
     * @throws NoSuchMethodException
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    public static String encodeContractDeploy(ContractMethod constructor, String byteCode, List constructorParams) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        String encodedParam = "";

        if(constructor != null) {
            constructor.checkTypeValid(constructorParams);
            encodedParam = ABI.encodeParameters(constructor, constructorParams);
        }

        return byteCode + encodedParam;
    }

    /**
     * Build a function name string.
     * @param method A ContractMethod instance.
     * @return String
     */
    public static String buildFunctionString(ContractMethod method) {
        StringBuilder result = new StringBuilder();
        result.append(method.getName());
        result.append("(");
        String params = method.getInputs().stream()
                .map(ContractIOType::getTypeAsString)
                .collect(Collectors.joining(","));
        result.append(params);
        result.append(")");

        //remove "tuple" string
        return result.toString().replace("tuple", "");
    }

    /**
     * Encodes a event signature.
     * @param event A ContractEvent instance.
     * @return String
     */
    public static String encodeEventSignature(ContractEvent event) {
        return encodeEventSignature(buildEventString(event));
    }

    /**
     * Encodes a event signature.
     * @param eventName A event signature.
     * @return String
     */
    public static String encodeEventSignature(String eventName) {
        byte[] input = eventName.getBytes();
        byte[] hash = Hash.sha3(input);
        return Numeric.toHexString(hash);
    }

    /**
     * Build a event name string.
     * @param event A ContractEvent instance
     * @return String
     */
    public static String buildEventString(ContractEvent event) {
        StringBuilder result = new StringBuilder();
        result.append(event.getName());
        result.append("(");
        String params = event.getInputs().stream()
                .map(ContractIOType::getTypeAsString)
                .collect(Collectors.joining(","));
        result.append(params);
        result.append(")");

        //remove "tuple" string
        return result.toString().replace("tuple", "");
    }

    /**
     * Encodes a parameter based on its type to its ABI representation.
     * @param solidityType A solidity type to encode.
     * @param value A value to encode
     * @return String
     * @throws ClassNotFoundException
     * @throws NoSuchMethodException
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    public static String encodeParameter(String solidityType, Object value) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        Type type = TypeDecoder.instantiateType(solidityType, value);
        return encodeParameter(type);
    }

    /**
     * Encodes a parameters based on its type to its ABI representation.
     * @param method A ContractMethod instance that contains to solidity type
     * @param values A List of value to encode
     * @return String
     * @throws ClassNotFoundException
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    public static String encodeParameters(ContractMethod method, List values) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        List solTypeList = new ArrayList<>();
        for(ContractIOType type : method.getInputs()) {
            solTypeList.add(type.getTypeAsString());
        }

        return encodeParameters(solTypeList, values);
    }

    /**
     * Encodes a parameters based on its type to its ABI representation.
     * @param solidityTypes A list of solidity type to encode
     * @param values A List of value to encode
     * @return String
     * @throws ClassNotFoundException
     * @throws NoSuchMethodException
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    public static String encodeParameters(List solidityTypes, List values) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        List typeList = new ArrayList<>();
        for(int i=0; i < solidityTypes.size(); i++) {
            Type type = TypeDecoder.instantiateType(solidityTypes.get(i), values.get(i));
            typeList.add(type);
        }

        return encodeParameters(typeList);
    }

    /**
     * Encodes a parameter based on its type to its ABI representation.
     * @param parameter A parameter that wrapped solidity type wrapper.
     * @return String
     */
    public static String encodeParameter(Type parameter) {
        return encodeParameters(Arrays.asList(parameter));
    }

    /**
     * Encodes a parameter based on its type to its ABI representation.
     * @param parameters A List of parameters that wrappped solidity type wrapper
     * @return String
     */
    public static String encodeParameters(List parameters) {
        return new DefaultFunctionEncoder().encodeParameters(parameters);
    }

    /**
     * Decodes an ABI encoded parameter.
     * @param solidityType A solidity type string.
     * @param encoded The ABI byte code to decode
     * @return Type
     * @throws ClassNotFoundException
     */
    public static Type decodeParameter(String solidityType, String encoded) throws ClassNotFoundException {
        return decodeParameters(Arrays.asList(solidityType), encoded).get(0);
    }

    /**
     * Decodes an ABI encoded parameters.
     * @param solidityTypeList A List of solidity type string.
     * @param encoded The ABI byte code to decode
     * @return List
     * @throws ClassNotFoundException
     */
    public static List decodeParameters(List solidityTypeList, String encoded) throws ClassNotFoundException {
        List> params = new ArrayList<>();

        for(String solType : solidityTypeList) {
            params.add(TypeReference.makeTypeReference(solType));
        }

        return FunctionReturnDecoder.decode(encoded, params);
    }

    /**
     * Decodes an ABI encoded output parameters.
     * @param method A ContractMethod instance.
     * @param encoded The ABI byte code to decoded
     * @return List
     * @throws ClassNotFoundException
     */
    public static List decodeParameters(ContractMethod method, String encoded) throws ClassNotFoundException {
        List> resultParams = new ArrayList<>();

        for(ContractIOType ioType: method.getOutputs()) {
            resultParams.add(TypeReference.makeTypeReference(ioType.getTypeAsString()));
        }

        return FunctionReturnDecoder.decode(encoded, resultParams);
    }

    /**
     * Decodes an ABI encoded log data and indexed topic data
     * @param inputs A list of ContractIOType instance.
     * @param data An ABI-encoded in the data field of a log
     * @param topics A list of indexed parameter topics of the log.
     * @return EventValues
     * @throws ClassNotFoundException
     */
    public static EventValues decodeLog(List inputs, String data, List topics) throws ClassNotFoundException {
        List> indexedList = new ArrayList<>();
        List> nonIndexedList = new ArrayList<>();

        for(ContractIOType input: inputs) {
            if(input.isIndexed()) {
                indexedList.add(TypeReference.makeTypeReference(input.getTypeAsString()));
            } else {
                nonIndexedList.add(TypeReference.makeTypeReference(input.getTypeAsString()));
            }
        }

        List nonIndexedValues = FunctionReturnDecoder.decode(data, nonIndexedList);
        List indexedValues = new ArrayList<>();

        for(int i=0; i < indexedList.size(); i++) {
            Type value = FunctionReturnDecoder.decodeIndexedValue(
                    topics.get(i + 1), indexedList.get(i));
            indexedValues.add(value);
        }

        return new EventValues(indexedValues, nonIndexedValues);
    }

    /**
     * Decodes a function call data that composed of function selector and encoded input argument.
     * 
Example :
     * {@code
     *  String encodedData = "0x24ee0.....";
     *  String abi = "{\n" +
     *                     "  \"name\":\"myMethod\",\n" +
     *                     "  \"type\":\"function\",\n" +
     *                     "  \"inputs\":[\n" +
     *                     "    {\n" +
     *                     "      \"type\":\"uint256\",\n" +
     *                     "      \"name\":\"myNumber\"\n" +
     *                     "    },\n" +
     *                     "    {\n" +
     *                     "      \"type\":\"string\",\n" +
     *                     "      \"name\":\"mystring\"\n" +
     *                     "    }\n" +
     *                     "  ]\n" +
     *                     "}";
     *
     * List params = caver.abi.decodeFunctionCall(abi, encoded);
     * }
     * 
* @param functionAbi The abi json string of a function. * @param encodedString The encode function call data string. * @return List<Type> * @throws ClassNotFoundException */ public static List decodeFunctionCall(String functionAbi, String encodedString) throws ClassNotFoundException { try { ObjectMapper mapper = ObjectMapperFactory.getObjectMapper(); ContractMethod method = mapper.readValue(functionAbi, ContractMethod.class); if(method.getName() == null || method.getName().isEmpty() || method.getInputs() == null) { throw new IllegalArgumentException("Insufficient info in abi object: The function name and inputs must be defined inside the abi function object."); } method.setSignature(ABI.encodeFunctionSignature(method)); return decodeFunctionCall(method, encodedString); } catch(IOException e) { throw new IllegalArgumentException("Invalid abi parameter type: To decode function call, you need to pass an abi object of the function as a first parameter.", e); } } /** * Decodes a function call data that composed of function selector and encoded input argument. *
Example :
     * {@code
     * String contractABI = "...";
     * Contract contract = caver.contract.create(contractABI);
     * ContractMethod method = contract.getMethod("myFunction");
     *
     * List result = caver.abi.decodeFunctionCall(method, "0x{ABI-encoded string}");
     * }
* @param method The ContractMethod instance to decode ABI-encoded string. * @param encodedString The encode function call data string. * @return List<Type> * @throws ClassNotFoundException */ public static List decodeFunctionCall(ContractMethod method, String encodedString) throws ClassNotFoundException { String encoded = Utils.stripHexPrefix(encodedString); String functionSignature = encoded.substring(0, 8); String encodedParams = encoded.substring(8); //Find a ContractMethod included overloaded function. ContractMethod findMethod = method.findMethodBySignature(functionSignature); if(findMethod == null) { throw new IllegalArgumentException("Invalid function signature: The function signature of the abi as a parameter and the function signatures extracted from the function call string do not match."); } List inputs = findMethod.getInputs() .stream() .map(ContractIOType::getTypeAsString) .collect(Collectors.toList()); List decoded = decodeParameters(inputs, encodedParams); return decoded; } }