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

org.fisco.bcos.sdk.v3.codec.ContractCodec Maven / Gradle / Ivy

/*
 * Copyright 2014-2020  [fisco-dev]
 *
 * 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 org.fisco.bcos.sdk.v3.codec;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.ByteArrayOutputStream;
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 org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.fisco.bcos.sdk.v3.codec.abi.Constant;
import org.fisco.bcos.sdk.v3.codec.datatypes.AbiTypes;
import org.fisco.bcos.sdk.v3.codec.datatypes.Address;
import org.fisco.bcos.sdk.v3.codec.datatypes.Bool;
import org.fisco.bcos.sdk.v3.codec.datatypes.DynamicArray;
import org.fisco.bcos.sdk.v3.codec.datatypes.DynamicBytes;
import org.fisco.bcos.sdk.v3.codec.datatypes.DynamicStruct;
import org.fisco.bcos.sdk.v3.codec.datatypes.StaticArray;
import org.fisco.bcos.sdk.v3.codec.datatypes.StaticStruct;
import org.fisco.bcos.sdk.v3.codec.datatypes.Type;
import org.fisco.bcos.sdk.v3.codec.datatypes.TypeReference;
import org.fisco.bcos.sdk.v3.codec.datatypes.Utf8String;
import org.fisco.bcos.sdk.v3.codec.datatypes.generated.Uint8;
import org.fisco.bcos.sdk.v3.codec.scale.FunctionReturnDecoder;
import org.fisco.bcos.sdk.v3.codec.wrapper.ABIDefinition;
import org.fisco.bcos.sdk.v3.codec.wrapper.ABIDefinitionFactory;
import org.fisco.bcos.sdk.v3.codec.wrapper.ABIObject;
import org.fisco.bcos.sdk.v3.codec.wrapper.ABIObjectFactory;
import org.fisco.bcos.sdk.v3.codec.wrapper.ContractABIDefinition;
import org.fisco.bcos.sdk.v3.codec.wrapper.ContractCodecJsonWrapper;
import org.fisco.bcos.sdk.v3.codec.wrapper.ContractCodecTools;
import org.fisco.bcos.sdk.v3.crypto.CryptoSuite;
import org.fisco.bcos.sdk.v3.crypto.hash.Hash;
import org.fisco.bcos.sdk.v3.crypto.hash.Keccak256;
import org.fisco.bcos.sdk.v3.crypto.hash.SM3Hash;
import org.fisco.bcos.sdk.v3.model.CryptoType;
import org.fisco.bcos.sdk.v3.model.EventLog;
import org.fisco.bcos.sdk.v3.utils.Hex;
import org.fisco.bcos.sdk.v3.utils.Numeric;
import org.fisco.bcos.sdk.v3.utils.ObjectMapperFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** ABI encode and decode tool */
public class ContractCodec {

    private static final Logger logger = LoggerFactory.getLogger(ContractCodec.class);

    private final CryptoSuite cryptoSuite;
    private final Hash hashImpl;
    private final boolean isWasm;
    private final ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper();
    private FunctionEncoderInterface functionEncoder = null;
    private FunctionReturnDecoderInterface functionReturnDecoder = null;
    private final ABIDefinitionFactory abiDefinitionFactory;
    private final ContractCodecJsonWrapper contractCodecJsonWrapper =
            new ContractCodecJsonWrapper();

    @Deprecated
    public ContractCodec(CryptoSuite cryptoSuite, boolean isWasm) {
        this.cryptoSuite = cryptoSuite;
        this.hashImpl = cryptoSuite.getHashImpl();
        this.isWasm = isWasm;
        if (isWasm) {
            this.functionEncoder =
                    new org.fisco.bcos.sdk.v3.codec.scale.FunctionEncoder(this.hashImpl);
            this.functionReturnDecoder = new FunctionReturnDecoder();
        } else {
            this.functionEncoder =
                    new org.fisco.bcos.sdk.v3.codec.abi.FunctionEncoder(this.hashImpl);
            this.functionReturnDecoder =
                    new org.fisco.bcos.sdk.v3.codec.abi.FunctionReturnDecoder();
        }
        this.abiDefinitionFactory = new ABIDefinitionFactory(hashImpl);
    }

    public ContractCodec(Hash hashImpl, boolean isWasm) {
        this.hashImpl = hashImpl;
        this.isWasm = isWasm;
        if (isWasm) {
            this.functionEncoder =
                    new org.fisco.bcos.sdk.v3.codec.scale.FunctionEncoder(this.hashImpl);
            this.functionReturnDecoder = new FunctionReturnDecoder();
        } else {
            this.functionEncoder =
                    new org.fisco.bcos.sdk.v3.codec.abi.FunctionEncoder(this.hashImpl);
            this.functionReturnDecoder =
                    new org.fisco.bcos.sdk.v3.codec.abi.FunctionReturnDecoder();
        }
        this.abiDefinitionFactory = new ABIDefinitionFactory(this.hashImpl);
        // for compatibility
        if (this.hashImpl instanceof SM3Hash) {
            this.cryptoSuite = new CryptoSuite(CryptoType.SM_TYPE);
        } else if (this.hashImpl instanceof Keccak256) {
            this.cryptoSuite = new CryptoSuite(CryptoType.ECDSA_TYPE);
        } else {
            this.cryptoSuite = null;
        }
    }

    public boolean isWasm() {
        return isWasm;
    }

    @Deprecated
    public CryptoSuite getCryptoSuite() {
        return this.cryptoSuite;
    }

    public ABIDefinitionFactory getAbiDefinitionFactory() {
        return abiDefinitionFactory;
    }

    public FunctionEncoderInterface getFunctionEncoder() {
        return functionEncoder;
    }

