Skip to content

Latest commit

 

History

History
190 lines (116 loc) · 16.3 KB

智能合约.asciidoc

File metadata and controls

190 lines (116 loc) · 16.3 KB

智能合约

我们已经在[intro]中讲过,Ethereum有两种不同类型的账户:外部账户(EOA)和合约账户。EOAs是由软件控制的,比如一个钱包应用程序,它属于Ethereum网络之外。合同帐户由运行在Ethereum虚拟机(EVM)中的软件控制。这两种类型的帐户都是通过Ethereum地址标识的。在本节中,我们将讨论第二种类型,合约帐户,以及控制它们的软件:智能合约。

什么是智能合约?

历史中,_智能合约_这个词描述过各种不同的物品。在20世纪90年代,密码学家尼克·萨布(Nick Szabo)创造了这个词,并将其定义为“以数字形式指定的一组承诺,包括双方履行其他承诺的协议。”从那时起,智能合同的概念就不断演变,尤其是在2009年比特币引入了去中心化的区块链概念之后。在本书中,定义“智能合约”这个术语,指的是在以太虚拟机的环境中运行的不可变的确定性的计算机程序,您可以把虚拟机看做是一种去中心化的全局计算机。

我们一起剖析一下本书中的定义:

计算机程序: 智能合约,很简单,指的就是计算机程序。合约这个词在这没有任何法律意思。 不可变的: 与传统的软件不同,智能合约一旦部署到网络中后,其代码就无法修改。只能通过部署一个新的实例去修改之前的智能合约。 确定性: 对于任何一个运行合约的人,在合约被调用的交易上下文中,以及合约被执行时的以太坊区块链状态,都是相同的。 虚拟机运行环境: 智能合约的运行环境非常有限。只能访问自己的状态、调用它的交易的上下文以及一些最近的区块的信息。 去中心化的全局计算机: 虚拟机虽然只是运行在单一节点上的一个实例,但是因为它的初始状态以及每次运行后的结果都是确定的,所以可以把它看做是单一的全局计算机。

智能合约的生命周期

智能合约是用高级语言编写的,例如Solidity. 运行前,必须被编译成EVM(参考[EVM])可执行的二进制代码。编译完成后,合约就可以通过一个指向特殊的合约地址的交易部署到Ethereum区块链网络中。合约是用Ethereum地址来标识的,这个地址是通过合约创建时的包含源地址以及nonce的交易生成的。你可以使用智能合约地址来给智能合约发送资金,或者调用智能合约的函数。

值得重视的是,智能合约只有当其被某个交易调用的时候才执行。Ethereum中的所有智能合约都是由EOA发起的交易来驱动执行的。合约也可以调用另一个合约,另一个合约也可以调用另一个合约.., 但是第一个合约的调用必须又EOA发起。合约永远不会“自己执行”或者“运行在后台”。合约在区块链上实际上处于“休眠”的状态,直到有一个交易触发它。

不论一个交易调用多少合约,以及调用这些合约做什么,它都是_原子的_。只有交易成功的执行终止,才会在全局状态(合约、账户等)中记录所有的更改。这里成功终止指的是交易中调用的合约全部执行完毕而且没有错误。如果一个交易出错了,那么所有的影响将会“回滚”到没有执行它之前的状态。出错的交易任然保存在区块链上并且扣掉源账户使用的gas,但是对于合约以及账户的状态不会有任何影响。

合约的代码不可以被修改,但是可以“删除”,这里“删除”指的是从区块链中移除代码以及内部状态(变量)。要删除合约,只需要运行一个EVM中叫做 SELFDESTRUCT (之前叫 SUICIDE )的操作吗。这个操作会花费“负gas”来触发改变全局状态,删除操作并不会删除合约的历史交易数据,因为区块链本身是不可修改的,因此只会在之后的区块中删除合约的状态。

Ethereum 高级语言介绍

EVM是一个运行名为 EVM bytecode 特殊形式机器码的仿真机,就像您的计算机CPU一样,只不过CPU中运行的是X86_64之类的机器码。我们将在[EVM]一章中详细介绍EVM的操作和语言。本节中,我们将研究如何编写在EVM上运行的智能合约。

虽然也可以通过直接编写EVM bytecode来编写智能合约,但是EVM的字节码非常笨拙而且难以阅读和理解,因此Ethereum开发人员都使用高级语言来编写,然后使用编译器将其转换为字节码。

尽管任何高级语言都可以编写智能合约,但这是一项非常繁的工作。因为智能合约是运行在一个资源高度受限并且极其简单的执行环境(EVM)中,在这个环境中,无法构建任何的用户界面、操作系统界面或者硬件界面。因此从头构建一种最小化的智能合约语言比约束通用语言使其适合编写智能合约要简单的多。正因如此,出现了很多专门编写智能合约的语言。Ethereum提供了几种这样的语言以及它们对应的编译器。

