浅入浅出智能合约 - 调用(三)
2018-05-22 00:00
1251 查看
当我们谈到 Ethereum 的智能合约时,很难不涉及 Solidity 的 ABI,这里的 ABI 就是一种与 Ethereum 生态系统中合约交互的标准方法。我们可以使用 ABI 从区块链外部调用合约(DApp)的提供的服务,也可以在合约中调用其他合约的函数。
在这篇文章中,我们将简单介绍 Ethereum 智能合约中的应用程序二进制接口(ABI)以及如何使用 ABI 调用其他智能合约中的函数。
当我们使用 Ethereum ABI 调用智能合约中的某些方法时,我们其实只需要关注 ABI 中的两方面内容,一是函数的选择器,二是参数的编码。
前者能够帮助我们选择智能合约中的函数,后者会在解码后作为参数传到函数中;函数的选择加上参数的传递就能在启动智能合约中的分布式应用。
Ethereum ABI 中的函数选择器其实就是四个字节的数据,它可以通过如下的方式进行计算:
其实就是函数签名的 SHA-3 哈希的前四个字节,也可以理解为最左侧的四个字节,函数的签名包含函数名以及所有参数类型用
函数选择器由于其本身的特点,只要让其本身遵循一定的约定并不会复杂太多,但是语言中的类型系统相比于函数选择器就是复杂的多。Solidity 作为一门图灵完备的编程语言,它包含两种数据类型,一些是占用固定大小的静态类型,另一些是占用动态大小的动态类型。
在这里我们简单介绍两几种动态类型的编码方式,数组、字节和字符串;由于变长数组的中元素个数的不确定,所以我们在编码时需要保留其长度信息:
数组中的其他内容将按照元组的方式进行编码,字节和字符串与数组一样,由于长度的不固定,都需要保留长度信息:
字节在编码时需要将内容补充到长度为 32 的倍数,而字符串其实就会按照 UTF8 编码后的字节进行编码;静态类型的编码其实没有太多好介绍的,因为它们的长度固定,所以编码的方式也非常简单,需要注意的是所有的参数在编码后的长度都是 32 的倍数。
想要了解更多与函数选择器与类型编码有关的内容,可以直接阅读官方的 ABI 文档。
我们在上一篇文章 浅入浅出智能合约 - 部署(二) 中介绍了如何在 Ethereum 上部署合约以及它的实现原理,其实无论是部署合约还是调用合约都是 Ethereum 网络上的一笔交易。
如果我们想从自己的地址发送一笔交易,能够主动改变的值并不多,
其中
最后的
当我们将上述合约部署到 Ethereum 网络上之后,就能找到如下的合约 ae9691592751a80f,在这时我们创建一个新的交易调用当前合约并改变其状态。
我们可以看到当前交易 1f7fda75602f7b71 的
你可以在 Ethereum 的测试网络 Rinkbey 中找到这笔用于调用合约函数的交易 1f7fda75602f7b71。
无论是部署合约还是调用合约中函数,我们都需要构建一笔交易并发送到整个网络中,合约的调用使用了
Application Binary Interface Specification
在这篇文章中,我们将简单介绍 Ethereum 智能合约中的应用程序二进制接口(ABI)以及如何使用 ABI 调用其他智能合约中的函数。
ABI
当我们使用 Ethereum ABI 调用智能合约中的某些方法时,我们其实只需要关注 ABI 中的两方面内容,一是函数的选择器,二是参数的编码。前者能够帮助我们选择智能合约中的函数,后者会在解码后作为参数传到函数中;函数的选择加上参数的传递就能在启动智能合约中的分布式应用。
函数选择器
Ethereum ABI 中的函数选择器其实就是四个字节的数据,它可以通过如下的方式进行计算:require 'digest/sha3' Digest::SHA3.hexdigest("baz(uint32,bool)", 256)[0...8] # => "cdcd77c0"
其实就是函数签名的 SHA-3 哈希的前四个字节,也可以理解为最左侧的四个字节,函数的签名包含函数名以及所有参数类型用
,连接后的字符串,从这里我们可以看到,ABI 中函数选择器的设计是非常简单的。
参数编码
函数选择器由于其本身的特点,只要让其本身遵循一定的约定并不会复杂太多,但是语言中的类型系统相比于函数选择器就是复杂的多。Solidity 作为一门图灵完备的编程语言,它包含两种数据类型,一些是占用固定大小的静态类型,另一些是占用动态大小的动态类型。在这里我们简单介绍两几种动态类型的编码方式,数组、字节和字符串;由于变长数组的中元素个数的不确定,所以我们在编码时需要保留其长度信息:
encode(arr) = encode(len(arr) encode(arr[0],arr[1],...arr[len(arr)-1])
数组中的其他内容将按照元组的方式进行编码,字节和字符串与数组一样,由于长度的不固定,都需要保留长度信息:
encode(bytes) = encode(len(bytes)) pad_right(bytes) encode(string) = encode(encode_utf8(string))
字节在编码时需要将内容补充到长度为 32 的倍数,而字符串其实就会按照 UTF8 编码后的字节进行编码;静态类型的编码其实没有太多好介绍的,因为它们的长度固定,所以编码的方式也非常简单,需要注意的是所有的参数在编码后的长度都是 32 的倍数。
想要了解更多与函数选择器与类型编码有关的内容,可以直接阅读官方的 ABI 文档。
调用合约
我们在上一篇文章 浅入浅出智能合约 - 部署(二) 中介绍了如何在 Ethereum 上部署合约以及它的实现原理,其实无论是部署合约还是调用合约都是 Ethereum 网络上的一笔交易。{ "jsonrpc": "2.0", "id": 1, "result": { "blockHash": "0xfb508342b89066fe2efa45d7dbb9a3ae241486eee66103c03049e2228a159ee8", "blockNumber": "0x208c0a", "from": "0xe118559d65f87aaa8caa4383b112ff679a21223a", "gas": "0x2935a", "gasPrice": "0x9502f9000", "hash": "0xe74c796a041bad60469f2ee023c87e087847a6603b27972839d0c0de2e852315", "input": "0x6080604052348015600f57600080fd5b50603580601d6000396000f3006080604052600080fd00a165627a7a72305820d9b24bc33db482b29de2352889cc2dfeb66029c28b0daf251aad5a5c4788774a0029", "nonce": "0x2", "to": null, "transactionIndex": "0x5", "value": "0x0", "v": "0x2c", "r": "0xa5516d78a7d486d111f818b6b16eef19989ccf46f44981ed119f12d5578022db", "s": "0x7125e271468e256c1577b1d7a40d26e2841ff6f0ebcc4da073610ab8d76c19d5" } }
如果我们想从自己的地址发送一笔交易,能够主动改变的值并不多,
gasPrice、
to和
input是三个我们能够控制的字段。
其中
gasPrice一般都是一个合理的数值,它仅仅用来表示当前交易愿意承担的『手续费』,无法携带太多的信息,
to也没有太多可以操作的空间,当它置空时就表示这是一笔用于创建合约的交易,当它为非空时,往往表示交易的接收方或者智能合约的地址。
最后的
input是能够搞事情的地方了,从理论上来讲我们可以在这里存储任何数据,也就意味着拥有了无限可能;当我们调用合约时,我们通过
input传递函数选择器和编码后的参数,这样节点在解码选择器和参数之后就可以执行合约中的某些函数了。
pragma solidity ^0.4.18; contract Contract { uint number; function Contract() public { } function add(uint x) public returns (uint) { number += x; return number; } }
当我们将上述合约部署到 Ethereum 网络上之后,就能找到如下的合约 ae9691592751a80f,在这时我们创建一个新的交易调用当前合约并改变其状态。
我们可以看到当前交易 1f7fda75602f7b71 的
input中包含了当前合约调用的函数选择器和编码后的参数。
{ "jsonrpc": "2.0", "id": 1, "result": { "blockHash": "0x6cd8ec67ecd8d9cf2cb488521439d92703036f20402a812634c5f6946f0d1ca1", "blockNumber": "0x226335", "from": "0xe118559d65f87aaa8caa4383b112ff679a21223a", "gas": "0x22a2a", "gasPrice": "0xee6b2800", "hash": "0x1f7fda75602f7b71fe4fd79cb119d009aad2b89616fb98a93bd241f43f9165cd", "input": "0x1003e2d2000000000000000000000000000000000000000000000000000000000000000a", "nonce": "0x7", "to": "0xae9691592751a80feccaa87a8898ea39dfb2cd4f", "transactionIndex": "0x3", "value": "0x0", "v": "0x2b", "r": "0x57096309a868e74e06ce62e99667c228dd677ba0ced74f4eb2b3c488a309e420", "s": "0x2ee943a546d9dac3835a0c7bc99e5e25aa71e8a9cb5b7dba64941318d99f950" } }
input中的数据可以被分割成以下的几个部分,函数选择器和十六进制编码的
10:
// 0x1003e2d2000000000000000000000000000000000000000000000000000000000000000a Function: add(uint256 x) *** MethodID: 0x1003e2d2 - [0]: 000000000000000000000000000000000000000000000000000000000000000a
你可以在 Ethereum 的测试网络 Rinkbey 中找到这笔用于调用合约函数的交易 1f7fda75602f7b71。
总结
无论是部署合约还是调用合约中函数,我们都需要构建一笔交易并发送到整个网络中,合约的调用使用了 input字段传递函数选择器以及编码后的参数,但是除此之外,我们也可以使用
input做任何意想不到的事情,因为对于 Ethereum 来说
input可能是无意义的,但是对于链外的应用来讲却可以存储有意义的数据。
相关文章
Reference
Application Binary Interface Specification
相关文章推荐
- 【智能合约】以太坊中智能合约调用中用的gas相关概念详解
- 以太坊智能合约代币应用开发(5)-web3调用智能代币合约
- 在geth客户端调用已部署的智能合约
- 区块链 之 部署和调用以太坊智能合约
- 智能合约调用另一合约中的payable方法
- 智能合约调用示例
- 如何部署并调用ethereum智能合约
- 智能合约部署及调用
- 【智能合约】客户端和web端对智能合约的事件Event进行调用的代码示例
- 如何用web3j调用智能合约
- 如何开发编译部署调用智能合约
- Solidity智能合约调用智能合约
- 基于mcat开发智能合约应用(二)调用合约
- 手把手教你搭建智能合约测试环境、开发、编译、部署以及如何通过JS调用合约方法
- 在geth客户端调用已部署的智能合约
- web3(1.0.0版本)与智能合约交互
- 『区块链智能合约』从零构建Ethereum智能合约到实战开发
- 极简入门:什么是智能合约?
- 使用truffle编译和部署智能合约
- 智能合约编程语言solidity的特性