参数编码与返回值

参数编码与解码

介绍Vision Network中调用智能合约时传递参数的编码及解码。

在通过HTTP接口triggersmartcontract调用智能合约时,需要传递编码后的parameter,在此我们通过VRC20-VST合约为例,提供javascript代码向开发者展示参数如何进行编码。solidity中对参数编解码详细说明请参考solidity文档

参数编码

以VRC20-VST合约中的转账函数为例:

function transfer(address to, uint256 value) public returns (bool);

假设给地址46b03b026cdeb86dc1c50d13f55a8ff5650eb252ad 转账 50000 VDT,调用的triggersmartcontract接口如下:

curl --request POST \
  --url https://vtest.infragrid.v.network/wallet/triggersmartcontract \
  --header 'Content-Type: application/json' \
  --data '{"contract_address":"4697bcc10fc1013976431eaef5ae1d2ca2f556b7d8",
  "owner_address":"46b03b026cdeb86dc1c50d13f55a8ff5650eb252ad",
  "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 = /^(46)/;
const ADDRESS_PREFIX = "46";

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: "46b03b026cdeb86dc1c50d13f55a8ff5650eb252ad"},
        {type: 'uint256', value: 100000000}
    ]
    let parameters = await encodeParams(inputs)
    console.log(parameters)
}
main();

示例代码输出参与加密的参数 以及 加密结果,方便核对

[ 'address', 'uint256' ] [ '0xb03b026cdeb86dc1c50d13f55a8ff5650eb252ad', 100000000 ]
000000000000000000000000b03b026cdeb86dc1c50d13f55a8ff5650eb252ad0000000000000000000000000000000000000000000000000000000005f5e100

参数解码

调用的triggersmartcontract生成交易对象,然后签名广播,交易成功上链后,可以通过gettransactionbyid获取链上的交易信息:

curl --request POST \
  --url https://vtest.infragrid.v.network/wallet/gettransactionbyid \
  --header 'Content-Type: application/json' \
  --data '{"value":"29a72205a1dcba5142b72ad2daf9b7deb63be8a42da09fa6863d05df4932176c"}'

得到的结果

  {
    "ret": [
        {
            "contractRet": "SUCCESS"
        }
    ],
    ..........
    "raw_data": {
        "contract": [
            {
                "parameter": {
                    "value": {
                        "data": "a9059cbb0000000000000000000000002ed5dd8a98aea00ae32517742ea5289761b2710e0000000000000000000000000000000000000000000000000000000ba43b7400",
                        "owner_address": "46b03b026cdeb86dc1c50d13f55a8ff5650eb252ad",
                        "contract_address": "4697bcc10fc1013976431eaef5ae1d2ca2f556b7d8"
                    },
                    "type_url": "type.googleapis.com/protocol.TriggerSmartContract"
                },
    ..........
}

上面返回值中的的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 = /^(46)/;
const ADDRESS_PREFIX = "46";

//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 = 'a9059cbb0000000000000000000000002ed5dd8a98aea00ae32517742ea5289761b2710e0000000000000000000000000000000000000000000000000000000ba43b7400'

    result = await decodeParams(['address', 'uint256'], data, true)
    console.log(result)
}
main();

得到结果

[ '462ed5dd8a98aea00ae32517742ea5289761b2710e',
  BigNumber { _hex: '0x0ba43b7400', _isBigNumber: true } ]