通常编程语言可以被分为两大类:声明式和命令式,或者叫做“函数式编程”和“过程式编程”。在声明式编程中,我们编写的函数表示程序的 logic ,而不是它的 flow ,创建的是没有 side effects 的程序,这意味着函数之外的状态不会改变。声明式编程语言包括Haskell、SQL和HTML。相反,命令式编程中,程序员将逻辑和流程混在一起,这种语言包括BASIC、C、C++和JAVA等。另外有些语言和“混合”模式的,它们既可以用来进行声明式编程,同时也支持命令编程范式。这种混合模式的语言包括Lisp、Erlang、Prolog、JavaScript和Python。一般来讲,任何命令语言都可以用声明式范式进行编程,但会导致不优雅的代码。相比之下,纯声明式语言并不能用于编写命令式程序,因为在这种语言中,没有“变量”。

命令式编程更容易编写和读取数据,程序员也经常使用,但是要想编写出完全符合预期的程序却非常困难。程序的任何部分都可以修改程序的状态,因此很难知道程序下一步执行的结果,编写这种语言的程序很容易引出错误或者异常。相比之下,声明式编程虽然难以编写,但是没有这种副作用,程序员更容易理解程序的行为。

智能合约的编写对于程序员来说代价很高:因为修复bug需要花钱。因此,在编写不会产生意外的智能合约至关重要。因此,声明式语言在智能合约中所起的作用比在通用软件中的大得多。然而,你会看到为什么Solidity成为智能合约最高产的语言。

智能合约相关的语言排序(按年代先后顺序):

LLL

一种功能(声明性的)编程语言,具有类似lisp的语法。这是Ethereum采用的第一个高级语言,但在今天很少使用。

Serpent

一种程序化(命令式)编程语言,其语法类似于Python。也可以用于编写函数(声明性)代码,尽管它并非完全没有副作用。首先由维塔利克·布特林创造。

Solidity

一种程序化(命令式)编程语言,其语法类似于JavaScript、c++或Java。Ethereum智能合约最流行、最常用的语言。作者是Gavin Wood(这本书的合著者)。

Vyper

一种最近开发的语言,类似于Serpent或者python的语法。想要比Python更接近一种纯粹的Serpent语言,但不是为了取代Python。首先由维塔利克·布特林创造。

Bamboo

一种新开发的语言,受Erlang的影响,具有显式的状态转换和没有迭代流(循环)。旨在减少副作用和增加可听性。非常新颖,很少使用。

正如您看到的,有许多语言可供选择。然而到目前为止,Solidity是最受欢迎的,因为它是以太坊事实上的官方语言,甚至一些其他的基于EVM的区块链也使用它。我们将花费大部分时间使用solid,但也将探索其他高级语言中的一些示例,以了解它们的不同哲学。

使用Solidity创建一个智能合约

From Wikipedia:

Solidity is a "contract-oriented" programming language for writing smart contracts. It is used for implementing smart contracts on various blockchain platforms. It was developed by Gavin Wood, Christian Reitwiessner, Alex Beregszaszi, Liana Husikyan, Yoichi Hirai and several former Ethereum core contributors to enable writing smart contracts on blockchain platforms such as Ethereum.
— Wikipedia entry for Solidity

Solidity is developed and currently maintained by a team of developers as the Solidity project on GitHub:

Solidity项目最重要的“产品”就是 Solidity编译器(solc),solc编译器将solidity代码转换成EVM虚拟机执行的二进制代码bytecode,同时输出代码相关的应用二进制借口(ABI)等。每个版本的编译器都对应特定版本的Solidity语言。

Solidity版本选择

Solidity编译器版本遵循 semantic versioninghttps://semver.org/) 版本控制规则,由使用点号分隔的三个数字构成: MAJOR.MINOR.PATCH 。其中“MAJOR”字段标识主要版本更新以及 非后向兼容 的修改,“MINOR”表示后向兼容特性的添加,“PATCH”表示bug修改或者安全问题相关修改。

目前为止,Solidity的最新版本为 0.4.21 ,其中 0.4 是他的“MAJOR”版本号,21是“MINOR”版本号,最后一部分是发布的编号。预计很快“MAJOR”为0.5的版本就会发布。

正如[intro]中所述, 您的智能合约中可以使用用一个 pragma 指令来指定用于编译您的合约的最小以及最大的编译器版本号。

由于Solidity项目更新很快,所以请尽量使用最新的版本号。

下载/安装

Solidity编译器有多种下载和安装的方式,您可以通过下载二进制安装包,或者通过编译其源代码的方式来安装。具体介绍,请查看一下网站中的相关文档:

[在ubuntu中安装Solidity] 部分,我们将使用 apt 包管理软件在Ubuntu/Debian系统上安装最新的Solidity编译器。

使用 apt 包管理软件在Ubunti/Debian上安装Solidity
$ sudo add-apt-repository ppa:ethereum/ethereum
$ sudo apt update
$ sudo apt install solc

安装完 solc 后,可以用以下命令验证安装是否成功:

$ solc --version
solc, the solidity compiler commandline interface
Version: 0.4.21+commit.dfe3193c.Linux.g++

安装Solidity的方式很多,取决于您的系统以及其他需求,其中您可以通过编译源代码的方式进行安装。更多细节请查阅:

Development environment

