参数编码和解码
本节的主要内容是Tron网络中调用智能合约时传递参数的编码及解码介绍。
在通过HTTP接口triggersmartcontract调用智能合约时,需要传递编码后的parameter,在此我们通过USDT合约为例,提供javascript代码向开发者展示参数如何进行编码。具体有关solidity中参数编解码详细请参考[solidity文档]及[tronweb相关代码]。
参数编码
我们以USDT合约中的转账函数为例:
function transfer(address to, uint256 value) public returns (bool);
假设给地址412ed5dd8a98aea00ae32517742ea5289761b2710e转账50000 USDT,调用的triggersmartcontract接口如下:
curl -X POST \
-d '{
"contract_address":"412dd04f7b26176aa130823bcc67449d1f451eb98f",
"owner_address":"411fafb1e96dfe4f609e2259bfaf8c77b60c535b93",
"function_selector":"transfer(address,uint256)",
"parameter":"0000000000000000000000002ed5dd8a98aea00ae32517742ea5289761b2710e0000000000000000000000000000000000000000000000000000000ba43b7400",
"call_value":0,
"fee_limit":1000000000,
"call_token_value":0,
"token_id":0
}'
上面的命令中,parameter的编码需要根据ABI规则进行,规则比较复杂, 用户可以利用ethers库的进行编码,下面是示例代码:
//建议使用ethers4.0.47版本
var ethers = require('ethers')
const AbiCoder = ethers.utils.AbiCoder;
const ADDRESS_PREFIX_REGEX = /^(41)/;
const ADDRESS_PREFIX = "41";
async function encodeParams(inputs){
let typesValues = inputs
let parameters = ''
if (typesValues.length == 0)
return parameters
const abiCoder = new AbiCoder();
let types = [];
const values = [];
for (let i = 0; i < typesValues.length; i++) {
let {type, value} = typesValues[i];
if (type == 'address')
value = value.replace(ADDRESS_PREFIX_REGEX, '0x');
else if (type == 'address[]')
value = value.map(v => toHex(v).replace(ADDRESS_PREFIX_REGEX, '0x'));
types.push(type);
values.push(value);
}
console.log(types, values)
try {
parameters = abiCoder.encode(types, values).replace(/^(0x)/, '');
} catch (ex) {
console.log(ex);
}
return parameters
}
async function main() {
let inputs = [
{type: 'address', value: "412ed5dd8a98aea00ae32517742ea5289761b2710e"},
{type: 'uint256', value: 50000000000}
]
let parameters = await encodeParams(inputs)
console.log(parameters)
main()
示例代码输出:
0000000000000000000000002ed5dd8a98aea00ae32517742ea5289761b2710e0000000000000000000000000000000000000000000000000000000ba43b7400
#参数解码
HTTP接口
上面参数的编码小节,调用的triggersmartcontract生成交易对象,然后签名广播,交易成功上链后,可以通过gettransactionbyid获取链上的交易信息:
curl -X POST \
https://api.trongrid.io/wallet/gettransactionbyid \
-d '{"value" : "1472178f0845f0bfb15957059f3fe9c791e7e039f449c3d5a843aafbc8bbdeeb"}'
返回结果如下:
{
"ret": [
{
"contractRet": "SUCCESS"
}
],
..........
"raw_data": {
"contract": [
{
"parameter": {
"value": {
"data": "a9059cbb0000000000000000000000002ed5dd8a98aea00ae32517742ea5289761b2710e0000000000000000000000000000000000000000000000000000000ba43b7400",
"owner_address": "418a4a39b0e62a091608e9631ffd19427d2d338dbd",
"contract_address": "41a614f803b6fd780986a42c78ec9c7f77e6ded13c"
},
"type_url": "type.googleapis.com/protocol.TriggerSmartContract"
},
..........
}
RPC接口
同样以交易1472178f0845f0bfb15957059f3fe9c791e7e039f449c3d5a843aafbc8bbdeeb
为例,TriggerSmartContract
包含在Transaction.rawData.contract[0].parameter
中。parameter
的类型是com.google.protobuf.Any,需要先对其进行解码,以得到参数。
交易的parameter
内容如下:
{
type_url: "type.googleapis.com/protocol.TriggerSmartContract"
value: 0a15418a4a39b0e62a091608e9631ffd19427d2d338dbd121541a614f803b6fd780986a42c78ec9c7f77e6ded13c2244a9059cbb0000000000000000000000002ed5dd8a98aea00ae32517742ea5289761b2710e0000000000000000000000000000000000000000000000000000000ba43b7400
}
用Any.unpack(TriggerSmartContract.class)
对其进行解码,可以得到TriggerSmartContract
对象,再获取其data
字段,便可获取到与上面HTTP接口相同的结果。
示例:
Any param = transaction.getRawData().getContractList().get(0).getParameter();
TriggerSmartContract contract = param.unpack(TriggerSmartContract.class);
ByteString data = contract.getData();
此时data
的将内容即为a9059cbb0000000000000000000000002ed5dd8a98aea00ae32517742ea5289761b2710e0000000000000000000000000000000000000000000000000000000ba43b7400
解码
上面返回值中的的raw_data.contract[0].parameter.value.data字段就是调用transfer(address to, uint256 value) 函数的参数,但是data字段和参数的编码小节描述的triggersmartcontract的parameter字段并不一样,前面还多了a9059cbb这4个字节,这4个字节是方法ID,这源自ASCII格式的 transfer(address,uint256) 签名的 Keccak 哈希的前 4 字节,用于虚拟机对函数的寻址。
下面代码是对data字段进行解码,获取出transfer函数传递的参数:
var ethers = require('ethers')
const AbiCoder = ethers.utils.AbiCoder;
const ADDRESS_PREFIX_REGEX = /^(41)/;
const ADDRESS_PREFIX = "41";
//types:参数类型列表,如果函数有多个返回值,列表中类型的顺序应该符合定义的顺序
//output: 解码前的数据
//ignoreMethodHash:对函数返回值解码,ignoreMethodHash填写false,如果是对gettransactionbyid结果中的data字段解码时,ignoreMethodHash填写true
async function decodeParams(types, output, ignoreMethodHash) {
if (!output || typeof output === 'boolean') {
ignoreMethodHash = output;
output = types;
}
if (ignoreMethodHash && output.replace(/^0x/, '').length % 64 === 8)
output = '0x' + output.replace(/^0x/, '').substring(8);
const abiCoder = new AbiCoder();
if (output.replace(/^0x/, '').length % 64)
throw new Error('The encoded string is not valid. Its length must be a multiple of 64.');
return abiCoder.decode(types, output).reduce((obj, arg, index) => {
if (types[index] == 'address')
arg = ADDRESS_PREFIX + arg.substr(2).toLowerCase();
obj.push(arg);
return obj;
}, []);
}
async function main() {
let data = '0xa9059cbb0000000000000000000000004f53238d40e1a3cb8752a2be81f053e266d9ecab000000000000000000000000000000000000000000000000000000024dba7580'
result = await decodeParams(['address', 'uint256'], data, true)
console.log(result)
}
示例代码输出:
[ '414f53238d40e1a3cb8752a2be81f053e266d9ecab', BigNumber { _hex: '0x024dba7580' } ]
注意decodeParams函数的第三个参数ignoreMethodHash必须设置为true,目的是让decodeParams函数跳过前4字节,对后面的内容进行解码。
返回值参数解码
我们以USDT合约中的查询函数为例:
balanceOf(address who) public constant returns (uint)
假设查询410583A68A3BCD86C25AB1BEE482BAC04A216B0261的余额,调用的triggersmartcontract接口如下:
curl -X POST \
https://api.trongrid.io/wallet/triggersmartcontract \
-d '{
"contract_address": "41a614f803b6fd780986a42c78ec9c7f77e6ded13c",
"owner_address": "411fafb1e96dfe4f609e2259bfaf8c77b60c535b93",
"function_selector": "balanceOf(address)",
"parameter": "0000000000000000000000000583a68a3bcd86c25ab1bee482bac04a216b0261",
"call_value": 0,
"fee_limit": 1000000000,
"call_token_value": 0,
"token_id": 0
}'
返回结果如下:
{
"result": {
"result": true
},
"constant_result": [
"000000000000000000000000000000000000000000000000000196ca228159aa"
],
............
上面的返回值中constant_result就是balanceOf的返回值,下面是对constant_result解码的示例代码:
async function main() {
//必须是0x开头
let outputs = '0x000000000000000000000000000000000000000000000000000196ca228159aa'
//
//['uint256']是返回值类型的列表,如果有多个返回值,在按照顺序填写类型
result = await decodeParams(['uint256'], outputs, false)
console.log(result)
}
示例代码输出:
[ BigNumber { _hex: '0x0196ca228159aa' } ]
Updated 12 months ago