    private Type buildType(ABIDefinition.NamedType namedType, String param)
            throws ContractCodecException, IOException {
        String typeStr = namedType.getType();
        ABIDefinition.Type paramType = new ABIDefinition.Type(typeStr);
        Type type = null;
        if (paramType.isList()) {
            List elements = new ArrayList();
            JsonNode jsonNode = this.objectMapper.readTree(param);

            ABIDefinition.NamedType subType = new ABIDefinition.NamedType();
            subType.setType(paramType.reduceDimensionAndGetType().getType());
            subType.setComponents(namedType.getComponents());

            for (JsonNode subNode : jsonNode) {
                String subNodeStr =
                        subNode.isTextual()
                                ? subNode.asText()
                                : this.objectMapper.writeValueAsString(subNode);
                Type element = buildType(subType, subNodeStr);
                elements.add(element);
            }
            if (elements.isEmpty()) {
                Class arrayClass = AbiTypes.getType(paramType.rawType);
                type =
                        paramType.isFixedList()
                                ? new StaticArray(arrayClass, elements)
                                : new DynamicArray(arrayClass, elements);
            } else {
                type =
                        paramType.isFixedList()
                                ? new StaticArray(elements.get(0).getClass(), elements)
                                : new DynamicArray(elements.get(0).getClass(), elements);
            }
            return type;
        } else if (typeStr.equals("tuple")) {
            List components = new ArrayList<>();
            JsonNode jsonNode = this.objectMapper.readTree(param);
            for (ABIDefinition.NamedType component : namedType.getComponents()) {
                JsonNode subNode = jsonNode.get(component.getName());
                String subNodeStr =
                        subNode.isTextual()
                                ? subNode.asText()
                                : this.objectMapper.writeValueAsString(subNode);
                components.add(buildType(component, subNodeStr));
            }
            type =
                    namedType.isDynamic()
                            ? new DynamicStruct(components)
                            : new StaticStruct(components);
            return type;
        } else {
            if (typeStr.startsWith("uint")) {
                int bitSize = 256;
                if (!typeStr.equals("uint")) {
                    String bitSizeStr = typeStr.substring("uint".length());
                    try {
                        bitSize = Integer.parseInt(bitSizeStr);
                    } catch (NumberFormatException e) {
                        String errorMsg =
                                " unrecognized type: " + typeStr + ", error:" + e.getCause();
                        logger.error(errorMsg);
                        throw new ContractCodecException(errorMsg);
                    }
                }

                try {
                    Class uintClass =
                            Class.forName(
                                    "org.fisco.bcos.sdk.v3.codec.datatypes.generated.Uint"
                                            + bitSize);
                    type =
                            (Type)
                                    uintClass
                                            .getDeclaredConstructor(BigInteger.class)
                                            .newInstance(new BigInteger(param));
                } catch (ClassNotFoundException
                        | NoSuchMethodException
                        | InstantiationException
                        | IllegalAccessException
                        | InvocationTargetException e) {
                    String errorMsg =
                            "buildType error, type: " + typeStr + ", error: " + e.getCause();
                    logger.error(errorMsg);
                    throw new ContractCodecException(errorMsg);
                }

                return type;
            }

            if (typeStr.startsWith("int")) {
                int bitSize = 256;
                if (!typeStr.equals("int")) {
                    String bitSizeStr = typeStr.substring("int".length());
                    try {
                        bitSize = Integer.parseInt(bitSizeStr);
                    } catch (NumberFormatException e) {
                        String errorMsg = "unrecognized int type: " + typeStr;
                        logger.error(errorMsg);
                        throw new ContractCodecException(errorMsg);
                    }
                }

                try {
                    Class uintClass =
                            Class.forName(
                                    "org.fisco.bcos.sdk.v3.codec.datatypes.generated.Int"
                                            + bitSize);
                    type =
                            (Type)
                                    uintClass
                                            .getDeclaredConstructor(BigInteger.class)
                                            .newInstance(new BigInteger(param));
                } catch (ClassNotFoundException
                        | NoSuchMethodException
                        | InstantiationException
                        | IllegalAccessException
                        | InvocationTargetException e) {
                    String errorMsg = "unrecognized type: " + typeStr + ", error:" + e.getCause();
                    logger.error(errorMsg);
                    throw new ContractCodecException(errorMsg);
                }

                return type;
            }

            if (typeStr.equals("bool")) {
                type = new Bool(Boolean.parseBoolean(param));
                return type;
            }

            if (typeStr.equals("string")) {
                type = new Utf8String(param);
                return type;
            }

            if (typeStr.equals("bytes")) {
                byte[] bytes = ContractCodecJsonWrapper.tryDecodeInputData(param);
                if (bytes == null) {
                    bytes = param.getBytes();
                }
                type = new DynamicBytes(bytes);
                return type;
            }

            if (typeStr.equals("address")) {
                type = new Address(param);
                return type;
            }

            // static bytesN
            if (typeStr.startsWith("bytes")) {
                String lengthStr = typeStr.substring("bytes".length());
                int length;
                try {
                    length = Integer.parseInt(lengthStr);
                } catch (NumberFormatException e) {
                    String errorMsg = "unrecognized static byte array type: " + typeStr;
                    logger.error(errorMsg);
                    throw new ContractCodecException(errorMsg);
                }

                if (length > 32) {
                    String errorMsg = "the length of static byte array exceeds 32: " + typeStr;
                    logger.error(errorMsg);
                    throw new ContractCodecException(errorMsg);
                }
                byte[] bytesN = ContractCodecJsonWrapper.tryDecodeInputData(param);
                if (bytesN == null) {
                    bytesN = param.getBytes();
                }
                if (bytesN.length != length) {
                    String errorMsg =
                            String.format(
                                    "expected byte array at length %d but length of provided in data is %d",
                                    length, bytesN.length);
                    logger.error(errorMsg);
                    throw new ContractCodecException(errorMsg);
                }

                try {
                    Class bytesClass =
                            Class.forName(
                                    "org.fisco.bcos.sdk.v3.codec.datatypes.generated.Bytes"
                                            + length);
                    type =
                            (Type)
                                    bytesClass
                                            .getDeclaredConstructor(byte[].class)
                                            .newInstance(bytesN);
                } catch (ClassNotFoundException
                        | NoSuchMethodException
                        | InstantiationException
                        | IllegalAccessException
                        | InvocationTargetException e) {
                    e.printStackTrace();
                }
                return type;
            }
        }
        String errorMsg = "unrecognized type: " + typeStr;
        logger.error(errorMsg);
        throw new ContractCodecException(errorMsg);
    }

