乐趣区

Solidity-057简明教程

以太坊不仅是一种加密数字货币,它更是功能完备的智能合约平台,solidity 就是用来开发以太坊上的智能合约的原生开发语言。solidity 最早发布于 2015 年,它是第一种图灵完备的智能合约专用开发语言。目前除了以太坊之外,在其他区块链中也逐渐开始支持 solidity,例如 hyperledger fabric、tendermint 等。在这个 solidity 快速教程中,我们将使用最新 0.5.7 版的 solidity,以一个具体的案例来介绍 solidity 智能合约的开发、部署与交互,希望对你快速掌握 solidity 智能合约的开发有所帮助。

如果要高效系统地掌握以太坊智能合约与 DApp 的开发,推荐访问汇智网的在线互动课程:

以太坊开发入门 | java 以太坊 | python 以太坊 | php 以太坊 | C# 以太坊 | 电商 DApp 实战 | ERC721 通证实战

0、问题的背景

有一个老爷爷,在生命的最后岁月别无他求,只是希望自己的财产能够通过遗嘱顺利地传给其他家庭成员。

在传统的遗嘱中,遗产分配方案是落实在法律文件上的,然后当真正开始分配时,法官需要重审文件并做出相应的决定。常见的问题发生在家庭成员之间对分配比例的争执上,甚至因此而导致家庭成员关系的破裂。在法庭听证阶段,这些都会影响法官最终的裁决,并因此可能导致不公平的结果,甚至对家庭关系造成进一步的伤害。

那么,如果我们可以让遗产分配自动进行,是否可以避免上述情况的发生?

如果遗产是一个智能合约,那么就不需要法官了。老爷爷可以自主地利用合约管理资产,然后在他去世后由程序来分配遗产给家庭成员。合约里的代码就决定了最终的分配结果,因此无需法官的介入。例如萨拉分 $10000,本得到 $5000,朱丽叶得到 $2000。代码执行后,资产以代币或加密货币的形式自动分配给这些家庭成员,而无需人工介入。虽然不能保证每个成员都对遗产的分配结果满意,但是没有人会和代码争执。这听起来还比较可行,对吗?

记住这个案例,在这个快速教程中,我们将使用 solidity,为老爷爷开发一个简单的遗嘱合约,来满足他最后的愿望。

1、搭建 solidity 开发环境

开发 solidity 智能合约最简单的方法,就是使用官方提供的在线集成开发环境 REMIX,你可以点击这里打开 remix,在网页里就完成 solidity 智能合约的编写、编译与部署:

在你打开 remix 页面后,注意在右侧的 run 选项页,environment下拉框中,要选中JavaScript VM。这个选项的意思是使用一个内存仿真以太坊节点作为你的 solidity 智能合约的运行平台,这样就不用考虑与实际的以太坊主网交互所需要的账号、资金、计算费用等问题,而可以先把精力聚焦在学习如何使用 solidity 表达你的业务逻辑上。

点击 remix 页面左上方的 + 图标,就可以创建一个新的代码文件,我们将其命名为 will.sol。在 remix 页面中间的编辑区域可以同时显示多个文件,当前正在编辑的文件,则以活动选项页的形式显示文件名称。

2、声明 solidity 编译器版本

solidity 还是很早期阶段的语言,从语法到编译器都在不断地演化,所以在 solidity 代码的第一行,一定要用 pragma 关键字声明这个文件中的 solidity 代码需要哪个版本的编译器。例如:

注意在 solidity 中,末尾的分号不可省略。

3、编写第一个 solidity 合约

接下来就可以定义我们的第一个合约:

使用 contract 关键字来定义一个合约,solidity 的合约类似于我们熟悉的 OOP 中的类,因此通常合约的名称首字母也会大写,例如Will。一对大括号用来定义合约的实现逻辑,单行注释也使用//,这和很多开发语言都类似。

4、solidity 中的全局变量和构造函数

在我们开始写代码之前,应当首先明确遗嘱的条款。假设老爷爷的遗产是 50 个以太币,其中 20 个留给他的儿子康莱德,剩下的 30 个留给他的妻子丽莎。在真实的环境中,当老爷爷去世后,应当有一个外部的程序将调用合约中定义的方法来分配遗产,但是我们为了便于学习将自己完成这个调用。

现在,让我们先完成如下代码:

  • 表征合约所有者的变量
  • 表征遗产数量的变量
  • 表征老爷爷是否还健在的变量
  • 设置上述变量初始值的构造函数

