当前位置:   article > 正文

cryptozombies——solidity语法学习三_modifier onlyowner

modifier onlyowner

一、智能协议的永固性

   在有几点以太坊上的 DApp 跟普通的应用程序有着天壤之别。

  在你把智能协议传上以太坊之后,它就变得不可更改, 这种永固性意味着你的代码永远不能被调整或更新。

你编译的程序会一直,永久的,不可更改的,存在以太网上。这就是Solidity代码的安全性如此重要的一个原因。如果你的智能协议有任何漏洞,即使你发现了也无法补救。你只能让你的用户们放弃这个智能协议,然后转移到一个新的修复后的合约上。

但这恰好也是智能合约的一大优势。 代码说明一切。 如果你去读智能合约的代码,并验证它,你会发现, 一旦函数被定义下来,每一次的运行,程序都会严格遵照函数中原有的代码逻辑一丝不苟地执行,完全不用担心函数被人篡改而得到意外的结果。

二、Ownable Contracts(OpenZeppelin库的Ownable 合约)

  1. /**
  2. * @title Ownable
  3. * @dev The Ownable contract has an owner address, and provides basic authorization control
  4. * functions, this simplifies the implementation of "user permissions".
  5. */
  6. contract Ownable {
  7. address public owner;
  8. event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
  9. /**
  10. * @dev The Ownable constructor sets the original `owner` of the contract to the sender
  11. * account.
  12. */
  13. function Ownable() public {
  14. owner = msg.sender;
  15. }
  16. /**
  17. * @dev Throws if called by any account other than the owner.
  18. */
  19. modifier onlyOwner() {
  20. require(msg.sender == owner);
  21. _;
  22. }
  23. /**
  24. * @dev Allows the current owner to transfer control of the contract to a newOwner.
  25. * @param newOwner The address to transfer ownership to.
  26. */
  27. function transferOwnership(address newOwner) public onlyOwner {
  28. require(newOwner != address(0));
  29. OwnershipTransferred(owner, newOwner);
  30. owner = newOwner;
  31. }
  32. }
  • 构造函数:function Ownable()是一个 constructor (构造函数),构造函数不是必须的,它与合约同名,构造函数一生中唯一的一次执行,就是在合约最初被创建的时候。

  • 函数修饰符:modifier onlyOwner()。 修饰符跟函数很类似,不过是用来修饰其他已有函数用的, 在其他语句执行前,为它检查下先验条件。 在这个例子中,我们就可以写个修饰符 onlyOwner 检查下调用者,确保只有合约的主人才能运行本函数。我们下一章中会详细讲述修饰符,以及那个奇怪的_;

  • indexed 关键字:别担心,我们还用不到它。

所以Ownable 合约基本都会这么干:

  1. 合约创建,构造函数先行,将其 owner 设置为msg.sender(其部署者)

  2. 为它加上一个修饰符 onlyOwner,它会限制陌生人的访问,将访问某些函数的权限锁定在 owner 上。

  3. 允许将合约所有权转让给他人

 三、onlyOwner 函数修饰符

函数修饰符看起来跟函数没什么不同,不过关键字modifier 告诉编译器,这是个modifier(修饰符),而不是个function(函数)。它不能像函数那样被直接调用,只能被添加到函数定义的末尾,用以改变函数的行为。

咱们仔细读读 onlyOwner:

  1. /**
  2. * @dev 调用者不是‘主人’,就会抛出异常
  3. */
  4. modifier onlyOwner() {
  5. require(msg.sender == owner);
  6. _;
  7. }

onlyOwner 函数修饰符是这么用的:

  1. contract MyContract is Ownable {
  2. event LaughManiacally(string laughter);
  3. //注意! `onlyOwner`上场 :
  4. function likeABoss() external onlyOwner {
  5. LaughManiacally("Muahahahaha");
  6. }
  7. }

注意 likeABoss 函数上的 onlyOwner 修饰符。 当你调用 likeABoss时,首先执行 onlyOwner 中的代码, 执行到 onlyOwner 中的 _; 语句时,程序再返回并执行 likeABoss 中的代码。

可见,尽管函数修饰符也可以应用到各种场合,但最常见的还是放在函数执行之前添加快速的 require检查。

因为给函数添加了修饰符 onlyOwner,使得唯有合约的主人(也就是部署者)才能调用它。

