Parameter encoding and return value

Parameter encoding and decoding

Introduce the encoding and decoding of parameters passed when invoking smart contracts in the Vision Network.

When calling a smart contract through the HTTP interface triggersmartcontract, the encoded parameter needs to be passed. Here we use the VRC20-VST contract as an example to provide javascript code to show developers how to encode the parameters. For a detailed description of the parameter encoding and decoding in solidity, please refer to the solidity documentation.

Parameter encoding

Take the transfer function in the VRC20-VST contract as an example:

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

Assuming that 50,000 VDT is transferred to the address 46b03b026cdeb86dc1c50d13f55a8ff5650eb252ad, the triggersmartcontract interface called is as follows:

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}'

In the above script, the parameter encoding needs to be carried out according to the ABI rules. The rules are more complicated. We can use the ethers library for encoding. The sample code is as follows:

// Parameter transcoding code
// It is recommended to use ethers4.0.47 version
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);
    }
    
		// Additional output parameters involved in encryption
    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();

The sample code outputs the parameters involved in encryption and the encryption result for easy verification

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

Parameter decoding

The invoked triggersmartcontract generates the transaction object, and then signs and broadcasts it. After the transaction is successfully connected to the chain, the transaction information on the chain can be obtained through gettransactionbyid:

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

The results obtained

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

The raw_data.contract[0].parameter.value.data field in the return value above is the parameter to call the transfer(address to, uint256 value) function, but the data field is different from the parameter field of triggersmartcontract described in the parameter encoding section , There are 4 bytes a9059cbb in front. These 4 bytes are the method ID, which is derived from the first 4 bytes of the Keccak hash signed by transfer(address,uint256) in ASCII format, which is used by the virtual machine to function Addressing.

var ethers = require('ethers')
const AbiCoder = ethers.utils.AbiCoder;
const ADDRESS_PREFIX_REGEX = /^(46)/;
const ADDRESS_PREFIX = "46";

//types: parameter type list, if the function has multiple return values, the order of the types in the list should conform to the defined order
//output: data before decoding
//ignoreMethodHash: decode the function return value, ignoreMethodHash fill in false, if it is to decode the data field in the gettransactionbyid result, ignoreMethodHash fill in 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();

got the answer

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