第 5 行代码定义了合约的所有者。当我们在 solidity 中定义变量时,必须先声明其类型。address是 solidity 中一种特殊的类型,它表示一个以太坊地址。address类型的变量有一些特殊的方法,我们在后面会进一步了解。

第 6 行代码定义的 fortune 变量用来保存老爷爷的遗产数量,它的类型是 uintunsigned int,意思是这个变量是 0 或正整数。solidity 中有很多数据类型,但我们不会在这里一一介绍,你可以在官方文档中深入了解 solidity 的数据类型。

第 7 行代码定义的 isDeceased 变量用来标识老爷爷是否已经去世,这是一个开关量,因此其类型为boolean,可能的值只有两个:true 或 false,默认值为 false。

第 9~13 行代码是合约的构造函数,这个特殊的函数将在合约部署的时候自动执行。

public关键字被称为 可见性修饰符 ,它的作用是声明被修饰的方法是否允许外部调用。public 意味着在合约内部或外部(由其他合约或其他人)都可以调用该方法。

payable关键字是 solidity 的特色之一,它使得被修饰的方法可以发送或接收以太币。为构造函数声明 payable 关键字意味着当我们部署合约的时候,可以直接向合约存入以太币,例如,作为遗产的 50 个以太币。当合约接收到以太币后,这些币就保存在合约地址上了。

在构造函数内部,我们将 owner 变量的值设置为msg.sender,这是一个以太坊平台预置的全局变量,表示调用合约方法的账号地址,在我们的案例中,这的地址是老爷爷的。

同时我们将 fortune 变量的值设置为msg.value,这是另一个全局变量,它表示被调用的方法接收到的以太币的数量。

虽然变量 isDeceased 被自动初始化为默认值 false,但为了清晰起见,我们将其显式地设置为 false。

5、使用 solidity 修饰符

在 solidity 中,修饰符(Modifier)可以为函数附加额外的条件逻辑。例如,假设我有一个用来关灯的方法,同时有一个修饰符要求灯开关必须处于 on 状态,那么
我们就可以在方法上附加声明这个修饰符,以便确保只有在灯开关处于 on 状态时,才可以调用这个方法,否则就抛出异常。

第 15 行代码定义了 onlyOwner 修饰符。如果一个方法附加声明了这个修饰符,那么就要求调用方法的账号(msg.sender)必须与 owner 变量的值一致(别忘了我们在构造函数中设置了 owner 的值)。这个调用条件有助于遗产的分配,我们将在后面看到这一点。

require关键字的意思是,括号里的表达式的值必须为真(true),否则就会抛出异常,不再继续执行代码。

_;起到占位符的作用,在执行过程中,以太坊虚拟机会用被修饰的方法代码来替换它。

第 20 行代码定义了 mustBeDeceased 修饰符。如果一个方法附加声明了这个修饰符,那么就只有在 isDeceased 变量值为 true 时,才可以调用该方法,否则就抛出异常。

在上面的代码中,我们使用修饰符来限定方法的执行条件,当然也可以不使用修饰符,而直接在方法实现代码中使用require,不过修饰符看起来更高级一些,也更容易实现代码的复用。

6、设定遗产分配方案

现在我们要继续完成遗产在家庭成员之间的分配任务,这需要他们的钱包地址和分配数量。

正如我们之前所述,康莱德将收到 20 个以太币而丽莎将继承 30 个。让我们创建一个数组来保存他们的钱包地址,然后写一个方法来分配遗产。

第 25 行代码定义了一个空数组 familyWallets,用来保存所有家庭成员的钱包地址。和其他语言一样,在 solidity 中数组是顺序存放并且可以使用序号来存取。注意方括号之前的关键字paybale,只有address payable 类型的变量,才可以接收以太币,这是 0.5 版本的 solidity 与之前版本的区别之一。

第 27 行代码创建了一个从 address 类型到 uint 类型的映射表变量inheritance,用来保存每个钱包地址的遗产数量。这是一个键 / 值对数据结构,类似于其他语言中的字典或哈希表,可以用键来存取值。

第 29 行代码定义了一个方法,它的功能是将一个钱包地址添加到 familyWallets 数组,然后设置该地址在 inheritance 映射表中的遗产数量。注意附加的 onlyOwner 修饰符,猜一下为什么我们要在这里声明这个修饰符?

第 30 行代码将传入方法的钱包地址追加到 familyWallets 数组的末尾。

第 31 行代码将传入方法的遗产继承数量设置为映射表 inheritance 的指定地址(传入方法的另一个参数)的值。

7、实现遗产自动分配

