参数编码和解码

本文简要介绍了TRON网络中调用和部署智能合约时的编/解码规则。

通过core/contract/Contract.deploy()部署合约,或通过ApiWrapper.constantCall()调用合约时,需要对构造函数的参数进行编码,或是对constant result进行解码。本文将用一个HelloWorld合约展示参数编码,并以测试网JST为例,展示如何使用trident进行解码。

要了解详细的ABI编码规则,请参考ABI编码规则,或Solidity文档

参数编码

部署合约需要用到bytecode以及abi,部署流程请参照部署智能合约

当合约有需要传参的构造函数时,在部署时需要对参数进行编码,并将这部分编码添加在合约的bytecode之后。在core.contract.Contract.deploy(List<Type> buildParams)中已经包含了对参数的编码,此处仅为介绍编码方法。

以下面的HelloWorld合约为例

//SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

contract HelloWorld {
    string public greeting;
    
    constructor(string memory message) {
        greeting = message;
    }
}

这个合约的bytecode是

608060405234801561001057600080fd5b5060405161055738038061055783398181016040528101906100329190610162565b806000908051906020019061004892919061004f565b505061031a565b82805461005b90610234565b90600052602060002090601f01602090048101928261007d57600085556100c4565b82601f1061009657805160ff19168380011785556100c4565b828001600101855582156100c4579182015b828111156100c35782518255916020019190600101906100a8565b5b5090506100d191906100d5565b5090565b5b808211156100ee5760008160009055506001016100d6565b5090565b6000610105610100846101d0565b6101ab565b905082815260208101848484011115610121576101206102fa565b5b61012c848285610201565b509392505050565b600082601f830112610149576101486102f5565b5b81516101598482602086016100f2565b91505092915050565b60006020828403121561017857610177610304565b5b600082015167ffffffffffffffff811115610196576101956102ff565b5b6101a284828501610134565b91505092915050565b60006101b56101c6565b90506101c18282610266565b919050565b6000604051905090565b600067ffffffffffffffff8211156101eb576101ea6102c6565b5b6101f482610309565b9050602081019050919050565b60005b8381101561021f578082015181840152602081019050610204565b8381111561022e576000848401525b50505050565b6000600282049050600182168061024c57607f821691505b602082108114156102605761025f610297565b5b50919050565b61026f82610309565b810181811067ffffffffffffffff8211171561028e5761028d6102c6565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b61022e806103296000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063ef690cc014610030575b600080fd5b61003861004e565b6040516100459190610115565b60405180910390f35b6000805461005b90610186565b80601f016020809104026020016040519081016040528092919081815260200182805461008790610186565b80156100d45780601f106100a9576101008083540402835291602001916100d4565b820191906000526020600020905b8154815290600101906020018083116100b757829003601f168201915b505050505081565b60006100e782610137565b6100f18185610142565b9350610101818560208601610153565b61010a816101e7565b840191505092915050565b6000602082019050818103600083015261012f81846100dc565b905092915050565b600081519050919050565b600082825260208201905092915050565b60005b83811015610171578082015181840152602081019050610156565b83811115610180576000848401525b50505050565b6000600282049050600182168061019e57607f821691505b602082108114156101b2576101b16101b8565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000601f19601f830116905091905056fea2646970667358221220c20290a76e91cdb989a85d989077e25cce8b2192b57853b4969cc353628bb16964736f6c63430008060033

将greeting的值定为“Hello”,需要将其编码并转为十六进制。trident中已经写好了编码的过程,指定参数类型并传入即可。参数的类型在org.tron.trident.abi.datatypes中,请根据参数类型选择合适的对应java类。

在本例中,参数类型是String,对应的类型是org.tron.trident.abi.datatypes.Utf8String

List params = new ArrayList<org.tron.trident.abi.datatypes.Type>();
params.add(new org.tron.trident.abi.datatypes.Utf8String("Hello"));
TransactionBuilder builder = cntr.deploy(params);