    // -------------------------------------------------------------------------------
    //  -------------------------------  encode begin --------------------------------

    public byte[] encodeConstructor(String abi, String bin, List params)
            throws ContractCodecException {

        ContractABIDefinition contractABIDefinition = this.abiDefinitionFactory.loadABI(abi);
        ABIDefinition abiDefinition = contractABIDefinition.getConstructor();
        ABIObject inputABIObject = ABIObjectFactory.createInputObject(abiDefinition);
        try {
            byte[] encodeParams =
                    ContractCodecTools.encode(
                            ContractCodecTools.decodeABIObjectValue(inputABIObject, params),
                            isWasm);
            return encodeConstructorFromBytes(bin, encodeParams);
        } catch (Exception e) {
            logger.error(" exception in encodeConstructor : {}", e.getMessage());
        }
        String errorMsg = " cannot encode in encodeConstructor with appropriate interface ABI";
        logger.error(errorMsg);
        throw new ContractCodecException(errorMsg);
    }

    public byte[] encodeConstructorFromString(String abi, String bin, List params)
            throws ContractCodecException {
        ContractABIDefinition contractABIDefinition = this.abiDefinitionFactory.loadABI(abi);
        ABIDefinition abiDefinition = contractABIDefinition.getConstructor();
        List inputTypes = abiDefinition.getInputs();
        if (inputTypes.size() != params.size()) {
            String errorMsg =
                    String.format(
                            " expected %d parameters but provided %d parameters",
                            inputTypes.size(), params.size());
            logger.error(errorMsg);
            throw new ContractCodecException(errorMsg);
        }

        try {
            ABIObject inputObject = ABIObjectFactory.createInputObject(abiDefinition);
            ABIObject abiObject = contractCodecJsonWrapper.encode(inputObject, params);
            byte[] paramBytes = abiObject.encode(isWasm);
            return encodeConstructorFromBytes(bin, paramBytes);
        } catch (Exception e) {
            String errorMsg =
                    " cannot encode in encodeMethodFromObject with appropriate interface ABI, cause:"
                            + e.getMessage();
            logger.error(errorMsg);
            throw new ContractCodecException(errorMsg);
        }
    }

    public byte[] encodeConstructorFromBytes(String bin, byte[] params)
            throws ContractCodecException {
        try {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

            if (!this.isWasm) {
                outputStream.write(Hex.decode(bin));
                if (params != null) {
                    outputStream.write(params);
                }
            } else {
                List deployParams = new ArrayList<>();
                deployParams.add(new DynamicBytes(Hex.decode(bin)));
                if (params != null) {
                    deployParams.add(new DynamicBytes(params));
                } else {
                    deployParams.add(new Uint8(0));
                }
                outputStream.write(
                        org.fisco.bcos.sdk.v3.codec.scale.FunctionEncoder.encodeParameters(
                                deployParams, null));
            }
            return outputStream.toByteArray();
        } catch (Exception e) {
            logger.error(" exception in encodeMethodFromObject : {}", e.getMessage());
            String errorMsg =
                    " cannot encode in encodeMethodFromObject with appropriate interface ABI, cause:"
                            + e.getMessage();
            logger.error(errorMsg);
            throw new ContractCodecException(errorMsg);
        }
    }

    public byte[] encodeMethod(String abi, String methodName, List params)
            throws ContractCodecException {
        ContractABIDefinition contractABIDefinition = this.abiDefinitionFactory.loadABI(abi);
        List methods = contractABIDefinition.getFunctions().get(methodName);
        if (methods == null || methods.isEmpty()) {
            throw new ContractCodecException(Constant.NO_APPROPRIATE_ABI_METHOD);
        }
        for (ABIDefinition abiDefinition : methods) {
            if (abiDefinition.getInputs().size() == params.size()) {
                try {
                    return encodeMethodByAbiDefinition(abiDefinition, params);
                } catch (Exception e) {
                    logger.error(" exception in encodeMethodFromObject : {}", e.getMessage());
                }
            }
        }
        logger.error(Constant.NO_APPROPRIATE_ABI_METHOD);
        throw new ContractCodecException(Constant.NO_APPROPRIATE_ABI_METHOD);
    }

    public byte[] encodeMethodByAbiDefinition(ABIDefinition abiDefinition, List params)
            throws ContractCodecException {
        ABIObject inputABIObject = ABIObjectFactory.createInputObject(abiDefinition);
        try {

            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            outputStream.write(abiDefinition.getMethodId(this.cryptoSuite));
            outputStream.write(
                    ContractCodecTools.encode(
                            ContractCodecTools.decodeABIObjectValue(inputABIObject, params),
                            isWasm));
            return outputStream.toByteArray();
        } catch (IOException e) {
            throw new ContractCodecException(Constant.NO_APPROPRIATE_ABI_METHOD);
        }
    }

    public byte[] encodeMethodById(String abi, byte[] methodId, List params)
            throws ContractCodecException {
        ContractABIDefinition contractABIDefinition = this.abiDefinitionFactory.loadABI(abi);
        ABIDefinition abiDefinition = contractABIDefinition.getABIDefinitionByMethodId(methodId);
        if (abiDefinition == null) {
            throw new ContractCodecException(Constant.NO_APPROPRIATE_ABI_METHOD);
        }
        Exception cause;
        try {
            return encodeMethodByAbiDefinition(abiDefinition, params);
        } catch (Exception e) {
            cause = e;
            logger.error(" exception in encodeMethodByIdFromObject : {}", e.getMessage());
        }

        String errorMsg =
                " cannot encode in encodeMethodByIdFromObject with appropriate interface ABI, cause:"
                        + cause.getMessage();
        logger.error(errorMsg);
        throw new ContractCodecException(errorMsg);
    }

