智能合约

【CryptoZombies|编写区块链游戏学智能合约】Less

2018-05-01  本文已影响203人  空灵一月

CryptoZombies是个在编游戏的过程中学习Solidity智能合约语言的互动教程。本教程是为了Solidity初学者而设计的,会从最基础开始教起,即便你从来没有接触过Solidity也可以学,CryptoZombies会手把手地教你。


在这一课,将综合利用在前面课程中学到的许多知识,创建一个僵尸作战系统。 我们也将学习payable函数,学习如何开发可以接收其他玩家付款的DApp。


1.可支付

我们先复习一下函数修饰符,我们有如下决定函数何时和被谁调用的可见性修饰符

我们也有状态修饰符,告诉我们函数如何和区块链交互:

这两种在被从合约外部调用的时候都不花费任何gas(但是它们在被内部其他函数调用的时候将会耗费gas)。

然后我们有了自定义的modifiers,例如在第三课学习的:onlyOwner和aboveLevel。 对于这些修饰符我们可以自定义其对函数的约束逻辑。这些修饰符可以同时作用于一个函数定义上:

function test() external view onlyOwner anotherModifier { /* ... */ }

在这一章,我们来学习一个新的修饰符payable。

payable修饰符

payable方法是让Solidity和以太坊变得如此酷的一部分——它们是一种可以接收以太的特殊函数。当你在调用一个普通网站服务器上的API函数的时候,你无法用你的函数传送美元——你也不能传送比特币。但是在以太坊中, 因为钱 (以太),数据 (事务负载),以及合约代码本身都存在于以太坊。你可以在同时调用函数并付钱给另外一个合约。

这就允许出现很多有趣的逻辑, 比如向一个合约要求支付一定的钱来运行一个函数。来看个例子:

contract OnlineStore {
  function buySomething() external payable {
    // 检查以确定0.001以太发送出去来运行函数:
    require(msg.value == 0.001 ether);
    // 如果为真,一些用来向函数调用者发送数字内容的逻辑
    transferThing(msg.sender);
  }
}

在这里,msg.value是一种可以查看向合约发送了多少以太的方法,另外 ether是一个內建单元。这里发生的事是,一些人会从web3.js调用这个函数 (从DApp的前端), 像这样 :

// 假设 `OnlineStore` 在以太坊上指向你的合约:
OnlineStore.buySomething().send(from: web3.eth.defaultAccount, value: web3.utils.toWei(0.001))

注意这个value字段, JavaScript 调用来指定发送多少(0.001)以太。如果把事务想象成一个信封,你发送到函数的参数就是信的内容。 添加一个 value很像在信封里面放钱 —— 信件内容和钱同时发送给了接收者。

注意: 如果一个函数没标记为payable, 而你尝试利用上面的方法发送以太,函数将拒绝你的事务。


2.提现

在你发送以太之后,它将被存储进以合约的以太坊账户中,并冻结在那里—— 除非你添加一个函数来从合约中把以太提现。你可以写一个函数来从合约中提现以太,类似这样:

contract GetPaid is Ownable {
  function withdraw() external onlyOwner {
    owner.transfer(this.balance);
  }
}

注意我们使用Ownable合约中的owner和onlyOwner,假定它已经被引入了。你可以通过 transfer 函数向一个地址发送以太, 然后this.balance 将返回当前合约存储了多少以太。 所以如果100个用户每人向我们支付1以太, this.balance将是100以太。你可以通过 transfer 向任何以太坊地址付钱。 比如,你可以有一个函数在 msg.sender超额付款的时候给他们退钱:

uint itemFee = 0.001 ether;
msg.sender.transfer(msg.value - itemFee);

或者在一个有买家和卖家的合约中, 你可以把卖家的地址存储起来, 当有人买了它的东西的时候,把买家支付的钱发送给它seller.transfer(msg.value)


3.随机数

keccak256来制造随机数。