这样就完成了编码的过程。完整的部署流程还包括feelimit的设置、签名以及广播。完整的演示代码如下

public static void deployTutorial() {
        try {
            String bytecode = "608060405234801561001057600080fd5b5060405161055738038061055783398181016040528101906100329190610162565b806000908051906020019061004892919061004f565b505061031a565b82805461005b90610234565b90600052602060002090601f01602090048101928261007d57600085556100c4565b82601f1061009657805160ff19168380011785556100c4565b828001600101855582156100c4579182015b828111156100c35782518255916020019190600101906100a8565b5b5090506100d191906100d5565b5090565b5b808211156100ee5760008160009055506001016100d6565b5090565b6000610105610100846101d0565b6101ab565b905082815260208101848484011115610121576101206102fa565b5b61012c848285610201565b509392505050565b600082601f830112610149576101486102f5565b5b81516101598482602086016100f2565b91505092915050565b60006020828403121561017857610177610304565b5b600082015167ffffffffffffffff811115610196576101956102ff565b5b6101a284828501610134565b91505092915050565b60006101b56101c6565b90506101c18282610266565b919050565b6000604051905090565b600067ffffffffffffffff8211156101eb576101ea6102c6565b5b6101f482610309565b9050602081019050919050565b60005b8381101561021f578082015181840152602081019050610204565b8381111561022e576000848401525b50505050565b6000600282049050600182168061024c57607f821691505b602082108114156102605761025f610297565b5b50919050565b61026f82610309565b810181811067ffffffffffffffff8211171561028e5761028d6102c6565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b61022e806103296000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063ef690cc014610030575b600080fd5b61003861004e565b6040516100459190610115565b60405180910390f35b6000805461005b90610186565b80601f016020809104026020016040519081016040528092919081815260200182805461008790610186565b80156100d45780601f106100a9576101008083540402835291602001916100d4565b820191906000526020600020905b8154815290600101906020018083116100b757829003601f168201915b505050505081565b60006100e782610137565b6100f18185610142565b9350610101818560208601610153565b61010a816101e7565b840191505092915050565b6000602082019050818103600083015261012f81846100dc565b905092915050565b600081519050919050565b600082825260208201905092915050565b60005b83811015610171578082015181840152602081019050610156565b83811115610180576000848401525b50505050565b6000600282049050600182168061019e57607f821691505b602082108114156101b2576101b16101b8565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000601f19601f830116905091905056fea2646970667358221220c20290a76e91cdb989a85d989077e25cce8b2192b57853b4969cc353628bb16964736f6c63430008060033";
            //Please add "entrys" before the compiled bytecode,
            //and add escape characters in the json string
            String abi = "{\"entrys\":[{\"inputs\":[{\"internalType\":\"string\",\"name\":\"message\",\"type\":\"string\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"greeting\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]}";
            Contract cntr = new Contract.Builder()
                                    .setOwnerAddr(aw.parseAddress("your address"))
                                    .setOriginAddr(aw.parseAddress("your address"))
                                    .setBytecode(ByteString.copyFrom(Numeric.hexStringToByteArray(bytecode)))
                                    .setAbi(abi)
                                    .build();
            
            cntr.setWrapper(aw); //set ApiWrapper with your private key
            List params = new ArrayList<Type>();
            params.add(new Utf8String("Hello")); //encode params
            TransactionBuilder builder = cntr.deploy(params); //set params
            builder.setFeeLimit(1000000000L); //set feelimit
            Transaction signedTxn = aw.signTransaction(builder.build()); //sign transaction
            String ret = aw.broadcastTransaction(signedTxn); //broadcast transaction
            System.out.println("======== Result ========\n" + ret); //print the transaction hash, only for display
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

参数解码