    public byte[] encodeMethodByInterface(String methodInterface, List params)
            throws ContractCodecException {

        ABIDefinition abiDefinition = ABIDefinition.createABIDefinition(methodInterface);
        if (abiDefinition.getInputs().size() == params.size()) {
            try {
                return encodeMethodByAbiDefinition(abiDefinition, params);
            } catch (Exception e) {
                logger.error(
                        " exception in encodeMethodByInterfaceFromObject : {}", e.getMessage());
            }
        }

        String errorMsg = " cannot encode in encodeMethodByInterfaceFromObject";
        logger.error(errorMsg);
        throw new ContractCodecException(errorMsg);
    }

    public byte[] encodeMethodFromString(String abi, String methodName, List params)
            throws ContractCodecException {
        ContractABIDefinition contractABIDefinition = this.abiDefinitionFactory.loadABI(abi);
        List methods = contractABIDefinition.getFunctions().get(methodName);
        if (methods == null) {
            logger.debug(
                    "Invalid methodName: {}, all the functions are: {}",
                    methodName,
                    contractABIDefinition.getFunctions());
            throw new ContractCodecException(
                    "Invalid method "
                            + methodName
                            + " , supported functions are: "
                            + contractABIDefinition.getFunctions().keySet());
        }

        for (ABIDefinition abiDefinition : methods) {
            if (abiDefinition.getInputs().size() == params.size()) {
                ABIObject inputObject = ABIObjectFactory.createInputObject(abiDefinition);
                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                try {
                    byte[] methodId = abiDefinition.getMethodId(cryptoSuite);
                    ABIObject abiObject = contractCodecJsonWrapper.encode(inputObject, params);
                    byte[] encode = abiObject.encode(isWasm);
                    outputStream.write(methodId);
                    outputStream.write(encode);
                    return outputStream.toByteArray();
                } catch (Exception e) {
                    logger.error(" exception in encodeMethodFromString : {}", e.getMessage());
                }
            }
        }

        String errorMsg =
                " cannot encode in encodeMethodFromString with appropriate interface ABI, make sure params match";
        logger.error(errorMsg);
        throw new ContractCodecException(errorMsg);
    }

    public byte[] encodeMethodByIdFromString(String abi, byte[] methodId, List params)
            throws ContractCodecException {
        ContractABIDefinition contractABIDefinition = this.abiDefinitionFactory.loadABI(abi);
        ABIDefinition abiDefinition = contractABIDefinition.getABIDefinitionByMethodId(methodId);
        if (abiDefinition == null) {
            logger.error(Constant.NO_APPROPRIATE_ABI_METHOD);
            throw new ContractCodecException(Constant.NO_APPROPRIATE_ABI_METHOD);
        }
        try {
            ABIObject inputObject = ABIObjectFactory.createInputObject(abiDefinition);
            ABIObject abiObject = contractCodecJsonWrapper.encode(inputObject, params);
            byte[] encode = abiObject.encode(isWasm);

            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            if (methodId != null) {
                outputStream.write(methodId);
            }
            outputStream.write(encode);
            return outputStream.toByteArray();
        } catch (Exception e) {
            logger.error(" exception in encodeMethodByIdFromString : {}", e.getMessage());
        }

        String errorMsg =
                " cannot encode in encodeMethodByIdFromString with appropriate interface ABI";
        logger.error(errorMsg);
        throw new ContractCodecException(errorMsg);
    }

    public byte[] encodeMethodByInterfaceFromString(String methodInterface, List params)
            throws ContractCodecException {

        ABIDefinition abiDefinition = ABIDefinition.createABIDefinition(methodInterface);
        if (abiDefinition.getInputs().size() == params.size()) {
            ABIObject inputABIObject = ABIObjectFactory.createInputObject(abiDefinition);
            try {
                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                outputStream.write(abiDefinition.getMethodId(this.cryptoSuite));
                outputStream.write(
                        ContractCodecTools.encode(
                                contractCodecJsonWrapper.encode(inputABIObject, params), isWasm));
                return outputStream.toByteArray();
            } catch (Exception e) {
                logger.error(
                        " exception in encodeMethodByInterfaceFromString : {}", e.getMessage());
            }
        }

        String errorMsg = " cannot encode in encodeMethodByInterfaceFromString";
        logger.error(errorMsg);
        throw new ContractCodecException(errorMsg);
    }

    //  -------------------------------  encode end -----------------------------------------------
    //  -------------------------------------------------------------------------------------------

    //  -------------------------------------------------------------------------------------------
    //  -------------------------------  decode input begin ---------------------------------------

    public Pair, List> decodeMethodAndGetInputObject(
            ABIDefinition abiDefinition, String input) throws ContractCodecException {
        ABIObject abiObject = decodeMethodAndGetInputObjectByABIDefinition(abiDefinition, input);
        return ContractCodecTools.decodeJavaObjectAndGetOutputObject(abiObject);
    }

    public ABIObject decodeMethodAndGetInputObjectByABIDefinition(
            ABIDefinition abiDefinition, String input) throws ContractCodecException {
        ABIObject inputObject = ABIObjectFactory.createInputObject(abiDefinition);
        try {
            boolean startWithHexPrefix = input.startsWith("0x") || input.startsWith("0X");
            return ContractCodecTools.decode(
                    inputObject, Hex.decode(input.substring(startWithHexPrefix ? 10 : 8)), isWasm);
        } catch (Exception e) {
            logger.error(" exception in decodeMethodToObject : ", e);
        }
        String errorMsg =
                " cannot decode in decodeMethodAndGetInputObject with appropriate interface ABI";
        logger.error(errorMsg);
        throw new ContractCodecException(errorMsg);
    }

