+-
使用ABI解码以太坊Transaction input数据,减少gas free

在写智能合约的时候,之前我的习惯是把需要查询的数据记录在合约的event中,这些event的字段有些就是方法的入参。虽然以太坊log数据gas free相对较少,但积累起来就可观了。经过思考后,我给自己定了一些规则:

需要索引的字段,计算的中间结果,可以保留在event中 无后台的DApp可以在event中保留更多的字段 具有复杂功能(比如分账明细)的DApp,如需要多次查询大量数据,可以考虑使用后台来查询区块链。此时event中含有的函数入参就没有必要保留了,数据可以通过transaction的input字段恢复。

拿我发送的一个transaction为例

{
  hash: '0xcc1c866186ff39555936ea007a63ead761aef80d4301eb4e0081e8fc8f6fe18d',
  nonce: 892,
  blockHash: '0xbfff2fc0dd268dfce90417a3ea3b5da3a9e59703d8d4ec6a5be3ba2dce59b924',
  blockNumber: 987,
  transactionIndex: 0,
  from: '0x40FB66078a2e688f83002930B7EbA981323d4bef',
  to: '0x2C71AC97716A17E66D7E524Cfeb28B97A3728250',
  value: '0',
  gas: 5000000,
  gasPrice: '10000000000',
  input: '0x70a1495c00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000003782dace9d900000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000056f70656e31000000000000000000000000000000000000000000000000000000',
  v: '0x1b',
  r: '0xbfbd3aef6c6777598847de0aa1ffcaf50470f785054771a54e2e274b89d1a633',
  s: '0x27aea35dd4d462598ac909b55444ec0131c7977fe2ad244eaac0cc28b70e07f1'
}

可以看到很长的input字段,这个字段以0x70a1495c开头,这其实就是函数签名,后面的数据则是按类型把数据拼接起来而已。我们再来看这个函数的abi

{
  constant: false,
  inputs: [
    { internalType: 'bytes', name: 'name', type: 'bytes' },
    { internalType: 'bool', name: 'isOpen', type: 'bool' },
    { internalType: 'bool', name: 'isCustom', type: 'bool' },
    { internalType: 'uint256', name: 'cusPrice', type: 'uint256' },
    { internalType: 'uint8', name: 'durationInYear', type: 'uint8' }
  ],
  name: 'registerRoot',
  outputs: [],
  payable: false,
  stateMutability: 'nonpayable',
  type: 'function',
  signature: '0x70a1495c'
}

可以看到signature也是0x70a1495c
这个签名是怎么生成的呢,其实很简单:
web3.eth.abi.encodeFunctionSignature("registerRoot(bytes,bool,bool,uint256,uint8)")
就是一个短Hash,去掉0x70a1495c后,我们把剩下数据解码:

web3.eth.abi.decodeParameters(["bytes","bool","bool","uint256","uint8"],"0x00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000003782dace9d900000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000056f70656e31000000000000000000000000000000000000000000000000000000")
Result {
  '0': '0x6f70656e31',
  '1': true,
  '2': true,
  '3': '4000000000000000000',
  '4': '1',

最后再拼上字段名称就可以了。
自己写了个解析函数,如下:

async function decodeParamsOfTransaction(txHash, func_abi){
    var txData = await web3.eth.getTransaction(txHash);
    var input = txData.input;
    var types = func_abi.inputs.map(x=>x.internalType);
    var _d = "0x"+input.replace(func_abi.signature,"");
    var names = func_abi.inputs.map(x=>x.name);
    var r = web3.eth.abi.decodeParameters(types, _d);
    var dic = {}
    for(var i=0; i<names.length; i++){
        dic[names[i]] = r[i];
    }
    return dic
}

可得到例如下面的结果:

{
  name: '0x6f70656e31',
  isOpen: true,
  isCustom: true,
  cusPrice: '4000000000000000000',
  durationInYear: '1'
}