这是个人solidity学习笔记整理
以太坊上的代币
如果你对以太坊的世界有一些了解,你很可能听过人们聊到代币——尤其是 ERC20 代币.
一个 代币 在以太坊基本上就是一个遵循一些共同规则的智能合约——即它实现了所有其他代币合约共享的一组标准函数
例如
transfer(address _to, uint256 _value)和balanceOf(address _owner).
在智能合约内部,通常有一个映射, mapping(address => uint256) balances,用于追踪每个地址还有多少余额
所以基本上一个代币只是一个追踪谁拥有多少该代币的合约,和一些可以让那些用户将他们的代币转移到其他地址的函数。
另一个代币标准更适合加密收藏品——它们被称为ERC721 代币. ERC721 代币是不能互换的,因为每个代币都被认为是唯一且不可分割的。 你只能以整个单位交易它们,并且每个单位都有唯一的 ID。
实现一个代币合约
在实现一个代币合约的时候,我们首先要做的是将接口复制到它自己的 Solidity 文件并导入它,import "./erc721.sol";。 接着,让我们的合约继承它,然后我们用一个函数定义来重写每个方法。
你的合约可以继承自多个合约
balanceOf
function balanceOf(address _owner) public view returns (uint256 _balance);
这个函数只需要一个传入 address 参数,然后返回这个 address 拥有多少代币。
ownerOf
function ownerOf(uint256 _tokenId) public view returns (address _owner);
这个函数需要传入一个代币 ID 作为参数 (我们的情况就是一个僵尸 ID),然后返回该代币拥有者的 address。
同样的,因为在我们的 DApp 里已经有一个 mapping (映射) 存储了这个信息,所以对我们来说这个实现非常直接清晰。我们可以只用一行 return 语句来实现这个函数。
ERC721: 转移标准
ERC721 规范有两种不同的方法来转移代币
function transfer(address _to, uint256 _tokenId) public;
function approve(address _to, uint256 _tokenId) public;
function takeOwnership(uint256 _tokenId) public;
- 第一种方法是代币的拥有者调用
transfer方法,传入他想转移到的address和他想转移的代币的_tokenId。 - 第二种方法是代币拥有者首先调用
approve,然后传入与以上相同的参数。接着,该合约会存储谁被允许提取代币,通常存储到一个mapping (uint256 => address)里。然后,当有人调用takeOwnership时,合约会检查msg.sender是否得到拥有者的批准来提取代币,如果是,则将代币转移给他。
transfer和 takeOwnership 都将包含相同的转移逻辑,只是以相反的顺序。 (一种情况是代币的发送者调用函数;另一种情况是代币的接收者调用它)。
ERC721: 批准
approve
使用 approve 或者 takeOwnership 的时候,转移有2个步骤:
- 你,作为所有者,用新主人的
address和你希望他获取的_tokenId来调用approve - 新主人用
_tokenId来调用takeOwnership,合约会检查确保他获得了批准,然后把代币转移给他。 因为这发生在2个函数的调用中,所以在函数调用之间,我们需要一个数据结构来存储什么人被批准获取什么。
ERC721: takeOwnership
函数 takeOwnership, 应该只是简单地检查以确保 msg.sender 已经被批准来提取这个代币或者僵尸。若确认,就调用 _transfer;
预防溢出
合约安全增强: 溢出和下溢
什么是 溢出 (overflow)?
假设我们有一个 uint8, 只能存储8 bit数据。这意味着我们能存储的最大数字就是二进制 11111111 (或者说十进制的 2^8 - 1 = 255).
uint8 number = 255;
number++;
我们导致了溢出 — 虽然我们加了1, 但是 number 出乎意料地等于 0了。 (如果你给二进制 11111111 加1, 它将被重置为 00000000,就像钟表从 23:59 走向 00:00)。
下溢(underflow)也类似,如果你从一个等于 0的uint8 减去 1, 它将变成 255 (因为 uint 是无符号的,其不能等于负数)
使用 SafeMath
为了防止这些情况,OpenZeppelin 建立了一个叫做 SafeMath 的 库 (library),默认情况下可以防止这些问题。
一个 库 是 Solidity 中一种特殊的合约。其中一个有用的功能是给原始数据类型增加一些方法。
我们来看看 add 的源代码看 SafeMath 做了什么
基本上 add 只是像
+ 一样对两个uint 相加, 但是它用一个 assert 语句来确保结果大于 a。这样就防止了溢出
assert和 require 相似,若结果为否它就会抛出错误。 assert和 require 区别在于,require 若失败则会返还给用户剩下的 gas, assert 则不会。所以大部分情况下,你写代码的时候会比较喜欢 require,assert 只在代码可能出现严重错误的时候使用,比如 uint 溢出。
注释
@title(标题)
@author (作者)
@notice (须知)向 用户 解释这个方法或者合约是做什么的
@dev (开发者) 是向开发者解释更多的细节。
@param (参数)用来描述这个方法需要传入什么参数
@return (返回) 用来描述这个方法需要传入返回什么值