ERC20Snapshot.sol 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. // SPDX-License-Identifier: MIT
  2. pragma solidity ^0.6.0;
  3. import "../../math/SafeMath.sol";
  4. import "../../utils/Arrays.sol";
  5. import "../../utils/Counters.sol";
  6. import "./ERC20.sol";
  7. /**
  8. * @dev This contract extends an ERC20 token with a snapshot mechanism. When a snapshot is created, the balances and
  9. * total supply at the time are recorded for later access.
  10. *
  11. * This can be used to safely create mechanisms based on token balances such as trustless dividends or weighted voting.
  12. * In naive implementations it's possible to perform a "double spend" attack by reusing the same balance from different
  13. * accounts. By using snapshots to calculate dividends or voting power, those attacks no longer apply. It can also be
  14. * used to create an efficient ERC20 forking mechanism.
  15. *
  16. * Snapshots are created by the internal {_snapshot} function, which will emit the {Snapshot} event and return a
  17. * snapshot id. To get the total supply at the time of a snapshot, call the function {totalSupplyAt} with the snapshot
  18. * id. To get the balance of an account at the time of a snapshot, call the {balanceOfAt} function with the snapshot id
  19. * and the account address.
  20. *
  21. * ==== Gas Costs
  22. *
  23. * Snapshots are efficient. Snapshot creation is _O(1)_. Retrieval of balances or total supply from a snapshot is _O(log
  24. * n)_ in the number of snapshots that have been created, although _n_ for a specific account will generally be much
  25. * smaller since identical balances in subsequent snapshots are stored as a single entry.
  26. *
  27. * There is a constant overhead for normal ERC20 transfers due to the additional snapshot bookkeeping. This overhead is
  28. * only significant for the first transfer that immediately follows a snapshot for a particular account. Subsequent
  29. * transfers will have normal cost until the next snapshot, and so on.
  30. */
  31. abstract contract ERC20Snapshot is ERC20 {
  32. // Inspired by Jordi Baylina's MiniMeToken to record historical balances:
  33. // https://github.com/Giveth/minimd/blob/ea04d950eea153a04c51fa510b068b9dded390cb/contracts/MiniMeToken.sol
  34. using SafeMath for uint256;
  35. using Arrays for uint256[];
  36. using Counters for Counters.Counter;
  37. // Snapshotted values have arrays of ids and the value corresponding to that id. These could be an array of a
  38. // Snapshot struct, but that would impede usage of functions that work on an array.
  39. struct Snapshots {
  40. uint256[] ids;
  41. uint256[] values;
  42. }
  43. mapping (address => Snapshots) private _accountBalanceSnapshots;
  44. Snapshots private _totalSupplySnapshots;
  45. // Snapshot ids increase monotonically, with the first value being 1. An id of 0 is invalid.
  46. Counters.Counter private _currentSnapshotId;
  47. /**
  48. * @dev Emitted by {_snapshot} when a snapshot identified by `id` is created.
  49. */
  50. event Snapshot(uint256 id);
  51. /**
  52. * @dev Creates a new snapshot and returns its snapshot id.
  53. *
  54. * Emits a {Snapshot} event that contains the same id.
  55. *
  56. * {_snapshot} is `internal` and you have to decide how to expose it externally. Its usage may be restricted to a
  57. * set of accounts, for example using {AccessControl}, or it may be open to the public.
  58. *
  59. * [WARNING]
  60. * ====
  61. * While an open way of calling {_snapshot} is required for certain trust minimization mechanisms such as forking,
  62. * you must consider that it can potentially be used by attackers in two ways.
  63. *
  64. * First, it can be used to increase the cost of retrieval of values from snapshots, although it will grow
  65. * logarithmically thus rendering this attack ineffective in the long term. Second, it can be used to target
  66. * specific accounts and increase the cost of ERC20 transfers for them, in the ways specified in the Gas Costs
  67. * section above.
  68. *
  69. * We haven't measured the actual numbers; if this is something you're interested in please reach out to us.
  70. * ====
  71. */
  72. function _snapshot() internal virtual returns (uint256) {
  73. _currentSnapshotId.increment();
  74. uint256 currentId = _currentSnapshotId.current();
  75. emit Snapshot(currentId);
  76. return currentId;
  77. }
  78. /**
  79. * @dev Retrieves the balance of `account` at the time `snapshotId` was created.
  80. */
  81. function balanceOfAt(address account, uint256 snapshotId) public view returns (uint256) {
  82. (bool snapshotted, uint256 value) = _valueAt(snapshotId, _accountBalanceSnapshots[account]);
  83. return snapshotted ? value : balanceOf(account);
  84. }
  85. /**
  86. * @dev Retrieves the total supply at the time `snapshotId` was created.
  87. */
  88. function totalSupplyAt(uint256 snapshotId) public view returns(uint256) {
  89. (bool snapshotted, uint256 value) = _valueAt(snapshotId, _totalSupplySnapshots);
  90. return snapshotted ? value : totalSupply();
  91. }
  92. // _transfer, _mint and _burn are the only functions where the balances are modified, so it is there that the
  93. // snapshots are updated. Note that the update happens _before_ the balance change, with the pre-modified value.
  94. // The same is true for the total supply and _mint and _burn.
  95. function _transfer(address from, address to, uint256 value) internal virtual override {
  96. _updateAccountSnapshot(from);
  97. _updateAccountSnapshot(to);
  98. super._transfer(from, to, value);
  99. }
  100. function _mint(address account, uint256 value) internal virtual override {
  101. _updateAccountSnapshot(account);
  102. _updateTotalSupplySnapshot();
  103. super._mint(account, value);
  104. }
  105. function _burn(address account, uint256 value) internal virtual override {
  106. _updateAccountSnapshot(account);
  107. _updateTotalSupplySnapshot();
  108. super._burn(account, value);
  109. }
  110. function _valueAt(uint256 snapshotId, Snapshots storage snapshots)
  111. private view returns (bool, uint256)
  112. {
  113. require(snapshotId > 0, "ERC20Snapshot: id is 0");
  114. // solhint-disable-next-line max-line-length
  115. require(snapshotId <= _currentSnapshotId.current(), "ERC20Snapshot: nonexistent id");
  116. // When a valid snapshot is queried, there are three possibilities:
  117. // a) The queried value was not modified after the snapshot was taken. Therefore, a snapshot entry was never
  118. // created for this id, and all stored snapshot ids are smaller than the requested one. The value that corresponds
  119. // to this id is the current one.
  120. // b) The queried value was modified after the snapshot was taken. Therefore, there will be an entry with the
  121. // requested id, and its value is the one to return.
  122. // c) More snapshots were created after the requested one, and the queried value was later modified. There will be
  123. // no entry for the requested id: the value that corresponds to it is that of the smallest snapshot id that is
  124. // larger than the requested one.
  125. //
  126. // In summary, we need to find an element in an array, returning the index of the smallest value that is larger if
  127. // it is not found, unless said value doesn't exist (e.g. when all values are smaller). Arrays.findUpperBound does
  128. // exactly this.
  129. uint256 index = snapshots.ids.findUpperBound(snapshotId);
  130. if (index == snapshots.ids.length) {
  131. return (false, 0);
  132. } else {
  133. return (true, snapshots.values[index]);
  134. }
  135. }
  136. function _updateAccountSnapshot(address account) private {
  137. _updateSnapshot(_accountBalanceSnapshots[account], balanceOf(account));
  138. }
  139. function _updateTotalSupplySnapshot() private {
  140. _updateSnapshot(_totalSupplySnapshots, totalSupply());
  141. }
  142. function _updateSnapshot(Snapshots storage snapshots, uint256 currentValue) private {
  143. uint256 currentId = _currentSnapshotId.current();
  144. if (_lastSnapshotId(snapshots.ids) < currentId) {
  145. snapshots.ids.push(currentId);
  146. snapshots.values.push(currentValue);
  147. }
  148. }
  149. function _lastSnapshotId(uint256[] storage ids) private view returns (uint256) {
  150. if (ids.length == 0) {
  151. return 0;
  152. } else {
  153. return ids[ids.length - 1];
  154. }
  155. }
  156. }