ERC20Snapshot.sol 5.8 KB

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