    @Deprecated
    public List decodeMethodAndGetInputObject(String abi, String methodName, String input)
            throws ContractCodecException {
        ContractABIDefinition contractABIDefinition = this.abiDefinitionFactory.loadABI(abi);
        List methods = contractABIDefinition.getFunctions().get(methodName);
        for (ABIDefinition abiDefinition : methods) {
            List inputs = abiDefinition.getInputs();
            List> inputTypes = new ArrayList<>();
            try {
                for (ABIDefinition.NamedType namedType : inputs) {
                    inputTypes.add(TypeReference.makeTypeReference(namedType.getType(), false));
                }
                boolean startWithHexPrefix = input.startsWith("0x") || input.startsWith("0X");
                return this.functionReturnDecoder.decode(
                        input.substring(startWithHexPrefix ? 10 : 8), inputTypes);
            } catch (Exception e) {
                logger.error("exception in decodeMethodToObject: {}, e:", e.getMessage(), e);
            }
        }

        String errorMsg =
                String.format(
                        "cannot decode in decodeMethodAndGetInputObject with appropriate interface ABI: methodName = %s",
                        methodName);
        logger.error(errorMsg);
        throw new ContractCodecException(errorMsg);
    }

    public ABIObject decodeMethodAndGetInputABIObject(String abi, String methodName, String input)
            throws ContractCodecException {
        ContractABIDefinition contractABIDefinition = this.abiDefinitionFactory.loadABI(abi);
        List methods = contractABIDefinition.getFunctions().get(methodName);
        for (ABIDefinition abiDefinition : methods) {
            ABIObject inputObject = ABIObjectFactory.createInputObject(abiDefinition);
            try {
                boolean startWithHexPrefix = Hex.hasHexPrefix(input);
                return ContractCodecTools.decode(
                        inputObject,
                        Hex.decode(input.substring(startWithHexPrefix ? 10 : 8)),
                        isWasm);
            } catch (Exception e) {
                logger.error("exception in decodeMethodToObject: {}, e:", e.getMessage(), e);
            }
        }

        String errorMsg =
                String.format(
                        "cannot decode in decodeMethodAndGetInputABIObject with appropriate interface ABI: methodName = %s",
                        methodName);
        logger.error(errorMsg);
        throw new ContractCodecException(errorMsg);
    }

    public List decodeConstructorInput(String abi, String bin, String input)
            throws ContractCodecException {
        String paramsInput = StringUtils.substringAfter(input, bin);
        if (paramsInput.isEmpty()) { //
            return new ArrayList<>();
        }

        ContractABIDefinition contractABIDefinition = this.abiDefinitionFactory.loadABI(abi);
        ABIDefinition abiDefinition = contractABIDefinition.getConstructor();
        return this.decodeMethodAndGetInputObject(abiDefinition, paramsInput).getLeft();
    }

    public List decodeConstructorInputToString(String abi, String bin, String input)
            throws ContractCodecException {
        String paramsInput = StringUtils.substringAfter(input, bin);
        if (paramsInput.isEmpty()) { //
            return new ArrayList<>();
        }

        ContractABIDefinition contractABIDefinition = this.abiDefinitionFactory.loadABI(abi);
        ABIDefinition abiDefinition = contractABIDefinition.getConstructor();
        ABIObject inputObject = ABIObjectFactory.createInputObject(abiDefinition);
        try {
            return contractCodecJsonWrapper.decode(inputObject, Hex.decode(paramsInput), isWasm);
        } catch (Exception e) {
            logger.error(" exception in decodeConstructorInputToString : {}", e.getMessage());
        }

        String errorMsg =
                " cannot decode in decodeConstructorInputToString with appropriate interface ABI";
        logger.error(errorMsg);
        throw new ContractCodecException(errorMsg);
    }

    public List decodeMethodInput(ABIDefinition abiDefinition, String input)
            throws ContractCodecException {
        return this.decodeMethodAndGetInputObject(abiDefinition, input).getLeft();
    }

    @Deprecated
    public List decodeMethodInput(String abi, String methodName, String input)
            throws ContractCodecException {
        return this.decodeMethodAndGetInputObject(abi, methodName, input);
    }

    public List decodeMethodInputById(String abi, byte[] methodId, byte[] input)
            throws ContractCodecException {
        ContractABIDefinition contractABIDefinition = this.abiDefinitionFactory.loadABI(abi);
        ABIDefinition abiDefinition = contractABIDefinition.getABIDefinitionByMethodId(methodId);
        if (abiDefinition == null) {
            String errorMsg = " methodId " + Hex.toHexString(methodId) + " is invalid";
            logger.error(errorMsg);
            throw new ContractCodecException(errorMsg);
        }
        ABIObject inputObject = ABIObjectFactory.createInputObject(abiDefinition);
        try {
            return ContractCodecTools.decodeJavaObject(
                    inputObject, Hex.toHexString(input).substring(8), isWasm);
        } catch (Exception e) {
            logger.error(" exception in decodeMethodByIdToObject : {}", e.getMessage());
        }

        String errorMsg = " cannot decode in decodeMethodInputById with appropriate interface ABI";
        logger.error(errorMsg);
        throw new ContractCodecException(errorMsg);
    }

    public List decodeMethodInputByInterface(
            String abi, String methodInterface, byte[] input) throws ContractCodecException {
        byte[] methodId = functionEncoder.buildMethodId(methodInterface);
        return this.decodeMethodInputById(abi, methodId, input);
    }