注意:主人对合约享有的特权当然是正当的,不过也可能被恶意使用。比如,万一,主人添加了个后门,允许他偷走别人的僵尸呢?

所以非常重要的是,部署在以太坊上的 DApp,并不能保证它真正做到去中心,你需要阅读并理解它的源代码,才能防止其中没有被部署者恶意植入后门;作为开发人员,如何做到既要给自己留下修复 bug 的余地,又要尽量地放权给使用者,以便让他们放心你,从而愿意把数据放在你的 DApp 中,这确实需要个微妙的平衡

 四、Gas - 驱动以太坊DApps的能源

 在 Solidity 中,你的用户想要每次执行你的 DApp 都需要支付一定的 gas,gas 可以用以太币购买,因此,用户每次跑 DApp 都得花费以太币。

一个 DApp 收取多少 gas 取决于功能逻辑的复杂程度。每个操作背后,都在计算完成这个操作所需要的计算资源,(比如,存储数据就比做个加法运算贵得多), 一次操作所需要花费的 gas 等于这个操作背后的所有运算花销的总和。

由于运行你的程序需要花费用户的真金白银,在以太坊中代码的编程语言,比其他任何编程语言都更强调优化。同样的功能,使用笨拙的代码开发的程序,比起经过精巧优化的代码来,运行花费更高,这显然会给成千上万的用户带来大量不必要的开销

1. 为什么要用 gas 来驱动?

以太坊就像一个巨大、缓慢、但非常安全的电脑。当你运行一个程序的时候,网络上的每一个节点都在进行相同的运算,以验证它的输出 —— 这就是所谓的”去中心化“ 由于数以千计的节点同时在验证着每个功能的运行,这可以确保它的数据不会被被监控,或者被刻意修改。

可能会有用户用无限循环堵塞网络,抑或用密集运算来占用大量的网络资源,为了防止这种事情的发生,以太坊的创建者为以太坊上的资源制定了价格,想要在以太坊上运算或者存储,你需要先付费。

注意:如果你使用侧链,倒是不一定需要付费。你不会想要在以太坊主网上玩儿“魔兽世界”吧? - 所需要的 gas 可能会买到你破产。但是你可以找个算法理念不同的侧链来玩它。

 2.省 gas 的招数:结构封装 (Struct packing)

 通常情况下我们不会考虑使用 uint 变种,因为无论如何定义 uint的大小,Solidity 为它保留256位的存储空间。例如,使用 uint8 而不是uintuint256)不会为你节省任何 gas。

除非,把 uint 绑定到 struct 里面。

如果一个 struct 中有多个 uint,则尽可能使用较小的 uint, Solidity 会将这些 uint 打包在一起,从而占用较少的存储空间。例如:

  1. struct NormalStruct {
  2. uint a;
  3. uint b;
  4. uint c;
  5. }
  6. struct MiniMe {
  7. uint32 a;
  8. uint32 b;
  9. uint c;
  10. }
  11. // 因为使用了结构打包,`mini` 比 `normal` 占用的空间更少
  12. NormalStruct normal = NormalStruct(10, 20, 30);
  13. MiniMe mini = MiniMe(10, 20, 30);

所以,当 uint 定义在一个 struct 中的时候,尽量使用最小的整数子类型以节约空间。 并且把同样类型的变量放一起(即在 struct 中将把变量按照类型依次放置),这样 Solidity 可以将存储空间最小化。例如,有两个 struct

uint c; uint32 a; uint32 b; 和 uint32 a; uint c; uint32 b;

前者比后者需要的gas更少,因为前者把uint32放一起了

3.时间单位 

Solidity 使用自己的本地时间单位。

变量 now 将返回当前的unix时间戳(自1970年1月1日以来经过的秒数)。我写这句话时 unix 时间是 1515527488

注意:Unix时间传统用一个32位的整数进行存储。这会导致“2038年”问题,当这个32位的unix时间戳不够用,产生溢出,使用这个时间的遗留系统就麻烦了。所以,如果我们想让我们的 DApp 跑够20年,我们可以使用64位整数表示时间,但为此我们的用户又得支付更多的 gas。真是个两难的设计啊!

