0%

Project-CoolErc20-snapshot-快照功能代币

Pre:

参考openzeppelin里的ERC20Snapshot,实现一个具有快照功能的erc20代币
github仓库地址: https://github.com/jerrychan807/cool-erc20/tree/main/snapshot

需求:

代币发生转移时,以当前区块数做快照id,记录发生转移前的用户余额或代币供应量。

部署:

编写合约:

自定义快照策略:

注释里有提到要怎么修改:

可以通过覆盖{_getCurrentSnapshotId} 方法来自定义快照策略。

例如,return block.number ,将在每个新块的开头触发创建快照。当覆盖这个函数,注意其结果的单调性(需要单增)。非单调的快照 id 会破坏合约。

ERC20Snapshot源码里的快照id原本是一个计数器,该计数器只能+1,-1这样去操作。

1
2
// ERC20Snapshot源码
Counters.Counter private _currentSnapshotId;

如果要用当前区块数做快照id,那就要重写该变量,并且重写与该变量有关的两个函数:

  • function _snapshot() internal virtual returns (uint256) {}

  • function _getCurrentSnapshotId() internal view virtual returns (uint256) {}

从而自己去管理这个快照id

还要注意_updateSnapshot()函数里,有个触发快照的判断条件

1
2
3
4
if (_lastSnapshotId(snapshots.ids) < currentId) { // 触发快照的条件:当前id要比上一次存储的id大
snapshots.ids.push(currentId);
snapshots.values.push(currentValue);
}

在测试几次后,发现要在构造器里初始化_currentSnapshotId才行,不然这个_currentSnapshotId一直为0,一直过不了这个判断条件,不执行快照。

在实现的时候,还被private修饰符搞得很晕。。。私有变量不能直接在子类访问或修改,只能通过调用父类的函数去访问或修改,或者该变量相关的定义和函数都自己重写覆盖掉(我采用的是后者)

tips: 理解清楚privateinternal,virtual这几个修饰符后,才能更好的写出面向对象的。。精简的代码。。

测试:

Remix:

先在remix简单测试,由于继承了父类的一些方法,用console.log去Debug,易于知道自己的方法在什么时候能够调用到

1
import "hardhat/console.sol";

20220621101109

hardhat:

获取tx详情:

hardhat自带环境,不用像测试网那样要wait,可直接获得被确认后的tx详情

1
2
tx = await tokenOwner.Token.transfer(users[0].address, amountWei1);
// console.log(tx) 不用wait,该tx就是被确认的tx

trick:

如果是使用hardhat环境,修改代码后不需要再编译、部署,直接运行 yarn hardhat test,就可以使用新代码

测试结果:

符合预期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
  Token contract
Transactions
Before Transfer:
【Step 1】tokenOwnerBalance(Ether): 1000.0
【Step 1】userBalance(Ether): 0.0
Transfer:
【Step 2】tokenOwner -> 100-> user0:
【Step 2】blockNum When tx confirmed: 2
【Step 2】tokenOwnerBalance(Ether): 900.0
【Step 2】userBalance(Ether): 100.0
Check Snapshot:
【Step 3】tokenOwnerBalance(Ether) AtSnapshot: 1000.0 BlockNum :2
【Step 3】userBalance(Ether) AtSnapshot: 0.0 BlockNum :2
Transfer again:
【Step 4】tokenOwner -> 100-> user0:
【Step 4】blockNum When tx confirmed: 3
【Step 4】tokenOwnerBalance(Ether): 800.0
【Step 4】userBalance(Ether): 200.0
Check Snapshot:
【Step 5】tokenOwnerBalance(Ether) AtSnapshot: 900.0 BlockNum :3
【Step 5】userBalance(Ether) AtSnapshot: 100.0 BlockNum :3
✔ Check snapshotId和users balance (1468ms)
Burn
Before burn :
【Step 1】tokenOwnerBalance(Ether): 1000.0
【Step 1】totalSupplyWeiBefore(Ether): 1000.0
tokenOwner Burn 100Ether token:
【Step 2】blockNum When tx confirmed: 2
【Step 2】tokenOwnerBalanceWei(Ether): 900.0
【Step 2】totalSupplyWei(Ether): 900.0
Check Snapshot:
【Step 3】tokenOwnerBalance(Ether) AtSnapshot: 1000.0 BlockNum :2
【Step 3】totalSupply(Ether) AtSnapshot: 1000.0 BlockNum :2
tokenOwner Burn 100Ether token Again:
【Step 4】blockNum When tx confirmed: 3
【Step 4】tokenOwnerBalanceWei(Ether): 800.0
【Step 4】totalSupplyWei(Ether): 800.0
Check Snapshot Again:
【Step 5】tokenOwnerBalance(Ether) AtSnapshot: 900.0 BlockNum :3
【Step 5】totalSupply(Ether) AtSnapshot: 900.0 BlockNum :3
✔ Burn, check Snapshot totalsupply (241ms)

2 passing (2s)

✨ Done in 9.05s.

Summary:

  1. 阅读源码技巧:Slither输出继承关系图,还差一个能输出函数互相调用的图的工具。

  2. 加深对solidity继承这一块的理解,理解清楚privateinternal,virtual等修饰符,才能更好的写出面向对象的精简的代码。。目前这一块还是有点晕😓

  3. 多看如openzeppelin这类优秀的源码库,可以学到很多优秀的代码实现,且少写很多代码

Refs: