直播预约通道开启!2021腾讯数字生态大会邀您共探产业发展新机遇! 了解详情
写点什么

浅入浅出智能合约 - 调用(三)

2019 年 12 月 05 日

浅入浅出智能合约 - 调用(三)


当我们谈到 Ethereum 的智能合约时,很难不涉及 Solidity 的 ABI,这里的 ABI 就是一种与 Ethereum 生态系统中合约交互的标准方法。我们可以使用 ABI 从区块链外部调用合约(DApp)的提供的服务,也可以在合约中调用其他合约的函数。



在这篇文章中,我们将简单介绍 Ethereum 智能合约中的_应用程序二进制接口_(ABI)以及如何使用 ABI 调用其他智能合约中的函数。


ABI

当我们使用 Ethereum ABI 调用智能合约中的某些方法时,我们其实只需要关注 ABI 中的两方面内容,一是函数的选择器,二是参数的编码。



前者能够帮助我们选择智能合约中的函数,后者会在解码后作为参数传到函数中;函数的选择加上参数的传递就能在启动智能合约中的分布式应用。


函数选择器

Ethereum ABI 中的函数选择器其实就是四个字节的数据,它可以通过如下的方式进行计算:


Ruby


require 'digest/sha3'
Digest::SHA3.hexdigest("baz(uint32,bool)", 256)[0...8] # => "cdcd77c0"
复制代码


其实就是函数签名的 SHA-3 哈希的前四个字节,也可以理解为最左侧的四个字节,函数的签名包含函数名以及所有参数类型用 , 连接后的字符串,从这里我们可以看到,ABI 中函数选择器的设计是非常简单的。


参数编码

函数选择器由于其本身的特点,只要让其本身遵循一定的约定并不会复杂太多,但是语言中的类型系统相比于函数选择器就是复杂的多。Solidity 作为一门图灵完备的编程语言,它包含两种数据类型,一些是占用固定大小的_静态_类型,另一些是占用动态大小的_动态_类型。


在这里我们简单介绍两几种动态类型的编码方式,数组、字节和字符串;由于变长数组的中元素个数的不确定,所以我们在编码时需要保留其长度信息:


JavaScript


encode(arr) = encode(len(arr) encode(arr[0],arr[1],...arr[len(arr)-1])
复制代码


数组中的其他内容将按照元组的方式进行编码,字节和字符串与数组一样,由于长度的不固定,都需要保留长度信息:


JavaScript


encode(bytes) = encode(len(bytes)) pad_right(bytes)encode(string) = encode(encode_utf8(string))
复制代码


字节在编码时需要将内容补充到长度为 32 的倍数,而字符串其实就会按照 UTF8 编码后的字节进行编码;静态类型的编码其实没有太多好介绍的,因为它们的长度固定,所以编码的方式也非常简单,需要注意的是所有的参数在编码后的长度都是 32 的倍数。


想要了解更多与函数选择器与类型编码有关的内容,可以直接阅读官方的 ABI 文档。


调用合约

我们在上一篇文章 浅入浅出智能合约 - 部署(二) 中介绍了如何在 Ethereum 上部署合约以及它的实现原理,其实无论是部署合约还是调用合约都是 Ethereum 网络上的一笔交易


JavaScript


{    "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"    }}
复制代码


如果我们想从自己的地址发送一笔交易,能够主动改变的值并不多,gasPricetoinput 是三个我们能够控制的字段。


其中 gasPrice 一般都是一个合理的数值,它仅仅用来表示当前交易愿意承担的『手续费』,无法携带太多的信息,to 也没有太多可以操作的空间,当它置空时就表示这是一笔用于创建合约的交易,当它为非空时,往往表示交易的接收方或者智能合约的地址。


最后的 input 是能够搞事情的地方了,从理论上来讲我们可以在这里存储任何数据,也就意味着拥有了无限可能;当我们调用合约时,我们通过 input 传递函数选择器和编码后的参数,这样节点在解码选择器和参数之后就可以执行合约中的某些函数了。


JavaScript


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,在这时我们创建一个新的交易调用当前合约并改变其状态。


我们可以看到当前交易 1f7fda75602f7b71input 中包含了当前合约调用的函数选择器和编码后的参数。


JavaScript