以Nile测试网上9e726b75b4c681cce6bb439df539c0ebb4800b911539780476448fd127c8b9a1交易为例。这笔交易是由TVw7mwc6vg9BxouG98Z2cS1tiwEvhx45THTMdLLovxwsPqxqjMSwD3gw2mNfw2AzLQp9转账1JST。

交易的data如下

final String DATA = "a9059cbb0000000000000000000000007fdf5157514bf89ffcb7ff36f34772afd4cdc7440000000000000000000000000000000000000000000000000de0b6b3a7640000";

data的前四个字节是函数选择器,由Keccak-256得来,无法逆向推导,可以通过两种方法获取函数签名:

  • 如果可以获取到合约ABI,可以计算每个合约函数的选择器,并与data的前四个字节比对来判断函数

  • 由合约生成的合约链上可能没有ABI,合约部署者也可以通过clearAbi接口来清除链上ABI。当无法获取到ABI时,可尝试通过Ethereum Signature Database来查询在数据库中的函数

由上述方法得到,0xa9059cbbtransfer(address,uint256)的函数选择器。每个静态参数的编码占32字节,data中的内容共分三段:

  • DATA.substring(0,8)是函数选择器

  • DATA.substring(8,72)是收款人地址

  • DATA.substring(72,136)是发送金额

可以根据参数的类型,在org.tron.trident.abi.TypeDecoder中选择合适的方法来对参数进行解码。本例中的两个参数分别为addressuint256类型,因此选择decodeAddressdecodeNumeric

final String DATA = "a9059cbb0000000000000000000000007fdf5157514bf89ffcb7ff36f34772afd4cdc7440000000000000000000000000000000000000000000000000de0b6b3a7640000";

public void dataDecodingTutorial() {
        String rawSignature = DATA.substring(0,8);
        String signature = "transfer(address,uint256)"; //function signature
        Address rawRecipient = TypeDecoder.decodeAddress(DATA.substring(8,72)); //recipient address
        String recipient = rawRecipient.toString();
        Uint256 rawAmount = TypeDecoder.decodeNumeric(DATA.substring(72,136), Uint256.class); //amount
        BigInteger amount = rawAmount.getValue();

        System.out.println(signature);
        System.out.println("Transfer " + amount + " to " + recipient);
    }

返回值解码

此处以查询地址TVw7mwc6vg9BxouG98Z2cS1tiwEvhx45TH的JST余额(balanceOf)为例,查询的constant result如下

“result”: {
        “result”: true
    },
    “energy_used”: 604,
    “constant_result”: [
        “000000000000000000000000000000000000000000000038ddcca628e8c40000”
    ]

在trident中,constant调用返回一个TransactionExtention对象,其中constant_result字段是一个List<ByteString>。在解析的过程中,须先构造一个org.tron.trident.abi.datatypes.Function对象,这个对象中指定了函数的返回类型,在org.tron.trident.abi.FunctionReturnDecoder中指定这个类型,就可以将constant_result转换为指定类型的对象。

构建Function对象需要三个参数:函数名、入参和出参。详见Function代码

trident在org.tron.trident.core.contract.Trc20Contract中对TRC-20的标准方法进行了封装处理,其中的只读函数包含了解码的过程,可以直接输出可读的结果。此处以Trc20Contract.balanceOf()为例,展示返回值的解码操作。

public BigInteger balanceOf(String accountAddr) {
        //construct the funtion
        Function balanceOf = new Function("balanceOf",
                Arrays.asList(new Address(accountAddr)), Arrays.asList(new TypeReference<Uint256>() {}));
        //call the function
        TransactionExtention txnExt = wrapper.constantCall(Base58Check.bytesToBase58(ownerAddr.toByteArray()), 
                Base58Check.bytesToBase58(cntrAddr.toByteArray()), balanceOf);
        //Convert constant result to human readable text
        String result = Numeric.toHexString(txnExt.getConstantResult(0).toByteArray());
        return (BigInteger)FunctionReturnDecoder.decode(result, balanceOf.getOutputParameters()).get(0).getValue();
      }