    public List decodeMethodInputToString(String abi, String methodName, byte[] input)
            throws ContractCodecException {
        ContractABIDefinition contractABIDefinition = this.abiDefinitionFactory.loadABI(abi);
        List methods = contractABIDefinition.getFunctions().get(methodName);
        if (methods == null) {
            throw new ContractCodecException(
                    "Invalid method "
                            + methodName
                            + ", supported methods are: "
                            + contractABIDefinition.getFunctions().keySet());
        }
        for (ABIDefinition abiDefinition : methods) {
            ABIObject inputObject = ABIObjectFactory.createInputObject(abiDefinition);
            try {
                return contractCodecJsonWrapper.decode(
                        inputObject, Arrays.copyOfRange(input, 4, input.length), isWasm);
            } catch (Exception e) {
                logger.error(" exception in decodeMethodToString : {}", e.getMessage());
            }
        }

        String errorMsg =
                " cannot decode in decodeMethodInputToString with appropriate interface ABI";
        logger.error(errorMsg);
        throw new ContractCodecException(errorMsg);
    }

    public List decodeMethodInputByIdToString(String abi, byte[] methodId, byte[] input)
            throws ContractCodecException {
        ContractABIDefinition contractABIDefinition = this.abiDefinitionFactory.loadABI(abi);
        ABIDefinition abiDefinition = contractABIDefinition.getABIDefinitionByMethodId(methodId);
        if (abiDefinition == null) {
            String errorMsg = " methodId " + Hex.toHexString(methodId) + " is invalid";
            logger.error(errorMsg);
            throw new ContractCodecException(errorMsg);
        }
        ABIObject outputABIObject = ABIObjectFactory.createInputObject(abiDefinition);
        try {
            return contractCodecJsonWrapper.decode(
                    outputABIObject, Arrays.copyOfRange(input, 4, input.length), isWasm);
        } catch (UnsupportedOperationException | ClassNotFoundException e) {
            logger.error(" exception in decodeMethodInputByIdToString : {}", e.getMessage());
        }

        String errorMsg =
                " cannot decode in decodeMethodInputByIdToString with appropriate interface ABI";
        logger.error(errorMsg);
        throw new ContractCodecException(errorMsg);
    }

    public List decodeMethodInputByInterfaceToString(
            String abi, String methodInterface, byte[] input) throws ContractCodecException {
        byte[] methodId = functionEncoder.buildMethodId(methodInterface);
        return this.decodeMethodInputByIdToString(abi, methodId, input);
    }

    //  -------------------------------  decode input end   ---------------------------------------
    //  -------------------------------------------------------------------------------------------

    //  -------------------------------------------------------------------------------------------
    //  -------------------------------  decode output begin --------------------------------------

    public Pair, List> decodeMethodAndGetOutputObject(
            ABIDefinition abiDefinition, String output) throws ContractCodecException {
        ABIObject abiObject = decodeMethodAndGetOutAbiObjectByABIDefinition(abiDefinition, output);
        return ContractCodecTools.decodeJavaObjectAndGetOutputObject(abiObject);
    }

    public Pair, List> decodeMethodOutputAndGetObject(
            String abi, String methodName, String output) throws ContractCodecException {
        ABIObject abiObject = decodeMethodAndGetOutputAbiObject(abi, methodName, output);
        return ContractCodecTools.decodeJavaObjectAndGetOutputObject(abiObject);
    }

    public ABIObject decodeMethodAndGetOutAbiObjectByABIDefinition(
            ABIDefinition abiDefinition, String output) throws ContractCodecException {
        ABIObject outputABIObject = ABIObjectFactory.createOutputObject(abiDefinition);
        try {
            return ContractCodecTools.decode(outputABIObject, Hex.decode(output), isWasm);
        } catch (Exception e) {
            logger.error(" exception in decodeMethodAndGetAbiObjectByABIDefinition : ", e);
        }
        String errorMsg =
                " cannot decode in decodeMethodAndGetAbiObjectByABIDefinition with appropriate interface ABI";
        logger.error(errorMsg);
        throw new ContractCodecException(errorMsg);
    }

    public ABIObject decodeMethodAndGetOutputAbiObject(String abi, String methodName, String output)
            throws ContractCodecException {
        ContractABIDefinition contractABIDefinition = this.abiDefinitionFactory.loadABI(abi);
        List methods = contractABIDefinition.getFunctions().get(methodName);
        for (ABIDefinition abiDefinition : methods) {
            ABIObject outputABIObject = ABIObjectFactory.createOutputObject(abiDefinition);
            try {
                return ContractCodecTools.decode(outputABIObject, Hex.decode(output), isWasm);
            } catch (Exception e) {
                logger.error(
                        "exception in decodeMethodAndGetOutputAbiObject: {}, e:",
                        e.getMessage(),
                        e);
            }
        }

        String errorMsg =
                String.format(
                        "cannot decode in decodeMethodAndGetOutputAbiObject with appropriate interface ABI: methodName = %s",
                        methodName);
        logger.error(errorMsg);
        throw new ContractCodecException(errorMsg);
    }

    @Deprecated
    public List decodeMethodAndGetOutputObject(String abi, String methodName, String output)
            throws ContractCodecException {
        ContractABIDefinition contractABIDefinition = this.abiDefinitionFactory.loadABI(abi);
        List methods = contractABIDefinition.getFunctions().get(methodName);
        for (ABIDefinition abiDefinition : methods) {
            List outputs = abiDefinition.getOutputs();
            List> outputTypes = new ArrayList<>();
            try {
                for (ABIDefinition.NamedType namedType : outputs) {
                    outputTypes.add(TypeReference.makeTypeReference(namedType.getType(), false));
                }
                return this.functionReturnDecoder.decode(output, outputTypes);
            } catch (Exception e) {
                logger.error("exception in decodeMethodToObject: {}, e:", e.getMessage(), e);
            }
        }

        String errorMsg =
                String.format(
                        "cannot decode in decodeMethodToObject with appropriate interface ABI: methodName = %s",
                        methodName);
        logger.error(errorMsg);
        throw new ContractCodecException(errorMsg);
    }