Solidity 中最好的随机数生成器是 keccak256 哈希函数,我们可以这样来生成一些随机数:

// 生成一个0到100的随机数:
uint randNonce = 0;
uint random = uint(keccak256(now, msg.sender, randNonce)) % 100;
randNonce++;
uint random2 = uint(keccak256(now, msg.sender, randNonce)) % 100;

这个方法首先拿到now的时间戳、 msg.sender、 以及一个自增数nonce(一个仅会被使用一次的数,这样我们就不会对相同的输入值调用一次以上哈希函数了)。然后利用 keccak 把输入的值转变为一个哈希值, 再将哈希值转换为uint,然后利用% 100来取最后两位, 就生成了一个0到100之间随机数了。

这个方法很容易被不诚实的节点攻击

在以太坊上, 当你在和一个合约上调用函数的时候, 你会把它广播给一个节点或者在网络上的 transaction 节点们。 网络上的节点将收集很多事务,试着成为第一个解决计算密集型数学问题的人,作为“工作证明”,然后将“工作证明”(Proof of Work, PoW)和事务一起作为一个 block 发布在网络上。一旦一个节点解决了一个PoW, 其他节点就会停止尝试解决这个 PoW,并验证其他节点的事务列表是有效的,然后接受这个节点转而尝试解决下一个节点。

这就让我们的随机数函数变得可利用了

假设有一个硬币翻转合约——正面你赢双倍钱,反面你输掉所有的钱。假如它使用上面的方法来决定是正面还是反面 (random >= 50 算正面, random < 50 算反面)。

如果我正运行一个节点,我可以只对我自己的节点发布一个事务,且不分享它。 我可以运行硬币翻转方法来偷窥我的输赢——如果我输了,我就不把这个事务包含进我要解决的下一个区块中去。我可以一直运行这个方法,直到我赢得了硬币翻转并解决了下一个区块,然后获利。

所以我们该如何在以太坊上安全地生成随机数呢?

因为区块链的全部内容对所有参与者来说是透明的, 这就让这个问题变得很难,它的解决方法不在本课程讨论范围,你可以阅读StackOverflow上的讨论 来获得一些灵感。 一个方法是利用 oracle 来访问以太坊区块链之外的随机数函数。

当然, 因为网络上成千上万的以太坊节点都在竞争解决下一个区块,我能成功解决下一个区块的几率非常之低。 这将花费我们巨大的计算资源来开发这个获利方法——但是如果奖励异常地高(比如我可以在硬币翻转函数中赢得 1个亿), 那就很值得去攻击了。

所以尽管这个方法在以太坊上不安全,在实际中,除非我们的随机函数有一大笔钱在上面,你游戏的用户一般是没有足够的资源去攻击的。


4.总结

在本课,我们重新复习了可见性修饰符、状态修饰符和自定义修饰符,学习了payable修饰符,它是可以接收以太的特殊函数,从而通过这个修饰符使区块链上付费变得简单。有付费就有提现(可能转移更准确),提现是通过普通的函数完成,它除了自身的含义外,还包括找零和代付。随机数在智能合约中的使用频率很高,Solidity中最好的随机数生成器是keccak256哈希函数,虽然这个函数很容易被攻击,但要攻击的代价也挺高。

结合前面几节课所学,使用本课的学到的付费、体现和随机数,一个僵尸作战系统的逻辑就出来了(代码就不贴了,有兴趣的可以去学习)。


【CryptoZombies|编写区块链游戏学智能合约】Lesson1: 搭建僵尸工厂
【CryptoZombies|编写区块链游戏学智能合约】Lesson2: 僵尸攻击人类
【CryptoZombies|编写区块链游戏学智能合约】Lesson3: 搭建僵尸工厂
【CryptoZombies|编写区块链游戏学智能合约】Lesson4: 僵尸作战系统
【CryptoZombies|编写区块链游戏学智能合约】Lesson5: ERC721标准和加密收藏品

上一篇下一篇

猜你喜欢

热点阅读