智能合约应用程序二进制接口规范

概述

本文章主要写调用合约发送交易参数组装编码过程;

在web3j源码中有一个codegen module模块,其中有个项功能是将solidity文件转换成java文件,该文件包含了合约的所有接口与deploy、load,这样对其他程序员来说,降低了他们对接区块链与智能合约时的学习成本;但这种方式也存在一些弊端,需要改进,如每编写一个合约都需要手动的生成一个对应的java文件,如果合约随着业务的变化一直在变,那么就需要不停的更新java文件,并附加到项目工程中,导致没更新一个合约就要重新更新项目版本;

so为了解决这个问题,只能抛弃web3j的方法,使用输入参数的形式,也就是部署合约与调用合约接口由专门的统一接口提供; 如:部署合约接口(deploy)、调用合约接口,发送交易(sendContractTransaction)、调用合约接口,不发生交易(callContract)。 下面就对这3个接口做一个详细的解析;因为每个链都有不同的数据结构和标准,所以这里以eth为例,其他以太坊系列的使用solidity语言的类似一次类推;

知识预备

智能合约应用程序二进制接口规范

这里不讲智能合约具体是啥,干什么用,若想了解可参考小编的另一篇关于solidity的博客;

这里讲智能合约应用程序二进制接口规范; 主要有以下基本类型:

uint<M>: unsigned integer type of M bits, 0 < M <= 256, M % 8 == 0. e.g. uint32, uint8, uint256.

int<M>: two’s complement signed integer type of M bits, 0 < M <= 256, M % 8 == 0.

address: equivalent to uint160, except for the assumed interpretation and language typing. For computing the function selector, address is used.

uint, int: synonyms for uint256, int256 respectively. For computing the function selector, uint256 and int256 have to be used.

bool: equivalent to uint8 restricted to the values 0 and 1. For computing the function selector, bool is used.

fixed<M>x<N>: signed fixed-point decimal number of M bits, 8 <= M <= 256, M % 8 ==0, and 0 < N <= 80, which denotes the value v as v / (10 ** N).

ufixed<M>x<N>: unsigned variant of fixed<M>x<N>.

fixed, ufixed: synonyms for fixed128x18, ufixed128x18 respectively. For computing the function selector, fixed128x18 and ufixed128x18 have to be used.

bytes<M>: binary type of M bytes, 0 < M <= 32.

function: an address (20 bytes) followed by a function selector (4 bytes). Encoded identical to bytes24.

The following (fixed-size) array type exists:

<type>[M]: a fixed-length array of M elements, M >= 0, of the given type.
The following non-fixed-size types exist:

bytes: dynamic sized byte sequence.

string: dynamic sized unicode string assumed to be UTF-8 encoded.

<type>[]: a variable-length array of elements of the given type.

Types can be combined to a tuple by enclosing a finite non-negative number of them inside parentheses, separated by commas:

(T1,T2,...,Tn): tuple consisting of the types T1, …, Tn, n >= 0
It is possible to form tuples of tuples, arrays of tuples and so on. It is also possible to form zero-tuples (where n == 0).

 

小编主要讲解如何解析合约方法编码问题,比较常见的2中,静态类型(static type)与动态类型(dynamic type),其中静态类型分基本类型与固定数组长度

这里写个合约做讲解 来之solidity官方文档

pragma solidity ^0.4.16;

contract Foo {
  // 基本类型
  function baz(uint32 x, bool y) public pure returns (bool r) { r = x > 32 || y; }
  // 固定数组长度
  function bar(bytes3[2]) public pure {}
  // 动态类型
  function sam(bytes, bool, uint[]) public pure {}
}

 

例如:调用baz(69,true),可由68字节组成,可细分为methodId+arg1(uint32 69)+arg2(bool true)

  • methodId:方法ID。这是作为签名的ASCII形式的Keccak散列的前4个字节导出的baz(uint32,bool);0xcdcd77c0
  • arg1(uint32 69):第一个参数,uint32值69填充为32字节, 0x0000000000000000000000000000000000000000000000000000000000000045
  • arg2(bool true):第二个参数,布尔值true,填充为32个字节;0x0000000000000000000000000000000000000000000000000000000000000001

0xcdcd77c000000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001

 

返回一个bool。因为bool true为1 false为0,所以输出若为false则 0x0000000000000000000000000000000000000000000000000000000000000000

例如:调用bar(["abc","def"]),则有68字节组成;可细分为methodId+arg1(bytes3[2])

  • methodId:方法ID。这是作为签名的ASCII形式的Keccak散列的前4个字节导出的bar(bytes3[2]);0xfce353f6
  • 0x6162630000000000000000000000000000000000000000000000000000000000:第一个参数的第一部分,一个bytes3值"abc"(左对齐)。
  • 0x6465660000000000000000000000000000000000000000000000000000000000:第一个参数的第二部分,一个bytes3值"def"(左对齐)。

0xfce353f661626300000000000000000000000000000000000000000000000000000000006465660000000000000000000000000000000000000000000000000000000000

 