您可以使用任何的文本编辑器加上命令行下的 solc 编译器进行Solidity开发。当然,目前已经有很多编辑器帮助您方便的进行Solidity的开发,比如可以使用安装了语法高亮插件的Atom编辑器。

除此之外,还有一些基于web的在线开发环境,比如Remix IDE (https://remix.ethereum.org/), 或者 EthFiddle (https://ethfiddle.com/)。

使用能让你高效工作的工具。但最终,Solidity程序只是纯文本文件。虽然花哨的编辑器和开发环境可以使事情变得更简单,但是您只需要一个简单的文本编辑器,例如vim (Linux/Unix)、TextEdit (MacOS)甚至NotePad (Windows)。 您只需要将编写好的文档保存为 .sol 后缀的格式,Solidity便会自动识别它并将它编译成Solidity程序。

编写一个简单的Solidity程序

[intro] 中,我们编写了第一个叫做 Faucet 的Solidity程序。当我们第一次构建 Faucet 时,我们使用Remix IDE编译和部署合约。在本节中,我们将回顾、改进并润色它。

我们之前编写的程序看起来是这样的:

Faucet.sol : A Solidity contract implementing a faucet
link:code/Solidity/Faucet.sol[role=include]

[make_it_better] 一章开始,我们会在这个实例的基础上进行构建。

使用Solidity编译器 (solc)

现在,我们将在命令行中使用 solc 编译我们写好的合约。solc 编译器提供了很多编译选项,我们可以通过使用 -help 参数来查看这些选项。

本例中,我们使用 --bin 和 --optimize 参数来生成经过优化的合约的二进制文件。

使用 solc 编译 Faucet.sol
$ solc --optimize --bin Faucet.sol
======= Faucet.sol:Faucet =======
Binary:
6060604052341561000f57600080fd5b60cf8061001d6000396000f300606060405260043610603e5763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416632e1a7d4d81146040575b005b3415604a57600080fd5b603e60043567016345785d8a0000811115606357600080fd5b73ffffffffffffffffffffffffffffffffffffffff331681156108fc0282604051600060405180830381858888f19350505050151560a057600080fd5b505600a165627a7a723058203556d79355f2da19e773a9551e95f1ca7457f2b5fbbf4eacf7748ab59d2532130029

solc 输出的结果是用十六进制序列化后的二进制文件,这个二进制文件就是要提交(部署)给以太坊的二进制文件。

以太坊智能合约应用二进制接口 (ABI)

在计算机软件中,应用程序二进制接口(ABI)是两个程序模块之间的接口;通常,一个在机器代码级别,另一个在用户运行的程序级别。ABI定义了如何在 机器代码 中访问数据结构和函数;不要把其与API混淆,API将这种访问定义为高级的、通常是人类可读的格式,如 源代码 。因此,ABI是编码和解码数据进出机器码的主要方式。

在以太坊中,ABI用来给EVM进行合约调用的编码以及从交易中读取数据。

在Ethereum中,ABI用于对EVM的合同调用进行编码,并从交易中读取数据。ABI的目的是定义可以调用契约中的哪些函数,并描述函数如何接受参数和返回数据。

一个契约的ABI的JSON格式是由一个描述函数的数组(参见 << ty_function >> )和事件(参见 << ty_events>> )给出的。函数的描述是一个JSON对象,包含“类型”、“名称”、“输入”、“输出”、“常量”和“应付”的字段。事件描述对象具有“类型”、“名称”、“输入”和“匿名”的字段。

我们使用 solc 命令行编译器为我们的 Faucet.sol 生成ABI:

solc --abi Faucet.sol
======= Faucet.sol:Faucet =======
Contract JSON ABI
[{"constant":false,"inputs":[{"name":"withdraw_amount","type":"uint256"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"}]

如您所见,编译器生成一个JSON对象,描述由 Faucet.sol 定义的两个函数。这个JSON对象可以被任何希望在部署后访问 Faucedt 合同的应用程序调用。使用ABI(一个应用程序,如钱包或DApp浏览器),可以构造调用 Faucet 中的函数的交易,并使用正确的参数和参数类型。例如,钱包会知道,要调用函数 withdraw ,它必须提供一个 uint256 参数,名为 withdraw_amount 。钱包可以提示用户提供该值,然后创建一个对其进行编码并执行 withdraw 函数的交易。

应用程序与合约交互所需要的只是一个ABI和部署合约的地址。

Solidity编译器和语言版本的选择

正如我们在 使用 solc 编译 Faucet.sol 中所看到的,我们的 Faucet 合约成功地使用Solidity 0.4.21进行编译。但是如果我们使用的是另一个版本的编译器呢? Solidity语言还在不断的进化,因此可能会带来意想不到的情况。我们的交易相当简单,但是如果我们的程序使用的特性只是在Solidity版本 0.4.19 中添加的,并且我们试图用+0.4.18+来编译它,会怎么样呢?

为了解决这些问题,Solidity提供了一个名为 version pragma编译器指令 ,它指示编译器该程序期望一个特定的编译器(和语言)版本。让我们来看一个例子:

pragma solidity ^0.4.19;