教程 | 以太坊开发演练, Part-3:安全性、限制性以及一些顾虑
教程 | 以太坊开发演练,Part-2:Truffle,Ganache,Geth 和 Mist
时间轴拨转到2023年,管理员 Dave 马上就要失业了,因为 SaaS 去中心化应用程序正在完成着他的工作,在人力被程序所替代的洪流中,无人幸免。然而作为一名诚实的工作者,他依然会站好最后一班岗。Dave 收到一封邮件,要求他向一份智能合约函数发送一些以太币和一个地址。
接着,Dave 打开了他的以太坊钱包,换言之,一个word文档。他进入了myWallets.docx 中并找到了四条记录:我的密钥,公司的密钥,我的密码,公司的密码。
他打开了 Mist 钱包,找到了那个他需要发送以太坊的智能合约,从公司账户里发送了1000个以太币到那个智能合约的函数中,接着从邮件里粘贴了那个他必须要给函数发送的以太坊地址。但不幸的是,Dave 没有把整个地址复制全。在意识到这个错误之后,他手动地补全了整个地址,但是输错了最后一个字母。现在,这个地址是一个完全不同的新地址了。
Dave 按下了发送键,粘贴下了公司的密码,确认了这笔交易以及函数的执行。在函数执行过程中,该函数将一部分以太币发送到了某些预先定义好的账户地址中,并通过合约的自毁调用把余下的以太币都发送到了 Dave 输入的地址中,因为编写这个智能合约的开发者认为在函数执行之后把这个已经没用的智能合约从区块链中清理干净(事实上这做不到)是一个相当不错的做法。
无巧不成书,那一个被误输入的地址正好是这篇教程中第一部分里“角力合约(Wrestling contract)”的地址,它其实也是由另一个人按照这篇教程中第二部分的第二种方法不小心在主网上布署的。
在同一时刻,有人发现了这个主网上的“角力合约”,他通过注册,成为了二号选手,然后向智能合约发送了一些以太币来调用 Wrestle() 函数,但是因为一号选手从来没有开始自己的回合,就导致了二号选手的以太币被永久地锁定在了“角力智能合约”中。所以在这个几乎不可能(吗?)的场景中,以太币的丢失是由于人为失误和不完整的智能合约而造成的。
『 关于“角力合约” 』
虽然我们实现了游戏的基本功能,但是我们我没有考虑到智能合约的生命周期。它何时被生成,何时被使用,何时不复存在。
如果一个选手在数个回合之后就不再继续玩游戏了,这之后怎么办?我们应该赋予玩家当某一个选手不再玩游戏后,在特定的一段时间后能取回自己资产的权限。
我们也要考虑到到当一个智能合约不再被使用之后的情形(在上文的案例中就是比赛结束的情形)。我曾经提及,为了让智能合约能收到以太币,我们应该在函数修饰符中加上“payable”关键字。但是一个合约仍然能通过如下两种途径来获得以太币(并且你无法禁止这些途径),当一个合约自毁并调用另一个智能合约的地址时(一种在Solidity中特殊的提前定义好的函数,能使智能合约失效,并把其拥有的以太币发送到预先设计好的地址中去),或是当智能合约接收挖矿所得的以太币时。所以在设计智能合约时,你应当确保其中具备提取以太币的方法,以备不时之需。
在这个角力的案例中,既然获胜者已经能够从合约中拿走所有的以太币,我们应当给他设计另一种如下这样的提款函数:
function withdraw() public {
require(gameFinished && theWinner == msg.sender);
msg.sender.transfer(this.balance);
}
这个函数能让他在合约中任其需求且不限次数地提走以太币。通常在转账时,一定要留意由于某些原因转账可能会失败,并且你也应当在设计时考虑让智能合约的用户来调用提款的功能,而不是由智能合约自动发起转账。
此外,你可能会认为让智能合约执行自毁是一个好的做法,这样貌似能够清理区块链,但是一个自毁之后的智能合约依然是保留在区块链上的,它仍可以通过上述的两种方法收到以太币。
你也应当考虑设计一个B计划,以防出于某些未知的原因,你的智能合约无法按照设计那般正常地执行。由于在部署之后,就无法再更改智能合约,你可能想要在智能合约中增加一些你能够触发的锁定状态,这些锁定状态或是能暂停智能合约上交易的执行,或是能向上文的案例那样允许用户通过令此合约向另外的合约发送以太币的方式来达到提款的目的。这样的触发器通过把权力交给第三方的方式使得智能合约去中心化的特性受损,所以是否采用这样的做法还是要看你的智能合约是用在具体哪一个场景之中。
『 安全性 』
要保证 Solidity 开发的安全性首先要遵循通用开发模式,然后要跟进 Solidity 开发平台的发展,通过测试来确保你的智能合约没有漏洞(或是至少向这个目标努力),认识到开发平台自身的局限性,保持你的代码尽量简洁,并且时刻提醒自己以太坊本身(区块链背后的软件,如 Solidity 编译器等)也存在诸多漏洞,并且以太坊每天都处于不断发展之中。
『 测试 』
在任何正式的开发中,测试都是十分重要的一环。如果你以前都是等着漏洞出现,然后再去修复他们,那就有你好受的了。
在第二部分中提及的 Truffle 就给我们提供了一种测试智能合约的简单方法。打开最后一部分中的项目,打开一些命令行接口,然后运行ganache-cli。
ganache-cli -p 7545
创建一个命名为“test”的文件夹,然后在其中创建一个“TestExample.js”文件。粘贴如下内容:
var Wrestling = artifacts.require("Wrestling");
contract('Wrestling', function(accounts) {
// "it" is the block to run a single test
it("should not be able to withdraw ether", function() {
Wrestling.deployed().then(function (inst) {
// We retrieve the instance of the deployed Wrestling contract
wrestlingInstance = inst;
var account0 = accounts[0];
// how much ether the account has before running the following transaction
var beforeWithdraw = web3.eth.getBalance(account0);
// We try to use the function withdraw from the Wrestling contract
// It should revert because the wrestling isn't finished
wrestlingInstance.withdraw({from: account0}).then(function (val) {
assert(false, "should revert");
}).catch(function (err) {
// We expect a "revert" exception from the VM, because the user
// should not be able to withdraw ether
console.log('Error: ' + err);
// how much ether the account has after running the transaction
var afterWithdraw = web3.eth.getBalance(account0);
var diff = beforeWithdraw - afterWithdraw;
// The account paid for gas to execute the transaction
console.log('Difference: ' + web3.fromWei(diff, "ether"));
})
})
})
});
上述所做的就是检索“角力合约”,将其部署到我们的测试网络中,并且尝试调用提款函数。因为在游戏结束之前没有人能调用提款函数,这就会在合约的提款函数处由于使用“require”指令而返回一个错误。
在开发控制台中运行test:
truffle test --network development
你应该能看到类似如下的输出:
因为执行合约函数是一个异步的进程,你应该使用 async/await 来获得一个更纯净的测试代码。但为了保持简单上手,我们在范例中帮你做了这个工作。
留给你一项模拟角力比赛的练习,你需要保证只有获胜者才能在游戏最后提走合约中的以太币。(和我们在上一节中使用 truflle控制台 所做的十分相像)
你能在这个教程中研究另一个案例。同时别犹豫,去看Truffle的文档。
或者,这里有一些由社区所编写的保证安全性的工具以供你自检代码时使用。列表在此。
『 商业逻辑 』
在合约内部测试函数是必不可少的,但是你也应当跳出来,看看智能合约中函数之间的互动是否能形成一个符合你设计初衷的整体,完好地运行(且没有执行其他事务)。
保证你的智能合约有良好的注释是编写清晰、条理代码的第一步(请注意到我们在第一部分提到的 Wrestling.sol 并没有这样做)。第二步是要保证你的智能合约尽可能地简洁,并且只将应用中需要去中心化的部分书写到智能合约当中。如果你的智能合约是作为一个去中心化应用(简而言之,去中心化应用程序是含有有去中心化的结构的web应用(也就是应用中的一个组成部分是智能合约,或者与智能合约发生交互))的一部分存在的,那就要设计好哪些功能是需要在区块链上运行的,而哪些功能是通过UI手段或者web应用的后端来实现的。
注释:去中心化应用程序的定义比上文要更为广泛,它包括所有应用点对点交互的程序。Mist、BitTorrent、Tor 等等都是能被称之为去中心化的应用。参见Vitalik Buterin的这篇文章。
『 了解开发平台 』
为了明确你所做的工作,你应该阅读这些文档,这一工作不可避免,并且你需要额外搜索这些文档之外Solidity开发的方方面面。
举例而言,你应该知道定点数变量(也叫浮点或双精度浮点)是 Solidity 暂时未能完全支持的,并且应该知道在例子二中,当使用 uint 类型来表示 3/7 时会转换到最接近的整数的区别。是故你不应该在一个仍然处于高频开发的平台上把一些东西看作理所当然的。
人都能知道你合约中变量的内容,你也仅仅能够尝试混淆其中信息的内容,而不能彻底把信息隐藏起来。在生成一个随机数或是随机字符串时也是同样的道理,每一个人都能看到你是如何生成这些信息的,这就存在某些不怀好意者精心伪造变量的可能性。事实上正有人在努力探究生成一个随机数的最好方法,如果那正和你意,那么欢迎加入。正如我之前提及的,所有东西都在进行着高频开发,对于很多东西其实并没有工业化的标准。
依赖时间的合约也是一个研究的热门,如果你的合约需要在一个特定的时间运行,你就只能依靠一个外部程序去实现(记住,有时这会导致合约停止或暂停),因为智能合约本身是无法计时的。如果你的智能合约在某些方便面需要时间来做判断,谨记一些不怀好意的矿工可能会通过破坏转账执行的时间来进行攻击。
牢牢记住你的合约是公开的,每个人都能阅读合约内容并与之交互,并且如果你的合约调用了外部别的合约或和他们交互的话,你必须警惕其他恶意的合约可能会破坏你自身合约的执行。
所有的这一切可能有些过于谨慎了,但是如果有一大笔处于不安全状态的资金,人们可能就会想方设法去操作了。如果你的智能合约仅仅是向老年人卖纸尿布的话,你可能不需要考虑那么多。
『 平台的局限性 』
明晰以太坊开发平台并不能用于大量的计算,并且你的转账操作受限于 gas 上限。你必须把逻辑设计的尽可能简单,并且小心无限循环,存储限制,值溢出和其他像这样的微小细节。因为当智能合约部署到区块链之上后就无法再更改了,你应该在部署之前就考虑到这些方方面面。
编译器和以太坊区块链背后的软件正在处于高频的开发之中,并且经历着持续不断的迭代,所以你应该牢记这些特性,并且保持更新。
『 第三方 』
有很多好的开发者希望便利用户和其他开发者,使其能够不需要在本地下载以太坊主链就能登入以太坊网络。比如说 MEW 能让你转账以太币并且部署合约,又如 INFURA 这样优秀的工具能让你搭配 Truffle,通过 API 接入以太坊主链。毋庸置疑这些工具良好的出发点,以及它们背后开发者卓越的技能,但是是否要将追求便利凌驾于安全性就是你自己的选择了。由于大量的转账操作,上述类型的工具通常都比你自己电脑上的节点更容易成为黑客攻击的目标。总的来说,你所采取的技术手段主要取决你操作和转账的金额大小。
『 一些方向,以及从何处开始 』
在结束这篇教程之前,向你提供一些可以开始研究的素材:
由@KonstantHacker提供的这个视频是个很好的入门介绍
Solidity常见模式文档
Solidity安全性考量文档
其他开发技巧详解
必须牢记,一旦涉及到安全问题,那么没有一份文档是完全能解决的,所以你必须根据自己的具体需求,调查实践,注意考虑到方方面面并用良好的编程技巧来实现。
最重要的是,加入到智能合约的编写中来,有许多开源的项目都需要开发者来检验他们的代码,寻找漏洞,只要用心,你可是能从中获取很多奖励的。
如果你是那个发现漏洞的有心人,或者找到了实现某种策略的更好方法,别吝啬分享你的知识。以太坊和区块链都拥抱着开放的原则,整个社区都会因为这一点一滴的发现而获益。
这一部分的代码能在 Github 上找到。
devzl/ethereum-walkthrough-3
< https://github.com/devzl/ethereum-walkthrough-3 >
『 结论 』
总而言之,你必须有一个作为智能合约开发者的思维模式,并且在发布合约之前完成一系列的调研和测试。正如Moody教授所言:
如果你喜欢这一部分,你可以联系我 @de_zl。
在下一节,我们将介绍代币。
原文链接: https://hackernoon.com/ethereum-development-walkthrough-part-3-security-limitations-and-considerations-d482f05278b4
作者: de_zl
翻译&校对: 安仔Clint & Elisa
译者:安仔Clint
技术投机客,关注区块链落地应用
本文由作者授权 EthFans 翻译及再出版。
你可能还会喜欢:
如何使用Solidity编写安全的智能合约代码?<https://ethfans.org/rubyu2/articles/128>
智能合约最佳实践之智能合约安全技术 <https://ethfans.org/tolak/articles/smart-contract-best-practices>
教程 | 以太坊编程的简单介绍(1)