让我们总结一下。到目前为止,我们已经学习了全局变量、数据类型、构造函数、特殊的关键字例如 payablepublic、内置的全局变量例如 msg.sendermsg.value、修饰符和require、数组、映射表和方法。我们已经搭好了合约的框架,现在让我们把各部分整合起来最终完成合约。

作为这个教程最后一部分的代码,我们将实现家庭成员遗产的自动分配。

第 34 行定义了 payout() 方法,注意 private 关键字,这个 可视性修饰符 public的反义词,它只允许被修饰的方法在合约内部调用,就像在第 42 行的代码那样。之所以在这里使用 private,主要是考虑到安全性,因为我们不希望任何来自合约外部的调用。注意最后的mustBeDeceased 修饰符,目前我们依然不能满足这个修饰符要求的条件来执行 payout() 方法。

第 35 行代码是一个 for 循环,用来遍历 familyWallets 数组。语法如下:

  • 定义一个计数器变量 i,
  • 声明循环的执行条件
  • 每个周期计数器变量 i 加 1

第 36 行代码是整个合约的核心,我们调用 address 类型的地址对象的 transfer() 方法,向该地址转账预定的遗产继承数量,inheritance[familyWallets[i]]表示在 inheritance 映射表中,键 familyWallets[i] 的值,也就是第 i 个家庭成员的遗产继承数量。

第 40~42 行代码定义了一个方法,当老爷爷去世后将调用这个方法来触发遗产的分配。在这里我们将变量 isDeceased 的值设置为 true。

现在我们完成了吗?

实际上,还不完全是 …

这个智能合约的代码是写完了,但是我们怎么用它?现在是收获果实的时候了。

8、solidity 合约部署与交互

你的 remix 页面看起来应该像这样:

在 remix 页面右边切换到 compile 选项页,确认按下图选中编译器的版本,然后点击[start to compile]:

你可能会看到静态分析生成的一个蓝色文本框,我们暂时忽略它的提醒,切换到 run 选项页:

确保 Environment 下拉框中选中了 Javascript VM,点击account 的下拉菜单将显示 5 个测试账户,每个账户都有 100 个以太币,让我们选择第一个。

向以太坊区块链部署合约并不是免费的,部署者需要支付手续费,通常被称为 gas。引入这一机制的目的是避免区块链计算资源被恶意滥用,要进一步了解 gas,可以查看这篇文章:1 分钟搞清 Gas/ Gas Price/ Gas Limit。

gas limit字段使用默认值就可以了,我们先不修改它。

value字段表示我们在部署合约时要发送给合约的以太币数量。输入 50,还记得我们在定义构造函数时附加的 payable 关键字吗?

现在继续,点击[deploy]。

你可能立刻会注意到 3 件事。首先,选中的账户余额现在变成了 49.9999…,这是因为我们转给合约 50 个以太币,还要扣除一点部署手续费。页面底部的控制台也会提供关于部署过程的详细信息,你可以查看一下。现在看起来是这样:

我们的合约已经成功部署了!它生成了自己的地址,并且显示出我们定义的两个合约方法。作为合约的持有者,我们要做的第一件事,是设置家庭成员的继承数量:康莱德(20)、丽莎(30)。假设我们用 account 下拉菜单中的第二个作为康莱德的账号,丽莎的用第三个。

选择第二个账号,点击 [拷贝到剪切板] 图标,然后输入上图中的 setInheritance 后面的文本输入框。

在我们执行 setInheritance 方法之前,有几件事情要记住。

传入合约的以太币数量的单位是 wei 而不是以太币,1 ETH = 1,000,000,000,000,000,000 WEI,这是非常小的单位,因此我们需要将以太币表示的遗产数量先转换为以 WEI 为单位的值。

在将遗产数量换算后,在将其写入上图中的 setInheritance 后面的文本输入框中,之前输入的地址后面,这两个值之间注意要用逗号隔开。

还有,别忘了在 account 下拉框选中第一个账号,还记得 onlyOwner 修饰符吗?只有合约的持有人才可以调用 setInheritance 方法!

现在让我们依次为康莱德和丽莎执行 setInheritance 方法。你应当可以看到控制台输出的成功信息。看一下其中的decoded input

你看,它显示的就是我们输入的数据。

遗产分配好了,但是坏消息来了。老爷爷在 73 岁时,在一次北极探险中不幸因心脏病突发去世。他总是这么充满激情与活力。

当我们纪念这位老爷爷的同时,我们同时调用遗嘱合约的 deceased() 方法,完成老爷爷的最后的愿望。。。


原文:solidity 0.5.7 简明教程

汇智网翻译整理,转载请标明出处

退出移动版