通用模式——Solidity中文文档(11)

写在前面:HiBlock区块链社区成立了翻译小组,翻译区块链相关的技术文档及资料,本文为Solidity文档翻译的第十一部分《通用模式》,特发布出来邀请solidity爱好者、开发者做公开的审校,您可以添加微信baobaotalk_com,验证输入“solidity”,然后将您的意见和建议发送给我们,也可以在文末“留言”区留言,有效的建议我们会采纳及合并进下一版本,同时将送一份小礼物给您以示感谢。

1

从合约中提款

在某个操作之后发送资金的推荐方式是使用取回(withdrawal)模式。尽管在某个操作之后,最直接地发送以太币方法是一个 send 调用, 但这并不推荐;因为这会引入一个潜在的安全风险。你可能需要参考 安全考量 来获取更多信息。

这里是一个在合约中使用取回模式的示例,它目标是通过向合约发送最多的钱来成为“最富有的人”, 其灵感来自 King of the Ether(https://www.kingoftheether.com/thrones/kingoftheether/index.html)。

在下边的合约中,如果你的“最富有”位置被其他人取代,你可以收到取代你成为“最富有”的人发送到合约的资金。

pragma solidity ^0.4.11;

contract WithdrawalContract {
   address public richest;
   uint public mostSent;

   mapping (address => uint) pendingWithdrawals;

   function WithdrawalContract() public payable {
       richest = msg.sender;
       mostSent = msg.value;
   }

   function becomeRichest() public payable returns (bool) {
       if (msg.value > mostSent) {
           pendingWithdrawals[richest] += msg.value;
           richest = msg.sender;
           mostSent = msg.value;
           return true;
       } else {
           return false;
       }
   }

   function withdraw() public {
       uint amount = pendingWithdrawals[msg.sender];
       // 记住,在发送资金之前将待发金额清零
       // 来防止重入(re-entrancy)攻击
       pendingWithdrawals[msg.sender] = 0;
       msg.sender.transfer(amount);
   }

}

下面是一个相反的直接使用发送模式的例子:

pragma solidity ^0.4.11;

contract SendContract {
   address public richest;
   uint public mostSent;

   function SendContract() public payable {
       richest = msg.sender;
       mostSent = msg.value;
   }

   function becomeRichest() public payable returns (bool) {
       if (msg.value > mostSent) {
           // 这一行会导致问题(详见下文)
           richest.transfer(msg.value);
           richest = msg.sender;
           mostSent = msg.value;
           return true;
       } else {
           return false;
       }
   }

}
复制代码

注意,在这个例子里,攻击者可以给这个合约设下陷阱,使其进入不可用状态,比如通过使一个 fallback 函数会失败的合约成为 richest (可以在 fallback 函数中调用 revert() 或者直接在 fallback 函数中使用超过 2300 gas 来使其执行失败)。这样,当这个合约调用 transfer 来给“下过毒”的合约 发送资金时,调用会失败,从而导致 becomeRichest 函数失败,这个合约也就被永远卡住了。

如果在合约中像第一个例子那样使用“取回(withdraw)”模式,那么攻击者只能使他/她自己的“取回”失败,并不会导致整个合约无法运作。

2

限制访问

限制访问是合约的一个通用模式。注意,你不可能限制任何人或机器读取你的交易内容或合约状态。 你可以通过加密使这种访问变得困难一些,但如果你想让你的合约读取这些数据,那么其他人也将可以做到。

你可以限制 其他合约 读取你的合约状态。 这(其他合约不能读取你的合约状态)是默认的,除非你将合约状态变量声明为 public。

此外,你可以对谁可以修改你的合约状态或调用你的合约函数加以限制,这是本节要介绍的内容。

通过使用“函数 修饰器modifier”,可以使这些限制变得非常明确。

pragma solidity ^0.4.22;

contract AccessRestriction {
   // 这些将在构造阶段被赋值
   // 其中,`msg.sender` 是
   // 创建这个合约的账户。
   address public owner = msg.sender;
   uint public creationTime = now;

   // 修饰器可以用来更改
   // 一个函数的函数体。
   // 如果使用这个修饰器,
   // 它会预置一个检查,仅允许
   // 来自特定地址的
   // 函数调用。
   modifier onlyBy(address _account)
   {
       require(
           msg.sender == _account,
           "Sender not authorized."
       );
       // 不要忘记写 `_;`!
       // 它会被实际使用这个修饰器的
       // 函数体所替代。
       _;
   }

   // 使 `_newOwner` 成为这个合约的
   // 新所有者。
   function changeOwner(address _newOwner)
       public
       onlyBy(owner)
   {
       owner = _newOwner;
   }

   modifier onlyAfter(uint _time) {
       require(
           now >= _time,
           "Function called too early."
       );
       _;
   }

   // 抹掉所有者信息。
   // 仅允许在合约创建成功 6 周以后
   // 的时间被调用。
   function disown()
       public
       onlyBy(owner)
       onlyAfter(creationTime + 6 weeks)
   {
       delete owner;
   }

   // 这个修饰器要求对函数调用
   // 绑定一定的费用。
   // 如果调用方发送了过多的费用,
   // 他/她会得到退款,但需要先执行函数体。
   // 这在 0.4.0 版本以前的 Solidity 中很危险,
   // 因为很可能会跳过 `_;` 之后的代码。
   modifier costs(uint _amount) {
       require(
           msg.value >= _amount,
           "Not enough Ether provided."
       );
       _;
       if (msg.value > _amount)
           msg.sender.send(msg.value - _amount);
   }

   function forceOwnerChange(address _newOwner)
       public
       payable
       costs(200 ether)
   {
       owner = _newOwner;
       // 这只是示例条件
       if (uint(owner) & 0 == 1)
           // 这无法在 0.4.0 版本之前的
           // Solidity 上进行退还。
           return;
       // 退还多付的费用
   }

}
复制代码

一个更专用地限制函数调用的方法将在下一个例子中介绍。

3

状态机

合约通常会像状态机那样运作,这意味着它们有特定的 阶段,使它们有不同的表现或者仅允许特定的不同函数被调用。 一个函数调用通常会结束一个阶段,并将合约转换到下一个阶段(特别是如果一个合约是以 交互 来建模的时候)。 通过达到特定的 时间 点来达到某些阶段也是很常见的。

一个典型的例子是盲拍(blind auction)合约,它起始于“接受盲目出价”, 然后转换到“公示出价”,最后结束于“确定拍卖结果”。

函数 修饰器modifier 可以用在这种情况下来对状态进行建模,并确保合约被正常的使用。

示例

在下边的示例中, 修饰器modifier atStage 确保了函数仅在特定的阶段才可以被调用。

根据时间来进行的自动阶段转换,是由 修饰器modifier timeTransitions 来处理的, 它应该用在所有函数上。

最后, 修饰器modifier transitionNext 能够用来在函数执行结束时自动转换到下一个阶段。

pragma solidity ^0.4.22;

contract StateMachine {
   enum Stages {
       AcceptingBlindedBids,
       RevealBids,
       AnotherStage,
       AreWeDoneYet,
       Finished
   }

   // 这是当前阶段。
   Stages public stage = Stages.AcceptingBlindedBids;

   uint public creationTime = now;

   modifier atStage(Stages _stage) {
       require(
           stage == _stage,
           "Function cannot be called at this time."
       );
       _;
   }

   function nextStage() internal {
       stage = Stages(uint(stage) + 1);
   }

   // 执行基于时间的阶段转换。
   // 请确保首先声明这个修饰器,
   // 否则新阶段不会被带入账户。
   modifier timedTransitions() {
       if (stage == Stages.AcceptingBlindedBids &&
                   now >= creationTime + 10 days)
           nextStage();
       if (stage == Stages.RevealBids &&
               now >= creationTime + 12 days)
           nextStage();
       // 由交易触发的其他阶段转换
       _;
   }

   // 这里的修饰器顺序非常重要!
   function bid()
       public
       payable
       timedTransitions
       atStage(Stages.AcceptingBlindedBids)
   {
       // 我们不会在这里实现实际功能(因为这仅是个代码示例,译者注)
   }

   function reveal()
       public
       timedTransitions
       atStage(Stages.RevealBids)
   {
   }

   // 这个修饰器在函数执行结束之后
   // 使合约进入下一个阶段。
   modifier transitionNext()
   {
       _;
       nextStage();
   }

   function g()
       public
       timedTransitions
       atStage(Stages.AnotherStage)
       transitionNext
   {
   }

   function h()
       public
       timedTransitions
       atStage(Stages.AreWeDoneYet)
       transitionNext
   {
   }

   function i()
       public
       timedTransitions
       atStage(Stages.Finished)
   {
   }

}
复制代码

延伸阅读:智能合约-Solidity官方文档(1)

安装Solidity编译器-Solidity官方文档(2)

根据例子学习Solidity-Solidity官方文档(3)

深入理解Solidity之源文件及合约结构——Solidity中文文档(4)

安全考量——Solidity中文文档(5)

合约的元数据——Solidity中文文档(6)

应用二进制接口(ABI) 说明——Solidity中文文档(7)

使用编译器——Solidity中文文档(8)

Yul语言及对象说明——Solidity中文文档(9)

风格指南——Solidity中文文档(10)

点击“阅读原文”即可查看完整中文文档

本文内容来源于HiBlock区块链社区翻译小组,感谢全体译者的辛苦工作。点击“阅读原文”即可查看完整中文文档。

:本文为solidity翻译的第十一部分《通用模式》,特发布出来邀请solidity爱好者、开发者做公开的审校,您可以添加微信baobaotalk_com,验证输入“solidity”,然后将您的意见和建议发送给我们,也可在文末“留言”区留言,或通过原文链接访问我们的Github。有效的建议我们会收纳并及时改进,同时将送一份小礼物给您以示感谢。

线上课程推荐

线上课程:《8小时区块链智能合约开发实践》

培训讲师:《白话区块链》作者 蒋勇

课程原价:999元,现价 399元

更多福利:

  • @所有人,识别下图二维码转发课程邀请好友报名,即可获得报名费50%返利

  • @学员,报名学习课程并在规定时间内完成考试即可瓜分10000元奖金

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值