本文简要介绍了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
交易为例。这笔交易是由TVw7mwc6vg9BxouG98Z2cS1tiwEvhx45TH
向TMdLLovxwsPqxqjMSwD3gw2mNfw2AzLQp9
转账1JST。
交易的data如下
final String DATA = "a9059cbb0000000000000000000000007fdf5157514bf89ffcb7ff36f34772afd4cdc7440000000000000000000000000000000000000000000000000de0b6b3a7640000";
data的前四个字节是函数选择器,由Keccak-256得来,无法逆向推导,可以通过两种方法获取函数签名:
-
如果可以获取到合约ABI,可以计算每个合约函数的选择器,并与data的前四个字节比对来判断函数
-
由合约生成的合约链上可能没有ABI,合约部署者也可以通过clearAbi接口来清除链上ABI。当无法获取到ABI时,可尝试通过Ethereum Signature Database来查询在数据库中的函数
由上述方法得到,0xa9059cbb
是transfer(address,uint256)
的函数选择器。每个静态参数的编码占32字节,data中的内容共分三段:
-
DATA.substring(0,8)
是函数选择器 -
DATA.substring(8,72)
是收款人地址 -
DATA.substring(72,136)
是发送金额
可以根据参数的类型,在org.tron.trident.abi.TypeDecoder
中选择合适的方法来对参数进行解码。本例中的两个参数分别为address
和uint256
类型,因此选择decodeAddress
和decodeNumeric
。
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();
}