智能合约学习 CryptoZombies 僵尸工厂 第四课修饰符和随机数

171 阅读4分钟

修饰符

可见性修饰符:

  • private意味着它只能被合约内部调用;
  • internal就像private但是也能被继承的合约调用;
  • external只能从合约外部调用;
  • public可以在任何地方调用,不管是内部还是外部。

状态修饰符:

  • view告诉我们运行这个函数不会更改和保存任何数据;
  • pure告诉我们这个函数不但不会往区块链写数据,它甚至不从区块链读取数据。
  • 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很像在信封里面放钱 —— 信件内容和钱同时发送给了接收者。

提现

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

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)

随机数

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 算反面)。

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