ERC20Snapshot.sol 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. pragma solidity ^0.5.2;
  2. import "../math/SafeMath.sol";
  3. import "../utils/Arrays.sol";
  4. import "../drafts/Counters.sol";
  5. import "../token/ERC20/ERC20.sol";
  6. /**
  7. * @title ERC20 token with snapshots.
  8. * inspired by Jordi Baylina's MiniMeToken to record historical balances
  9. * Snapshots store a value at the time a snapshot is taken (and a new snapshot id created), and the corresponding
  10. * snapshot id. Each account has individual snapshots taken on demand, as does the token's total supply.
  11. * @author Validity Labs AG <info@validitylabs.org>
  12. */
  13. contract ERC20Snapshot is ERC20 {
  14. using SafeMath for uint256;
  15. using Arrays for uint256[];
  16. using Counters for Counters.Counter;
  17. // Snapshoted values have arrays of ids and the value corresponding to that id. These could be an array of a
  18. // Snapshot struct, but that would impede usage of functions that work on an array.
  19. struct Snapshots {
  20. uint256[] ids;
  21. uint256[] values;
  22. }
  23. mapping (address => Snapshots) private _accountBalanceSnapshots;
  24. Snapshots private _totalSupplySnaphots;
  25. // Snapshot ids increase monotonically, with the first value being 1. An id of 0 is invalid.
  26. Counters.Counter private _currentSnapshotId;
  27. event Snapshot(uint256 id);
  28. // Creates a new snapshot id. Balances are only stored in snapshots on demand: unless a snapshot was taken, a
  29. // balance change will not be recorded. This means the extra added cost of storing snapshotted balances is only paid
  30. // when required, but is also flexible enough that it allows for e.g. daily snapshots.
  31. function snapshot() public returns (uint256) {
  32. _currentSnapshotId.increment();
  33. uint256 currentId = _currentSnapshotId.current();
  34. emit Snapshot(currentId);
  35. return currentId;
  36. }
  37. function balanceOfAt(address account, uint256 snapshotId) public view returns (uint256) {
  38. (bool snapshotted, uint256 value) = _valueAt(snapshotId, _accountBalanceSnapshots[account]);
  39. return snapshotted ? value : balanceOf(account);
  40. }
  41. function totalSupplyAt(uint256 snapshotId) public view returns(uint256) {
  42. (bool snapshotted, uint256 value) = _valueAt(snapshotId, _totalSupplySnaphots);
  43. return snapshotted ? value : totalSupply();
  44. }
  45. // _transfer, _mint and _burn are the only functions where the balances are modified, so it is there that the
  46. // snapshots are updated. Note that the update happens _before_ the balance change, with the pre-modified value.
  47. // The same is true for the total supply and _mint and _burn.
  48. function _transfer(address from, address to, uint256 value) internal {
  49. _updateAccountSnapshot(from);
  50. _updateAccountSnapshot(to);
  51. super._transfer(from, to, value);
  52. }
  53. function _mint(address account, uint256 value) internal {
  54. _updateAccountSnapshot(account);
  55. _updateTotalSupplySnapshot();
  56. super._mint(account, value);
  57. }
  58. function _burn(address account, uint256 value) internal {
  59. _updateAccountSnapshot(account);
  60. _updateTotalSupplySnapshot();
  61. super._burn(account, value);
  62. }
  63. // When a valid snapshot is queried, there are three possibilities:
  64. // a) The queried value was not modified after the snapshot was taken. Therefore, a snapshot entry was never
  65. // created for this id, and all stored snapshot ids are smaller than the requested one. The value that corresponds
  66. // to this id is the current one.
  67. // b) The queried value was modified after the snapshot was taken. Therefore, there will be an entry with the
  68. // requested id, and its value is the one to return.
  69. // c) More snapshots were created after the requested one, and the queried value was later modified. There will be
  70. // no entry for the requested id: the value that corresponds to it is that of the smallest snapshot id that is
  71. // larger than the requested one.
  72. //
  73. // In summary, we need to find an element in an array, returning the index of the smallest value that is larger if
  74. // it is not found, unless said value doesn't exist (e.g. when all values are smaller). Arrays.findUpperBound does
  75. // exactly this.
  76. function _valueAt(uint256 snapshotId, Snapshots storage snapshots)
  77. private view returns (bool, uint256)
  78. {
  79. require(snapshotId > 0);
  80. require(snapshotId <= _currentSnapshotId.current());
  81. uint256 index = snapshots.ids.findUpperBound(snapshotId);
  82. if (index == snapshots.ids.length) {
  83. return (false, 0);
  84. } else {
  85. return (true, snapshots.values[index]);
  86. }
  87. }
  88. function _updateAccountSnapshot(address account) private {
  89. _updateSnapshot(_accountBalanceSnapshots[account], balanceOf(account));
  90. }
  91. function _updateTotalSupplySnapshot() private {
  92. _updateSnapshot(_totalSupplySnaphots, totalSupply());
  93. }
  94. function _updateSnapshot(Snapshots storage snapshots, uint256 currentValue) private {
  95. uint256 currentId = _currentSnapshotId.current();
  96. if (_lastSnapshotId(snapshots.ids) < currentId) {
  97. snapshots.ids.push(currentId);
  98. snapshots.values.push(currentValue);
  99. }
  100. }
  101. function _lastSnapshotId(uint256[] storage ids) private view returns (uint256) {
  102. if (ids.length == 0) {
  103. return 0;
  104. } else {
  105. return ids[ids.length - 1];
  106. }
  107. }
  108. }