Solidity 还包含秒(seconds)分钟(minutes)小时(hours)天(days)周(weeks) 和 年(years) 等时间单位。它们都会转换成对应的秒数放入 uint 中。所以 1分钟 就是 601小时是 3600(60秒×60分钟),1天86400(24小时×60分钟×60秒),以此类推。

下面是一些使用时间单位的实用案例:

  1. uint lastUpdated;
  2. // 将‘上次更新时间’ 设置为 ‘现在’
  3. function updateTimestamp() public {
  4. lastUpdated = now;
  5. }
  6. // 如果到上次`updateTimestamp` 超过5分钟,返回 'true'
  7. // 不到5分钟返回 'false'
  8. function fiveMinutesHavePassed() public view returns (bool) {
  9. return (now >= (lastUpdated + 5 minutes));
  10. }

 4.结构体

由于结构体的存储指针可以以参数的方式传递给一个 private 或 internal 的函数,因此结构体可以在多个函数之间相互传递。

 遵循这样的语法:

  1. function _doStuff(Zombie storage _zombie) internal {
  2. // do stuff with _zombie
  3. }

5.带参数的函数修饰符

之前我们已经读过一个简单的函数修饰符了:onlyOwner。函数修饰符也可以带参数。例如:

  1. / 存储用户年龄的映射
  2. mapping (uint => uint) public age;
  3. // 限定用户年龄的修饰符
  4. modifier olderThan(uint _age, uint _userId) {
  5. require(age[_userId] >= _age);
  6. _;
  7. }
  8. // 必须年满16周岁才允许开车 (至少在美国是这样的).
  9. // 我们可以用如下参数调用`olderThan` 修饰符:
  10. function driveCar(uint _userId) public olderThan(16, _userId) {
  11. // 其余的程序逻辑
  12. }

看到了吧, olderThan 修饰符可以像函数一样接收参数,是“宿主”函数 driveCar 把参数传递给它的修饰符的。

6.利用 'View' 函数节省 Gas

这是因为 view 函数不会真正改变区块链上的任何数据 - 它们只是读取。因此用 view 标记一个函数,意味着告诉 web3.js,运行这个函数只需要查询你的本地以太坊节点,而不需要在区块链上创建一个事务(事务需要运行在每个节点上,因此花费 gas)。

注意:如果一个 view 函数在另一个函数的内部被调用,而调用函数与 view 函数的不属于同一个合约,也会产生调用成本。这是因为如果主调函数在以太坊创建了一个事务,它仍然需要逐个节点去验证。所以标记为 view 的函数只有在外部调用时才是免费的。

7.存储非常昂贵

Solidity 使用storage(存储)是相当昂贵的,”写入“操作尤其贵。

这是因为,无论是写入还是更改一段数据, 这都将永久性地写入区块链。”永久性“啊!需要在全球数千个节点的硬盘上存入这些数据,随着区块链的增长,拷贝份数更多,存储量也就越大。这是需要成本的!

为了降低成本,不到万不得已,避免将数据写入存储。这也会导致效率低下的编程逻辑 - 比如每次调用一个函数,都需要在 memory(内存) 中重建一个数组,而不是简单地将上次计算的数组给存储下来以便快速查找。

在大多数编程语言中,遍历大数据集合都是昂贵的。但是在 Solidity 中,使用一个标记了external view的函数,遍历比 storage 要便宜太多,因为 view 函数不会产生任何花销。 (gas可是真金白银啊!)。

在内存中声明数组:

在数组后面加上 memory关键字, 表明这个数组是仅仅在内存中创建,不需要写入外部存储,并且在函数调用结束时它就解散了。与在程序结束时把数据保存进 storage 的做法相比,内存运算可以大大节省gas开销 -- 把这数组放在view里用,完全不用花钱。

以下是申明一个内存数组的例子:

  1. function getArray() external pure returns(uint[]) {
  2. // 初始化一个长度为3的内存数组
  3. uint[] memory values = new uint[](3);
  4. // 赋值
  5. values.push(1);
  6. values.push(2);
  7. values.push(3);
  8. // 返回数组
  9. return values;
  10. }

注意:内存数组 必须 用长度参数(在本例中为3)创建。目前不支持 array.push()之类的方法调整数组大小,在未来的版本可能会支持长度修改。

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/369174
推荐阅读
相关标签
  

闽ICP备14008679号