Pre:
对OpenZeppelin_ERC20_extensions文件夹里的自定义拓展合约,源码学习一波。
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/README.adoc
目录:
Contract | 使用场景 |
---|---|
ERC20Burnable | 对自己或有授权额度的地址进行代币销毁 |
ERC20Capped | 在铸造代币时对总供应量设定一个不可变的上限值 |
ERC20Pausable | 具有暂停代币传输的功能 |
ERC20Snapshot | 有效存储过去的代币余额/供应量,以便以后随时查询 |
ERC20Burnable:
使用场景
对自己或有授权额度的地址进行代币销毁,使得代币总供应量↓,可能会让用户觉得自己持有的币会越来越有价值。。。
源码解析:
多了一个burnFrom()
委托销毁的函数
重要函数变量表:
Contract | Function/Variable | 作用 | 操作 |
---|---|---|---|
ERC20Burnable | burn(uint256 amount) | token持有者销毁自己的token | 调用_burn 函数,余额↓,总供应量↓ |
ERC20Burnable | burnFrom(address account, uint256 amount) | token授权者销毁指定地址的token | caller授权额度↓,指定地址余额↓,总供应量↓ |
Inspire:
授权额度给别人或合约之后,第三者不只是可以拿来转账,也可以用来销毁,简言之就是具备了控制权
ERC20Capped:
使用场景
在代币的构造函数里对总供应量设定一个不可变的上限值
,可防止在代码实现中不小心改动了这个值,从而防止代币无限增发。
注意:是上限值
不可变,总供应量
还是可变的,只是上限值
被写死了。
源码解析:
1 | // 重要代码片段: |
重要函数变量表:
Contract | Function/Variable | 作用 | 操作 |
---|---|---|---|
ERC20Capped | constructor(uint256 cap_) | 只有在构造函数能够修改代币供应量上限 的值 |
提前定义上限值为immutable 的变量 |
ERC20Capped | cap() | 返回上限值 | —— |
ERC20Capped | _mint(address account, uint256 amount) | 铸币增发 | 判断:代币供应量+增发量<= 上限值 |
Inspire:
看来solidity里的各类修饰符 如immutable
,还是大有学问的,在某些特定场景上使用起来能够起到合适的限制,后面要仔细总结一下
同理,对于某些通缩机制的代币,也可以设定一个下限值,在不断销毁代币时,也不能低于这个下限值
ERC20Pausable:
使用场景
出bug的时候,可以由管理员设置暂停开关
,阻止代币的传输、铸造和燃烧等功能,相当于一个紧急停止机制。
源码解析:
1 | // 重要代码片段: |
ERC20Pausable.sol
继承Pausable.sol,Pausable.sol
里围绕一个_paused
暂停开关写了几个event
和modifier
,核心还是这个暂停开关变量。
如果自己的erc20合约想要实现暂停开关功能的话,继承ERC20
, Pausable
合约,重写_beforeTokenTransfer
函数,在里面加上对暂停开关的判断条件就可以了。
其中_beforeTokenTransfer
函数在_transfer
函数里被调用到。
ERC20.sol
的_transfer
函数里可以分成3个阶段:
-
token转移之前,
_beforeTokenTransfer()
-
token转移时的具体处理逻辑(一般是
sender
余额↓,recipient
余额↑) -
token转移之后,
_afterTokenTransfer
控制了_beforeTokenTransfer
函数后,就不会进入到token转移时的具体处理逻辑,也就控制了代币的转账、铸造、销毁这三类行为,从而实现了暂停功能。
重要函数变量表:
Contract | Function | 作用 | 操作 |
---|---|---|---|
Pausable | constructor() | —— | 暂停开关初始化 为关 |
ERC20Pausable | function _beforeTokenTransfer(address from,address to,uint256 amount) | 阻止进入token转移的处理逻辑 | 加上对暂停开关变量的判断 |
ERC20Snapshot:
使用快照机制扩展了 ERC20 代币。创建快照时,记录用户余额和当时的总供应量,以供以后访问。
快照不是每个区块或每隔一段时间都会自动生成的,快照的周期依附于你设定的快照策略。
比如你可以设定每次
Transfer
执行一次快照等
使用场景:
这可用于安全地创建基于代币余额的机制,例如无需信任的股息或加权投票。
可以通过重用来自不同地方的相同余额来执行“双花”攻击帐户。通过使用快照来计算股息或投票权,这些攻击不再适用。
它也可以用于创建高效的 ERC20 分叉机制
源码解析:
开始看之前有一些疑问:
-
用户余额该用什么数据结构来存储?
-
什么时候、多少周期创建一个快照?
存储的数据结构:
-
存储用户的余额:
1 | struct Snapshots { |
存储用户余额的变量_accountBalanceSnapshots
是一个二维的数据结构,类似于python
里一个dict
里存放两个list
,两个list
里的值索引是相对应的:
1 | "addr":地址做键值 |
-
存储总供应量:
1 | Snapshots private _totalSupplySnapshots; |
存储代币总供应量的变量_totalSupplySnapshots
是一个一维的数据结构,类似于python
里就两个list
,两个list
里的值索引是相对应的:
1 | ids数组:存快照id |
源码里的快照id策略:
还没跑过原本的代码,源码里的快照id策略应该是:定义了一个计数器变量,只能单增,具备单调性。
1 | // 快照id单调递增,第一个值为1。id为0无效。 |
自定义快照id策略:
自己实现了一下基于blockNumber
的快照策略,详见文章
继承图:
用Slither
生成的,方便参照去阅读源码
重要函数变量表:
Contract | Function/Variable | 使用场景 | 操作 |
---|---|---|---|
ERC20Snapshot | _accountBalanceSnapshots | 存储用户快照余额 | 二维数据结构 |
ERC20Snapshot | _totalSupplySnapshots | 存储总供应量快照余额 | 一维数据结构 |
ERC20Snapshot | _currentSnapshotId | 当前快照id | id为0无效,要具备单调性如单增 |
ERC20Snapshot | _snapshot() | 创建一个新快照并返回其快照 ID | 可重写该函数,自定义快照策略 |
ERC20Snapshot | _getCurrentSnapshotId() | 获取当前的快照Id | xx |
ERC20Snapshot | _updateSnapshot(Snapshots storage snapshots, uint256 currentValue) | 更新快照 | 注意里面的触发快照的条件 |
Template
Contract | Function/Variable | 使用场景 | 操作 |
---|---|---|---|
xx | xx | xx | xx |
xx | xx | xx | xx |
Refs:
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/README.adoc