cryptozombies(加密僵尸)/solidity语法学习四

一:可支付(payable)

  1. 我们有决定函数何时和被谁调用的可见性修饰符: private 意味着它只能被合约内部调用; internal 就像 private 但是也能被继承的合约调用; external 只能从合约外部调用;最后 public 可以在任何地方调用,不管是内部还是外部。
  2. 我们也有状态修饰符, 告诉我们函数如何和区块链交互: view 告诉我们运行这个函数不会更改和保存任何数据; pure 告诉我们这个函数不但不会往区块链写数据,它甚至不从区块链读取数据。这两种在被从合约外部调用的时候都不花费任何gas(但是它们在被内部其他函数调用的时候将会耗费gas)。
  3. 然后我们有了自定义的 modifiers,例如 onlyOwner 和 olderThan。 对于这些修饰符我们可以自定义其对函数的约束逻辑。

这些修饰符可以同时作用于一个函数定义上:

function test() external view onlyOwner anotherModifier {

}
payable 修饰符

payable是一个新的函数修饰符.
payable 方法是让 Solidity 和以太坊变得如此酷的一部分 —— 它们是一种可以接收以太的特殊函数。

先放一下。当你在调用一个普通网站服务器上的API函数的时候,你无法用你的函数传送美元——你也不能传送比特币。

但是在以太坊中, 因为钱 (以太), 数据 (事务负载), 以及合约代码本身都存在于以太坊。你可以在同时调用函数 并付钱给另外一个合约。

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

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, 而你尝试利用上面的方法发送以太,函数将拒绝你的事务。

二:提现

知道了如何向合约发送以太,那么在发送之后会发生什么呢?

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

你可以写一个函数来从合约中提现以太,类似这样:

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)。

以太坊上的代币

这里写图片描述
这里写图片描述
这里写图片描述

ERC20标准和ERC721标准

ERC20标准:

contract ERC20 {

        function totalSupply() constant returns (uint totalSupply);         //获取总的发行量

        function balanceOf(address _owner) constant returns (uint balance); //查询账户余额

        function transfer(address _to, uint _value)returns(bool success); // 发送Token到某个地址(转账)

        function transferFrom(address _from, address _to, uint _value) returns (bool success); //从地址from 发送token到to地址

        function approve(address _spender, uint _value)returns(bool success);//允许_spender从你的账户转出token

        function allowance(address _owner, address _spender) constant returns (uint remaining);//查询允许spender转移的Token数量

        event Transfer(address indexed _from, address indexed _to, uint _value);//transfer方法调用时的通知事件

        event Approval(address indexed _owner, address indexed _spender, uint _value); //approve方法调用时的通知事件

}

ERC721标准

contract ERC721 {
  event Transfer(address indexed _from, address indexed _to, uint256 _tokenId);//当成功转移token时,一定要触发Transfer事件
  event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId);//当调用approval函数成功时,一定要触发Approval事件

  function balanceOf(address _owner) public view returns (uint256 _balance);//返回传入address地址拥有多少代币
  function ownerOf(uint256 _tokenId) public view returns (address _owner);//传入一个代币Id作为参数,返回该代币拥有者的地址address
  function transfer(address _to, uint256 _tokenId) public;//代币的拥有者调用这个方法,传入他想转移到的address和他想转移的代币Id
  function approve(address _to, uint256 _tokenId) public;//代币的拥有者调用该方法,参数同上,该合约会存储谁被允许提取代币,通常存储到一个mapping (uint256 => address)里.然后,当有人调用takeOwnership时,合约会检查msg.sender 是否得到拥有者的批准来提取代币,如果是,则将代币转移给他。
  function takeOwnership(uint256 _tokenId) public;//transfer 和 takeOwnership 都将包含相同的转移逻辑,只是以相反的顺序。 (一种情况是代币的发送者调用函数;另一种情况是代币的接收者调用它)
}

实现一个代币合约

在实现一个代币合约的时候,我们首先要做的是将接口复制到它自己的 Solidity 文件并导入它,import ./erc721.sol。 接着,让我们的合约继承它,然后我们用一个函数定义来重写每个方法。
在Solidity中,合约可以继承多个合约.

contract ZombieOwnership is ZombieAttack, ERC721 {

  function balanceOf(address _owner) public view returns (uint256 _balance) {

  }

  function ownerOf(uint256 _tokenId) public view returns (address _owner) {

  }

  function transfer(address _to, uint256 _tokenId) public {

  }

  function approve(address _to, uint256 _tokenId) public {

  }

  function takeOwnership(uint256 _tokenId) public {

  }
}

ERC721:批准

使用 approve 或者 takeOwnership 的时候,转移有2个步骤:

你,作为所有者,用新主人的 address 和你希望他获取的 _tokenId 来调用 approve

新主人用 _tokenId 来调用 takeOwnership,合约会检查确保他获得了批准,然后把代币转移给他。

因为这发生在2个函数的调用中,所以在函数调用之间,我们需要一个数据结构来存储什么人被批准获取什么。

三:预防溢出

用户不会不小心把他们的僵尸转移给0 地址(这被称作 “烧币”, 基本上就是把代币转移到一个谁也没有私钥的地址,让这个代币永远也无法恢复)。
https://openzeppelin.org/ 一个智能合约审查服务商
这里写图片描述

这里写图片描述

library SafeMath {

  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    if (a == 0) {
      return 0;
    }
    uint256 c = a * b;
    assert(c / a == b);
    return c;
  }

  function div(uint256 a, uint256 b) internal pure returns (uint256) {
    // assert(b > 0); // Solidity automatically throws when dividing by 0
    uint256 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    return c;
  }

  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    assert(b <= a);
    return a - b;
  }

  function add(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a + b;
    assert(c >= a);
    return c;
  }
}

猜你喜欢

转载自blog.csdn.net/uziandkobe/article/details/80451055