all files / p1/ StRSRVotes.sol

100% Statements 49/49
100% Branches 26/26
100% Functions 20/20
100% Lines 55/55
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229                                                                                                  51×   51×       11×             15×       846×       52× 52×       93× 92× 92×       67× 66× 66×       15× 14×                     330× 330× 330× 323× 323× 21×   302×     330×       47×                                     312× 311×       73× 73×               394× 392×       49× 49× 49×   49×   49×               441× 44×             44× 43×         43×                     482× 482× 482×   482×   481×                   405×       77×                    
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.17;
 
import "@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol";
import "../interfaces/IStRSRVotes.sol";
import "./StRSR.sol";
 
/*
 * @title StRSRP1Votes
 * @notice StRSRP1Votes is an extension of StRSRP1 that makes it IVotesUpgradeable.
 *   It is heavily based on OZ's ERC20VotesUpgradeable
 */
contract StRSRP1Votes is StRSRP1, IStRSRVotes {
    // A Checkpoint[] is a value history; it faithfully represents the history of value so long
    // as that value is only ever set by _writeCheckpoint. For any *previous* block number N, the
    // recorded value at the end of block N was cp.val, where cp in the value history is the
    // Checkpoint value with fromBlock maximal such that fromBlock <= N.
 
    // In particular, if the value changed during block N, there will be exactly one
    // entry cp with cp.fromBlock = N, and cp.val is the value at the _end_ of that block.
    struct Checkpoint {
        uint48 fromBlock;
        uint224 val;
    }
 
    bytes32 private constant _DELEGATE_TYPEHASH =
        keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)");
 
    // _delegates[account] is the address of the delegate that `accountt` has specified
    mapping(address => address) private _delegates;
 
    // era history
    Checkpoint[] private _eras; // {era}
 
    // {era} => ...
    // `_checkpoints[era][account]` is the history of voting power of `account` during era `era`
    mapping(uint256 => mapping(address => Checkpoint[])) private _checkpoints; // {qStRSR}
    // `_totalSupplyCheckpoints[era]` is the history of totalSupply values during era `era`
    mapping(uint256 => Checkpoint[]) private _totalSupplyCheckpoints; // {qStRSR}
 
    // When RSR is seized, stakeholders are divested not only of their economic position,
    // but also of their governance position.
 
    // ===
 
    /// Rebase hook
    /// No need to override beginDraftEra: we are only concerned with raw balances (stakes)
    function beginEra() internal override {
        super.beginEra();
 
        _writeCheckpoint(_eras, _add, 1);
    }
 
    function currentEra() external view returns (uint256) {
        return era;
    }
 
    function checkpoints(address account, uint48 pos) public view returns (Checkpoint memory) {
        return _checkpoints[era][account][pos];
    }
 
    function numCheckpoints(address account) public view returns (uint48) {
        return SafeCastUpgradeable.toUint48(_checkpoints[era][account].length);
    }
 
    function delegates(address account) public view returns (address) {
        return _delegates[account];
    }
 
    function getVotes(address account) public view returns (uint256) {
        uint256 pos = _checkpoints[era][account].length;
        return pos == 0 ? 0 : _checkpoints[era][account][pos - 1].val;
    }
 
    function getPastVotes(address account, uint256 blockNumber) public view returns (uint256) {
        require(blockNumber < block.number, "ERC20Votes: block not yet mined");
        uint256 pastEra = _checkpointsLookup(_eras, blockNumber);
        return _checkpointsLookup(_checkpoints[pastEra][account], blockNumber);
    }
 
    function getPastTotalSupply(uint256 blockNumber) public view returns (uint256) {
        require(blockNumber < block.number, "ERC20Votes: block not yet mined");
        uint256 pastEra = _checkpointsLookup(_eras, blockNumber);
        return _checkpointsLookup(_totalSupplyCheckpoints[pastEra], blockNumber);
    }
 
    function getPastEra(uint256 blockNumber) public view returns (uint256) {
        require(blockNumber < block.number, "ERC20Votes: block not yet mined");
        return _checkpointsLookup(_eras, blockNumber);
    }
 
    /// Return the value from history `ckpts` that was current for block number `blockNumber`
    function _checkpointsLookup(Checkpoint[] storage ckpts, uint256 blockNumber)
        private
        view
        returns (uint256)
    {
        // We run a binary search to set `high` to the index of the earliest checkpoint
        // taken after blockNumber, or ckpts.length if no checkpoint was taken after blockNumber
        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].val;
    }
 
    function delegate(address delegatee) public {
        _delegate(_msgSender(), delegatee);
    }
 
    function delegateBySig(
        address delegatee,
        uint256 nonce,
        uint256 expiry,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public {
        require(block.timestamp <= expiry, "ERC20Votes: signature expired");
        address signer = ECDSAUpgradeable.recover(
            _hashTypedDataV4(keccak256(abi.encode(_DELEGATE_TYPEHASH, delegatee, nonce, expiry))),
            v,
            r,
            s
        );
        require(nonce == _useDelegationNonce(signer), "ERC20Votes: invalid nonce");
        _delegate(signer, delegatee);
    }
 
    function _mint(address account, uint256 amount) internal override {
        super._mint(account, amount);
        _writeCheckpoint(_totalSupplyCheckpoints[era], _add, amount);
    }
 
    function _burn(address account, uint256 amount) internal override {
        super._burn(account, amount);
        _writeCheckpoint(_totalSupplyCheckpoints[era], _subtract, amount);
    }
 
    function _afterTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal override {
        super._afterTokenTransfer(from, to, amount);
        _moveVotingPower(delegates(from), delegates(to), amount);
    }
 
    function _delegate(address delegator, address delegatee) internal {
        address currentDelegate = delegates(delegator);
        uint256 delegatorBalance = balanceOf(delegator);
        _delegates[delegator] = delegatee;
 
        emit DelegateChanged(delegator, currentDelegate, delegatee);
 
        _moveVotingPower(currentDelegate, delegatee, delegatorBalance);
    }
 
    function _moveVotingPower(
        address src,
        address dst,
        uint256 amount
    ) private {
        if (src != dst && amount > 0) {
            if (src != address(0)) {
                (uint256 oldWeight, uint256 newWeight) = _writeCheckpoint(
                    _checkpoints[era][src],
                    _subtract,
                    amount
                );
                emit DelegateVotesChanged(src, oldWeight, newWeight);
            }
 
            if (dst != address(0)) {
                (uint256 oldWeight, uint256 newWeight) = _writeCheckpoint(
                    _checkpoints[era][dst],
                    _add,
                    amount
                );
                emit DelegateVotesChanged(dst, oldWeight, newWeight);
            }
        }
    }
 
    // Set this block's value in the history `ckpts`
    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].val;
        newWeight = op(oldWeight, delta);
 
        if (pos > 0 && ckpts[pos - 1].fromBlock == block.number) {
            ckpts[pos - 1].val = SafeCastUpgradeable.toUint224(newWeight);
        } else {
            ckpts.push(
                Checkpoint({
                    fromBlock: SafeCastUpgradeable.toUint48(block.number),
                    val: 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[46] private __gap;
}