备注
- Solidity初学者看该章之前的前提,还是请至少看完前面两章:
【Solidity学练系列1—搭建僵尸工厂】
【Solidity学练系列2—僵尸攻击人类】 - 妥协了,还是原网站的排版好,所以不折腾了,直接按照原网站的排版了;不过代码部分不会全抄,只抄实战演习 用到的相关的;
- 依然强烈推荐该课程的原开源学习网站 高级Solidity理论
1. 智能协议的永固性
到现在为止,我们讲的Solidity和其他语言没有质的区别,它长得很像JavaScript;
但是,在有几点以太坊上的DApp跟普通的应用程序有着天壤之别。
第一个例子,在你把智能协议传上以太坊之后,它就变得不可更改,这种永固性意味着你的代码永远不能被调整或更新。
你编译的程序会一直,永久的、不可更改的,存在以太坊上。这就是Solidity代码的安全性如此重要的一个原因。如果你的智能协议有任何漏洞,即使你发现了也无法补救,你只能让你的用户们放弃这个智能协议,然后转移到一个新的修复后的合约上。
但这恰好也是智能合约的一大优势。代码说明一切。如果你去读智能合约的代码,并验证它,你会发现,一旦函数被定义下来,每一次的运行,程序都会严格遵守函数中原有的代码逻辑一丝不苟地执行,完全不用担心函数被人篡改而得到意外的结果。
外部依赖关系
在第2课中,我们将加密小猫(CryptoKitties)合约的地址硬编码到DApp中去了。有没有想过,如果加密小猫除了点问题,比方说,集体消失了会怎样?虽然这种事情几乎不可能发生,但是,如果小猫没了,我们的DApp也会随之失效—因为我们在DApp的代码中庸“硬编码”的方式指定了加密小猫的地址,如果这个根据地址找不到小猫,我们的僵尸也就吃不到小猫了,而按照前面的叙述,我们却没法修改合约去应对这个变化!
因此,我们不能硬编码,而要采用“函数”,以便于DApp的关键部分可以以参数形式修改;
比方说,我们不再一开始就把猎物抵制给写入代码,而是写个函数setKittyContractAddress,运行时在设定猎物的地址,这样我们就可以随时去锁定新的猎物,也不用担心加密小猫集体消失了;
实战演习
请修改第2课的代码使得可以通过程序更改CryptoKitty合约地址。
- 删除采用硬编码 方式的 ckAddress 代码行。
- 之前创建 kittyContract 变量的那行代码,修改为对 kittyContract 变量的声明 – 暂时不给它指定具体的实例。
- 创建名为 setKittyContractAddress 的函数, 它带一个参数 _address(address类型), 可见性设为external。
- 在函数内部,添加一行代码,将 kittyContract 变量设置为返回值:KittyInterface(_address)。
// 1. 移除这一行:
//address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d;
// 2. 只声明变量:
//KittyInterface kittyContract = KittyInterface(ckAddress);
KittyInterface kittyContract;
// 3. 增加 setKittyContractAddress 方法
function setKittyContractAddress(address _address) external {
kittyContract = KittyInterface(_address);
}
2. Ownable Contracts
上一章中,您有没有发现任何安全漏洞呢?
呀!setKittyContractAddress可见性居然申明为“外部的” external,岂不是任何人都可以调用它! 也就是说,人和调用该函数的人都可以更改CryptoKitties合约的地址,使得其他人都没法在运行我们的程序了;
我们确实是希望这个地址能够在合约中修改,但我可没说让每个人去改它啊。
要对付这样的情况,通常的做法只指定合约的“所有权”。就是说,给它制定一个主人,只有主人对它享有特权。
Openzepplin库Ownalbe合约
下面是一个Ownable合约的例子:来自**_OpenZeppelin_Solidity*库的Ownable合约。OpenZeppelin是主打安保和社区审查的智能合约库,你可以在自己的DApp中引用。
//Ownable合约有一个owner地址,并提供了基本的授权控制功能,这简化了“用户权限”的实现。
contract Ownable {
address public owner;
event OwnershpTransferred(address indexed previousOwner, address indexed newOwner);
//Ownable构造函数将合同的原始“所有者”设置为发送方帐户
function Ownable() public {
owner = msg.sender;
}
//如果不是owner地址则抛出异常
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
//允许当前的所有者将合同的控制权转移给新的所有者
function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0));
OwnershipTransferred(owner,newOwner);
owner = newOwner;
}
}
- 构造函数: function Ownable() 是一个 constructor (构造函数), 构造函数不是必须的,它与合约同名,构造函数一生中惟一的一次执行,就是在合约最初被创建的时候;
- 函数修饰符:modifier onlyOwner() 修饰符跟函数很类似,不过是用来修饰其他已有函数用的, 在其他语句执行前,为它检查下先验条件。在这个例子中,我们就可恶意下个修饰符onlyOwner检查下调用者,确保只有合约的主人才能运行本函数。
所以Owner合约基本都会这么干:
- 合约创建,构造函数线性,将其owner设置为msg.sender(其部署者);
- 为它加上一个修饰符onlyOwner,它会限制陌生人的访问,将访问某些函数的权限锁定在owner上;
- 允许合约所有权转让给他人;
onluOwner简直人见人爱,大多数人开发自己的Solidity DApps,都是从复制/粘贴Ownable开始的,从它再继承出子类,并在之上进行功能开发;
实战演习
首先,将 Ownable 合约的代码复制一份到新文件 ownable.sol 中。 接下来,创建一个 ZombieFactory,继承 Ownable。
- 在程序中导入 ownable.sol 的内容。 如果您不记得怎么做了,参考下 zombiefeeding.sol。
- 修改 ZombieFactory 合约, 让它继承自 Ownable。 如果您不记得怎么做了,看看 zombiefeeding.sol。