    @Deprecated
    public List decodeMethodByABIDefinition(ABIDefinition abiDefinition, String output)
            throws ContractCodecException {
        List outputs = abiDefinition.getOutputs();
        List> outputTypes = new ArrayList<>();
        try {
            for (ABIDefinition.NamedType namedType : outputs) {
                outputTypes.add(TypeReference.makeTypeReference(namedType.getType(), false));
            }
            return this.functionReturnDecoder.decode(output, outputTypes);
        } catch (Exception e) {
            logger.error("exception in decodeMethodToObject: {}, e:", e.getMessage(), e);
        }
        String errorMsg =
                String.format(
                        "cannot decode in decodeMethodToObject with appropriate interface ABI: methodName = %s",
                        abiDefinition.getMethodSignatureAsString());
        logger.error(errorMsg);
        throw new ContractCodecException(errorMsg);
    }

    public List decodeMethod(ABIDefinition abiDefinition, String output)
            throws ContractCodecException {
        return this.decodeMethodAndGetOutputObject(abiDefinition, output).getLeft();
    }

    @Deprecated
    public List decodeMethod(String abi, String methodName, String output)
            throws ContractCodecException {
        return this.decodeMethodAndGetOutputObject(abi, methodName, output);
    }

    public List decodeMethodById(String abi, byte[] methodId, byte[] output)
            throws ContractCodecException {
        ContractABIDefinition contractABIDefinition = this.abiDefinitionFactory.loadABI(abi);
        ABIDefinition abiDefinition = contractABIDefinition.getABIDefinitionByMethodId(methodId);
        if (abiDefinition == null) {
            String errorMsg = " methodId " + Hex.toHexString(methodId) + " is invalid";
            logger.error(errorMsg);
            throw new ContractCodecException(errorMsg);
        }
        ABIObject outputABIObject = ABIObjectFactory.createOutputObject(abiDefinition);
        try {
            return ContractCodecTools.decodeJavaObject(
                    outputABIObject, Hex.toHexString(output), isWasm);
        } catch (Exception e) {
            logger.error(" exception in decodeMethodByIdToObject : {}", e.getMessage());
        }

        String errorMsg = " cannot decode in decodeMethodToObject with appropriate interface ABI";
        logger.error(errorMsg);
        throw new ContractCodecException(errorMsg);
    }

    public List decodeMethodByInterface(String abi, String methodInterface, byte[] output)
            throws ContractCodecException {
        byte[] methodId = functionEncoder.buildMethodId(methodInterface);
        return this.decodeMethodById(abi, methodId, output);
    }

    public List decodeMethodToString(String abi, String methodName, byte[] output)
            throws ContractCodecException {
        ContractABIDefinition contractABIDefinition = this.abiDefinitionFactory.loadABI(abi);
        List methods = contractABIDefinition.getFunctions().get(methodName);
        if (methods == null) {
            throw new ContractCodecException(
                    "Invalid method "
                            + methodName
                            + ", supported methods are: "
                            + contractABIDefinition.getFunctions().keySet());
        }
        for (ABIDefinition abiDefinition : methods) {
            ABIObject outputABIObject = ABIObjectFactory.createOutputObject(abiDefinition);
            try {
                return contractCodecJsonWrapper.decode(outputABIObject, output, isWasm);
            } catch (Exception e) {
                logger.error(" exception in decodeMethodToString : {}", e.getMessage());
            }
        }

        String errorMsg = " cannot decode in decodeMethodToString with appropriate interface ABI";
        logger.error(errorMsg);
        throw new ContractCodecException(errorMsg);
    }

    public List decodeMethodByIdToString(String abi, byte[] methodId, byte[] output)
            throws ContractCodecException {
        ContractABIDefinition contractABIDefinition = this.abiDefinitionFactory.loadABI(abi);
        ABIDefinition abiDefinition = contractABIDefinition.getABIDefinitionByMethodId(methodId);
        if (abiDefinition == null) {
            String errorMsg = " methodId " + Hex.toHexString(methodId) + " is invalid";
            logger.error(errorMsg);
            throw new ContractCodecException(errorMsg);
        }
        ABIObject outputABIObject = ABIObjectFactory.createOutputObject(abiDefinition);
        try {
            return contractCodecJsonWrapper.decode(outputABIObject, output, isWasm);
        } catch (UnsupportedOperationException | ClassNotFoundException e) {
            logger.error(" exception in decodeMethodByIdToString : {}", e.getMessage());
        }

        String errorMsg =
                " cannot decode in decodeMethodByIdToString with appropriate interface ABI";
        logger.error(errorMsg);
        throw new ContractCodecException(errorMsg);
    }

    public List decodeMethodByInterfaceToString(
            String abi, String methodInterface, byte[] output) throws ContractCodecException {
        byte[] methodId = functionEncoder.buildMethodId(methodInterface);
        return this.decodeMethodByIdToString(abi, methodId, output);
    }

    //  -------------------------------  decode output end   --------------------------------------
    //  -------------------------------------------------------------------------------------------

    public List decodeEvent(String abi, String eventName, EventLog log)
            throws ContractCodecException {
        ContractABIDefinition contractABIDefinition = this.abiDefinitionFactory.loadABI(abi);
        List events = contractABIDefinition.getEvents().get(eventName);
        if (events == null) {
            throw new ContractCodecException(
                    "Invalid event "
                            + eventName
                            + ", supported events are: "
                            + contractABIDefinition.getEvents().keySet());
        }
        for (ABIDefinition abiDefinition : events) {
            ABIObject inputObject = ABIObjectFactory.createEventInputObject(abiDefinition);
            try {
                List params = new ArrayList<>();
                if (!log.getData().equals("0x")) {
                    params =
                            ContractCodecTools.decodeJavaObject(inputObject, log.getData(), isWasm);
                }
                List topics = decodeIndexedEvent(log, abiDefinition);
                return this.mergeEventParamsAndTopics(abiDefinition, params, topics);
            } catch (Exception e) {
                logger.error(" exception in decodeEventToObject : {}", e.getMessage());
            }
        }

        String errorMsg = " cannot decode in decodeEventToObject with appropriate interface ABI";
        logger.error(errorMsg);
        throw new ContractCodecException(errorMsg);
    }