如果想调用sam("dave",true,[1,2,3]),注意,uint将被替换成uint256

  • 0xa5643bf2:方法ID。这来自签名sam(bytes,bool,uint256[])。请注意,它将uint替换为其规范表示uint256。
  • 0x0000000000000000000000000000000000000000000000000000000000000060:第一个参数(动态类型)的数据部分的位置,以参数块开头的字节为单位。在这种情况下,0x60。这里再补充一遍0x60是怎么计算出来的,是指从第60个偏移量开始为该动态类型的内容;下面的xa同理
  • 0x0000000000000000000000000000000000000000000000000000000000000001:第二个参数:布尔值true。
  • 0x00000000000000000000000000000000000000000000000000000000000000a0:第三个参数(动态类型)的数据部分的位置,以字节为单位。在这种情况下,0xa0
  • 0x0000000000000000000000000000000000000000000000000000000000000004:第一个参数的数据部分,它以元素中字节数组的长度开始,在本例中为4。0x6461766500000000000000000000000000000000000000000000000000000000:第一个参数的内容:UTF-8(在本例中等于ASCII)编码"dave",右边填充32个字节。 0x0000000000000000000000000000000000000000000000000000000000000003:第三个参数的数据部分,它以元素中数组的长度开始,在本例中为3。0x0000000000000000000000000000000000000000000000000000000000000001:第三个参数的第一个条目。0x0000000000000000000000000000000000000000000000000000000000000002:第三个参数的第二个条目。0x0000000000000000000000000000000000000000000000000000000000000003:第三个参数的第三个条目。

0xa5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003

 

合约事件也是同理,只是对应以太坊的logs中的topics

eth接口

eth_sendTransaction

Creates new message call transaction or a contract creation, if the data field contains code.

Parameters

  1. Object - The transaction object
  • from: DATA, 20 Bytes - The address the transaction is send from.
  • to: DATA, 20 Bytes - (optional when creating new contract) The address the transaction is directed to.
  • gas: QUANTITY - (optional, default: 90000) Integer of the gas provided for the transaction execution. It will return unused gas.
  • gasPrice: QUANTITY - (optional, default: To-Be-Determined) Integer of the gasPrice used for each paid gas
  • value: QUANTITY - (optional) Integer of the value sent with this transaction
  • data: DATA - The compiled code of a contract OR the hash of the invoked method signature and encoded parameters. For details see Ethereum Contract ABI
  • nonce: QUANTITY - (optional) Integer of a nonce. This allows to overwrite your own pending transactions that use the same nonce.
params: [{
  "from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155",
  "to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
  "gas": "0x76c0", // 30400
  "gasPrice": "0x9184e72a000", // 10000000000000
  "value": "0x9184e72a", // 2441406250
  "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"
}]

 

Returns DATA, 32 Bytes - the transaction hash, or the zero hash if the transaction is not yet available.

Example

// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"eth_sendTransaction","params":[{see above}],"id":1}'

// Result
{
  "id":1,
  "jsonrpc": "2.0",
  "result": "0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331"
}

 

eth_sendRawTransaction

Creates new message call transaction or a contract creation for signed transactions.

Parameters

  1. DATA, The signed transaction data.
params: ["0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"]

Returns DATA, 32 Bytes - the transaction hash, or the zero hash if the transaction is not yet available.

Use eth_getTransactionReceipt to get the contract address, after the transaction was mined, when you created a contract.

Example

// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":[{see above}],"id":1}'

// Result
{
  "id":1,
  "jsonrpc": "2.0",
  "result": "0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331"
}

 

调用合约接口,发送交易(sendContractTransaction)

这里先以调用合约接口,发送交易(sendContractTransaction)为例,

输入:合约地址,合约方法名称,合约参数,合约ABI

  1. 检验参数
    • 要求合约地址不为空,去前缀0x时,长度为40字节;
    • 合约ABI不能为空
    • 合约方法名真是存在ABI json中
  2. 通过合约方法名称,获取abi中的方法全名如(sam(bytes,bool,uint256[]))
    • 若合约方法名称为空则获取合约构造方法
  3. 检验合约参数类型是否匹配与是否规范
    • 方法全名中参数类型与合约方法参数个数是否一样
    • 方法全名中参数类型与合约方法参数类型是否一样,并校验对应类型的参数规范限定,如:address类型则必须符合去前缀0x,长度为40字节;
  4. 对方法全名作ASCII形式的Keccak散列的签名的前4个字节导出
    • 若该方法名为构造函数则无需签名,直接返回空字符串""(因为此处是以调用sendContractTransaction所以没有部署合约时的bin参数,若是调用构造函数则表明是deploy即需要在前面insert 合约二进制编码bin)
  5. 对参数进行编码(编码规则参考 预备知识)
    • 若参数长度为0,则直接返回空字符串""
    • 若不为零,则根据编码规则参考 预备知识
  6. 将4与5结果拼接 作为 eth_sendTransaction 接口 的data参数
  7. 补全eth_sendTransaction其他参数,将这些参数作为一个对象处理,Transaction
  8. 对Transaction 进行RLP编码该处为byte[]类型所以无法打印(可转换成十六进制字符串输出)
  9. 用以太坊钱包私钥对8步骤结果进行签名,以十六进制字符串输出

其中,6,7步骤根据不同的区块链数据结构与编码要求修改,1~5则是根据solidity语言做修改(一般不会有改动)

输出:合约十六进制编码字符串

 

合约数据结构UML设计;

 

 

posted @ 2018-11-18 22:02  q兽兽  阅读(868)  评论(1编辑  收藏  举报