{    "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


JavaScript


// 0x1003e2d2000000000000000000000000000000000000000000000000000000000000000a
Function: add(uint256 x) ***
MethodID: 0x1003e2d2- [0]: 000000000000000000000000000000000000000000000000000000000000000a
复制代码


你可以在 Ethereum 的测试网络 Rinkbey 中找到这笔用于调用合约函数的交易 1f7fda75602f7b71


总结

无论是部署合约还是调用合约中函数,我们都需要构建一笔交易并发送到整个网络中,合约的调用使用了 input 字段传递函数选择器以及编码后的参数,但是除此之外,我们也可以使用 input 做任何意想不到的事情,因为对于 Ethereum 来说 input 可能是无意义的,但是对于链外的应用来讲却可以存储有意义的数据。


相关文章


Reference


本文转载自 Draveness 技术博客。


原文链接:https://draveness.me/smart-contract-invoke


2019 年 12 月 05 日 18:17186

评论

发布
暂无评论
发现更多内容

Three.js杂记(九)—— 练习:地球

空城机

前端 WebGL 3D可视化 three.js

DCGM:监控Kubernetes集群的GPU资源

DCOS

kubernetest

Three.js杂记(十)——贴图

空城机

前端 WebGL 3D渲染 3D可视化 three.js

如何减少加班导致的离职?

石云升

项目管理 28天写作 职场经验 管理经验 3月日更

「产品经理训练营」作业 06:用户路径地图与漏斗模型

狷介

产品经理训练营

渣硕试水字节跳动,本以为简历都过不了,123+HR面直接拿到意向书

云流

Java 程序员 架构 面试

《Redis 核心技术与实战》学习笔记 07

escray

redis 极客时间 学习笔记 3月日更 Redis 核心技术与实战

Three.js杂记(七)—— 全景效果制作·上(含python爬虫偷碎图,canvas重组图片)

空城机

前端 WebGL 3D渲染 3D可视化 three.js

2021春招Java后端开发面试总结:25个技术专题(最全面试攻略)

比伯

Java 编程 架构 面试 程序人生

面试拜佛保过?圈内罕见阿里面试官手册,2021最强面试笔记非它莫属

云流

Java 程序员 架构 面试

金三银四,GitHub 热门面试在这里

GitHub指北

纸币会消失吗:数字货币如何走进我们生活

CECBC区块链专委会

货币

Three.js杂记(十一)—— 精灵与粒子(绘制中国地图)

空城机

前端 WebGL 3D渲染 3D可视化 three.js

moviepy音视频剪辑:视频剪辑基类VideoClip的属性及方法详解

老猿Python

Python 编程语言 音视频 Moviepy

Python-计算机视觉-OpenCV-video

Aldeo

Python OpenCV Video

区块链下乡

CECBC区块链专委会

区块链

Three.js杂记(十一)—— 精灵与粒子(绘制中国地图)

空城机

前端 WebGL 3D渲染 3D可视化 three.js

Three.js杂记(六)——3D模型

空城机

前端 WebGL 3D模型 3D可视化 three.js

真香!Github一夜爆火,阿里性能优化不传之秘终于开源

互联网架构师小马

Java 性能优化 JVM 性能调优 调优

moviepy简介及安装

老猿Python

Python 编程语言 音视频 Moviepy PyQt

域名和服务器的购买和配置

空城机

阿里云 轻量级服务器 云翼计划

时间复杂度总结

我是程序员小贱

3月日更

Wireshark数据包分析学习笔记Day19

穿过生命散发芬芳

Wireshark 数据包分析 3月日更

开启Python学习之旅,分享学习路上的神器集合!

王小王-123

Python 学习神器 资源分享 工具分享 学习网站

工程方法事例实战

风翱

软件工程 3月日更

教你如何用霍夫变换完成扭曲车牌识别

程序媛观澜

机器学习 图像识别

去了解一下区块链

空城机

区块链 笔记 区块链发展

从一道美团春招笔试题目出发,揭开树DP的神秘面纱

面鲸

面试 数据结构与算法 笔试题

moviepy音视频剪辑:moviepy中的剪辑基类Clip的属性和方法详解

老猿Python

FastApi-01-初识

Python测试和开发

Python Web FastApi

Three.js杂记(八)—— 文本几何体

空城机

前端 WebGL 3D渲染 3D可视化 three.js

技术为帆,纵横四海- Lazada技术东南亚探索和成长之旅

技术为帆,纵横四海- Lazada技术东南亚探索和成长之旅

浅入浅出智能合约 - 调用(三)-InfoQ