VotesExtended.sol 3.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. // SPDX-License-Identifier: MIT
  2. pragma solidity ^0.8.20;
  3. import {Checkpoints} from "../../utils/structs/Checkpoints.sol";
  4. import {Votes} from "./Votes.sol";
  5. import {SafeCast} from "../../utils/math/SafeCast.sol";
  6. /**
  7. * @dev Extension of {Votes} that adds checkpoints for delegations and balances.
  8. *
  9. * WARNING: While this contract extends {Votes}, valid uses of {Votes} may not be compatible with
  10. * {VotesExtended} without additional considerations. This implementation of {_transferVotingUnits} must
  11. * run AFTER the voting weight movement is registered, such that it is reflected on {_getVotingUnits}.
  12. *
  13. * Said differently, {VotesExtended} MUST be integrated in a way that calls {_transferVotingUnits} AFTER the
  14. * asset transfer is registered and balances are updated:
  15. *
  16. * ```solidity
  17. * contract VotingToken is Token, VotesExtended {
  18. * function transfer(address from, address to, uint256 tokenId) public override {
  19. * super.transfer(from, to, tokenId); // <- Perform the transfer first ...
  20. * _transferVotingUnits(from, to, 1); // <- ... then call _transferVotingUnits.
  21. * }
  22. *
  23. * function _getVotingUnits(address account) internal view override returns (uint256) {
  24. * return balanceOf(account);
  25. * }
  26. * }
  27. * ```
  28. *
  29. * {ERC20Votes} and {ERC721Votes} follow this pattern and are thus safe to use with {VotesExtended}.
  30. */
  31. abstract contract VotesExtended is Votes {
  32. using SafeCast for uint256;
  33. using Checkpoints for Checkpoints.Trace160;
  34. using Checkpoints for Checkpoints.Trace208;
  35. mapping(address delegator => Checkpoints.Trace160) private _delegateCheckpoints;
  36. mapping(address account => Checkpoints.Trace208) private _balanceOfCheckpoints;
  37. /**
  38. * @dev Returns the delegate of an `account` at a specific moment in the past. If the `clock()` is
  39. * configured to use block numbers, this will return the value at the end of the corresponding block.
  40. *
  41. * Requirements:
  42. *
  43. * - `timepoint` must be in the past. If operating using block numbers, the block must be already mined.
  44. */
  45. function getPastDelegate(address account, uint256 timepoint) public view virtual returns (address) {
  46. uint48 currentTimepoint = clock();
  47. if (timepoint >= currentTimepoint) {
  48. revert ERC5805FutureLookup(timepoint, currentTimepoint);
  49. }
  50. return address(_delegateCheckpoints[account].upperLookupRecent(timepoint.toUint48()));
  51. }
  52. /**
  53. * @dev Returns the `balanceOf` of an `account` at a specific moment in the past. If the `clock()` is
  54. * configured to use block numbers, this will return the value at the end of the corresponding block.
  55. *
  56. * Requirements:
  57. *
  58. * - `timepoint` must be in the past. If operating using block numbers, the block must be already mined.
  59. */
  60. function getPastBalanceOf(address account, uint256 timepoint) public view virtual returns (uint256) {
  61. uint48 currentTimepoint = clock();
  62. if (timepoint >= currentTimepoint) {
  63. revert ERC5805FutureLookup(timepoint, currentTimepoint);
  64. }
  65. return _balanceOfCheckpoints[account].upperLookupRecent(timepoint.toUint48());
  66. }
  67. /// @inheritdoc Votes
  68. function _delegate(address account, address delegatee) internal virtual override {
  69. super._delegate(account, delegatee);
  70. _delegateCheckpoints[account].push(clock(), uint160(delegatee));
  71. }
  72. /// @inheritdoc Votes
  73. function _transferVotingUnits(address from, address to, uint256 amount) internal virtual override {
  74. super._transferVotingUnits(from, to, amount);
  75. if (from != to) {
  76. if (from != address(0)) {
  77. _balanceOfCheckpoints[from].push(clock(), _getVotingUnits(from).toUint208());
  78. }
  79. if (to != address(0)) {
  80. _balanceOfCheckpoints[to].push(clock(), _getVotingUnits(to).toUint208());
  81. }
  82. }
  83. }
  84. }