// SPDX-License-Identifier: UNLICENSED pragma solidity =0.8.18; import "./IERC20HistoryUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; /** * @notice Implementation of the {IERC20HistoryUpgradeable} interface. */ abstract contract ERC20HistoryUpgradeable is Initializable, IERC20HistoryUpgradeable, ERC20Upgradeable { /** * @notice Initializer for extended contracts. */ function __ERC20History_init() internal onlyInitializing { __Context_init_unchained(); __ERC20History_init_unchained(); } /** * @notice Unchained initializer for extended contracts. */ function __ERC20History_init_unchained() internal onlyInitializing {} /** * @notice Checkpoint structure. * @param fromBlock The block marking the start of the checkpoint. * @param balance The balance of an account in a given checkpoint. */ struct Checkpoint { uint32 fromBlock; uint224 balance; } mapping(address => Checkpoint[]) private _checkpoints; Checkpoint[] private _totalSupplyCheckpoints; /** * @notice Get the `pos`-th checkpoint for `account`. * @param account The account to return the checkpoints of. * @param pos The index of the checkpoint to return. * @return The checkpoint structure representing the `pos`-th checkpoint for `account`. */ function checkpoints(address account, uint32 pos) public view virtual returns (Checkpoint memory) { return _checkpoints[account][pos]; } /** * @notice Returns the number of checkpoints for `account`. * @param account The account to return the number of checkpoints for. * @return The number of checkpoints for `account`. */ function numCheckpoints(address account) public view virtual returns (uint32) { return SafeCastUpgradeable.toUint32(_checkpoints[account].length); } /** * @inheritdoc IERC20HistoryUpgradeable */ function getPastBalance(address account, uint256 blockNumber) public view virtual override returns (uint256) { require(blockNumber < block.number, "ERC20History: block not yet mined"); return _checkpointsLookup(_checkpoints[account], blockNumber); } /** * @inheritdoc IERC20HistoryUpgradeable */ function getPastTotalSupply(uint256 blockNumber) public view virtual override returns (uint256) { require(blockNumber < block.number, "ERC20History: block not yet mined"); return _checkpointsLookup(_totalSupplyCheckpoints, blockNumber); } /** * @dev Lookup a value in a list of (sorted) checkpoints. */ function _checkpointsLookup(Checkpoint[] storage ckpts, uint256 blockNumber) private view returns (uint256) { // We run a binary search to look for the earliest checkpoint taken after `blockNumber`. // // During the loop, the index of the wanted checkpoint remains in the range [low-1, high). // With each iteration, either `low` or `high` is moved towards the middle of the range to maintain the invariant. // - If the middle checkpoint is after `blockNumber`, we look in [low, mid) // - If the middle checkpoint is before or equal to `blockNumber`, we look in [mid+1, high) // Once we reach a single value (when low == high), we've found the right checkpoint at the index high-1, if not // out of bounds (in which case we're looking too far in the past and the result is 0). // Note that if the latest checkpoint available is exactly for `blockNumber`, we end up with an index that is // past the end of the array, so we technically don't find a checkpoint after `blockNumber`, but it works out // the same. uint256 high = ckpts.length; uint256 low = 0; while (low < high) { uint256 mid = MathUpgradeable.average(low, high); if (ckpts[mid].fromBlock > blockNumber) { high = mid; } else { low = mid + 1; } } return high == 0 ? 0 : ckpts[high - 1].balance; } /** * @dev Maximum token supply. Defaults to `type(uint224).max` (2^224^ - 1). */ function _maxSupply() internal view virtual returns (uint224) { return type(uint224).max; } /** * @dev Snapshots the totalSupply after it has been increased. */ function _mint(address account, uint256 amount) internal virtual override { super._mint(account, amount); require(totalSupply() <= _maxSupply(), "ERC20History: total supply risks overflowing votes"); _writeCheckpoint(_totalSupplyCheckpoints, _add, amount); } /** * @dev Snapshots the totalSupply after it has been decreased. */ function _burn(address account, uint256 amount) internal virtual override { super._burn(account, amount); _writeCheckpoint(_totalSupplyCheckpoints, _subtract, amount); } /** * @dev Move balance when tokens are transferred. * * Emits a {DelegateVotesChanged} event. */ function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual override { super._afterTokenTransfer(from, to, amount); _moveBalance(from, to, amount); } function _moveBalance(address src, address dst, uint256 amount) private { if (src != dst && amount > 0) { if (src != address(0)) { (uint256 oldWeight, uint256 newWeight) = _writeCheckpoint(_checkpoints[src], _subtract, amount); emit BalanceChanged(src, oldWeight, newWeight); } if (dst != address(0)) { (uint256 oldWeight, uint256 newWeight) = _writeCheckpoint(_checkpoints[dst], _add, amount); emit BalanceChanged(dst, oldWeight, newWeight); } } } function _writeCheckpoint( Checkpoint[] storage ckpts, function(uint256, uint256) view returns (uint256) op, uint256 delta ) private returns (uint256 oldWeight, uint256 newWeight) { uint256 pos = ckpts.length; oldWeight = pos == 0 ? 0 : ckpts[pos - 1].balance; newWeight = op(oldWeight, delta); if (pos > 0 && ckpts[pos - 1].fromBlock == block.number) { ckpts[pos - 1].balance = SafeCastUpgradeable.toUint224(newWeight); } else { ckpts.push( Checkpoint({ fromBlock: SafeCastUpgradeable.toUint32(block.number), balance: SafeCastUpgradeable.toUint224(newWeight) }) ); } } function _add(uint256 a, uint256 b) private pure returns (uint256) { return a + b; } function _subtract(uint256 a, uint256 b) private pure returns (uint256) { return a - b; } /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[47] private __gap; }