    public List decodeIndexedEvent(EventLog log, ABIDefinition abiDefinition)
            throws ClassNotFoundException {
        List eventIndexedObject =
                ABIObjectFactory.createEventIndexedObject(abiDefinition);
        List topics = new ArrayList<>();
        if (!log.getTopics().isEmpty()) {
            topics.add(log.getTopics().get(0));
            for (int i = 1; i < log.getTopics().size(); i++) {
                ABIObject indexedObject = eventIndexedObject.get(i - 1);
                if (indexedObject.isDynamic()) {
                    topics.add(log.getTopics().get(i));
                } else {
                    List objects =
                            contractCodecJsonWrapper.decode(
                                    indexedObject, Hex.decode(log.getTopics().get(i)), isWasm);
                    if (!objects.isEmpty()) {
                        topics.add(objects.get(0));
                    }
                }
            }
        }
        return topics;
    }

    public List decodeEventByTopic(String abi, String eventTopic, EventLog log)
            throws ContractCodecException {
        ContractABIDefinition contractABIDefinition = this.abiDefinitionFactory.loadABI(abi);
        ABIDefinition abiDefinition =
                contractABIDefinition.getABIDefinitionByEventTopic(eventTopic);
        ABIObject inputObject = ABIObjectFactory.createEventInputObject(abiDefinition);
        try {
            List params = new ArrayList<>();
            if (!log.getData().equals("0x")) {
                params = ContractCodecTools.decodeJavaObject(inputObject, log.getData(), isWasm);
            }
            List topics = decodeIndexedEvent(log, abiDefinition);
            return this.mergeEventParamsAndTopics(abiDefinition, params, topics);
        } catch (Exception e) {
            logger.error(" exception in decodeEventByTopicToObject : {}", e.getMessage());
        }

        String errorMsg =
                " cannot decode in decodeEventByTopicToObject with appropriate interface ABI";
        logger.error(errorMsg);
        throw new ContractCodecException(errorMsg);
    }

    public List decodeEventByInterface(String abi, String eventSignature, EventLog log)
            throws ContractCodecException {
        byte[] methodId = functionEncoder.buildMethodId(eventSignature);
        return this.decodeEventByTopic(abi, Numeric.toHexString(methodId), log);
    }

    public List decodeEventToString(String abi, String eventName, EventLog log)
            throws ContractCodecException {
        ContractABIDefinition contractABIDefinition = this.abiDefinitionFactory.loadABI(abi);
        List events = contractABIDefinition.getEvents().get(eventName);
        if (events == null) {
            throw new ContractCodecException(
                    "Invalid event "
                            + eventName
                            + ", current supported events are: "
                            + contractABIDefinition.getEvents().keySet());
        }
        for (ABIDefinition abiDefinition : events) {
            ABIObject inputObject = ABIObjectFactory.createEventInputObject(abiDefinition);
            try {
                List params = new ArrayList<>();
                if (!log.getData().equals("0x")) {
                    params =
                            contractCodecJsonWrapper.decode(
                                    inputObject, Hex.decode(log.getData()), isWasm);
                }
                List topics = decodeIndexedEvent(log, abiDefinition);
                return this.mergeEventParamsAndTopicsToString(abiDefinition, params, topics);
            } catch (Exception e) {
                logger.error(" exception in decodeEventToString : {}", e.getMessage());
            }
        }

        String errorMsg = " cannot decode in decodeEventToString with appropriate interface ABI";
        logger.error(errorMsg);
        throw new ContractCodecException(errorMsg);
    }

    public List decodeEventByTopicToString(String abi, String eventTopic, EventLog log)
            throws ContractCodecException {
        ContractABIDefinition contractABIDefinition = this.abiDefinitionFactory.loadABI(abi);
        ABIDefinition abiDefinition =
                contractABIDefinition.getABIDefinitionByEventTopic(eventTopic);
        ABIObject inputObject = ABIObjectFactory.createEventInputObject(abiDefinition);
        try {
            List params = new ArrayList<>();
            if (!log.getData().equals("0x")) {
                params =
                        contractCodecJsonWrapper.decode(
                                inputObject, Hex.decode(log.getData()), isWasm);
            }
            List topics = decodeIndexedEvent(log, abiDefinition);
            return this.mergeEventParamsAndTopicsToString(abiDefinition, params, topics);
        } catch (Exception e) {
            logger.error(" exception in decodeEventByTopicToString : {}", e.getMessage());
        }

        String errorMsg =
                " cannot decode in decodeEventByTopicToString with appropriate interface ABI";
        logger.error(errorMsg);
        throw new ContractCodecException(errorMsg);
    }

    public List decodeEventByInterfaceToString(
            String abi, String eventSignature, EventLog log) throws ContractCodecException {
        byte[] methodId = functionEncoder.buildMethodId(eventSignature);
        return this.decodeEventByTopicToString(abi, Numeric.toHexString(methodId), log);
    }

    private List mergeEventParamsAndTopics(
            ABIDefinition abiDefinition, List params, List topics) {
        List ret = new ArrayList<>();
        int paramIdx = 0;
        int topicIdx = 1;
        for (ABIDefinition.NamedType namedType : abiDefinition.getInputs()) {
            if (namedType.isIndexed()) {
                ret.add(topics.get(topicIdx));
                topicIdx++;
            } else {
                ret.add(params.get(paramIdx));
                paramIdx++;
            }
        }
        return ret;
    }

    private List mergeEventParamsAndTopicsToString(
            ABIDefinition abiDefinition, List params, List topics) {
        List ret = new ArrayList<>();
        int paramIdx = 0;
        int topicIdx = 1;
        for (ABIDefinition.NamedType namedType : abiDefinition.getInputs()) {
            if (namedType.isIndexed()) {
                ret.add(topics.get(topicIdx));
                topicIdx++;
            } else {
                ret.add(params.get(paramIdx));
                paramIdx++;
            }
        }
        return ret;
    }
}