WrappedGovLst

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "lib/stGOV/src/WrappedGovLst.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Staker} from "staker/Staker.sol";

import {FixedGovLst} from "./FixedGovLst.sol";
import {GovLst} from "./GovLst.sol";

/// @title WrappedGovLst
/// @author [ScopeLift](https://scopelift.co)
/// @notice A wrapper contract that provides a non-rebasing interface to liquid stake tokens. The wrapper accepts
/// stake tokens, `GovLST`, or `FixedGovLST` tokens. Wrapped tokens maintain 1:1 backing with `FixedGovLST`
/// shares, allowing holders to benefit from staking rewards without balance changes and avoid off-by-one rounding
/// issues when transferring tokens. The voting weight for all tokens held by a given wrapper deployment is assigned to
/// a single delegatee, which is controlled by the wrapper's owner.
contract WrappedGovLst is ERC20Permit, Ownable {
  using SafeERC20 for IERC20;

  /// @notice Emitted when a holder wraps `GovLst` tokens.
  event RebasingWrapped(address indexed holder, uint256 rebasingAmount, uint256 wrappedAmount);

  /// @notice Emitted when a holder wraps `FixedGovLst` tokens.
  event FixedWrapped(address indexed holder, uint256 fixedAmount, uint256 wrappedAmount);

  /// @notice Emitted when a holder wraps the underlying stake token.
  event UnderlyingWrapped(address indexed holder, uint256 underlyingAmount, uint256 wrappedAmount);

  /// @notice Emitted when a holder unwraps tokens into `GovLst` tokens.
  event RebasingUnwrapped(address indexed holder, uint256 lstAmount, uint256 wrappedAmount);

  /// @notice Emitted when a holder unwraps tokens into `FixedGovLst` tokens.
  event FixedUnwrapped(address indexed holder, uint256 lstAmount, uint256 wrappedAmount);

  /// @notice Emitted when the wrapper's owner updates the delegatee to which wrapped tokens voting weight is assigned.
  event DelegateeSet(address indexed oldDelegatee, address indexed newDelegatee);

  /// @notice Emitted when a holder tries to wrap or unwrap 0 liquid stake tokens.
  error WrappedGovLst__InvalidAmount();

  /// @notice The address of the `GovLst` contract which can be wrapped.
  GovLst public immutable LST;

  /// @notice The address of the `FixedGovLst` contract which backs the wrapped tokens 1:1 and can be wrapped.
  FixedGovLst public immutable FIXED_LST;

  /// @notice The address of the `IERC20` token used in the underlying staker.
  IERC20 public immutable STAKE_TOKEN;

  /// @notice Local copy of the LST's scale factor that is stored at deployment for use in wrapper calculations.
  uint256 internal immutable SHARE_SCALE_FACTOR;

  /// @notice The Staker deposit identifier which holds the wrapper's underlying tokens.
  Staker.DepositIdentifier public depositId;

  /// @param _name The name of the wrapper token.
  /// @param _symbol The symbol of the wrapper token.
  /// @param _lst The contract of the liquid stake token being wrapped.
  /// @param _delegatee The initial delegatee to whom the wrapper's voting weight will be delegated.
  /// @param _initialOwner The initial owner of the wrapper contract.
  /// @param _preFundWrapped The amount of `FixedGovLst` tokens to prefund the wrapper. If 0 some fixed tokens should be
  /// sent after deployment to the wrapper.
  constructor(
    string memory _name,
    string memory _symbol,
    GovLst _lst,
    address _delegatee,
    address _initialOwner,
    uint256 _preFundWrapped
  ) ERC20Permit(_name) ERC20(_name, _symbol) Ownable(_initialOwner) {
    LST = _lst;
    FIXED_LST = _lst.FIXED_LST();
    SHARE_SCALE_FACTOR = _lst.SHARE_SCALE_FACTOR();
    FIXED_LST.transferFrom(msg.sender, address(this), _preFundWrapped);
    STAKE_TOKEN = IERC20(address(LST.STAKE_TOKEN()));
    STAKE_TOKEN.approve(address(FIXED_LST), type(uint256).max);
    _setDelegatee(_delegatee);
  }

  /// @notice The address of the delegatee to which the wrapped token's voting weight is currently delegated.
  function delegatee() public view virtual returns (address) {
    return FIXED_LST.delegateeForHolder(address(this));
  }

  /// @notice Preview the amount of wrapped tokens that would be minted when wrapping `GovLST` tokens.
  /// @param _rebasingTokensToWrap The amount of `GovLST` tokens to wrap.
  /// @return The minimum amount of wrapped tokens that would be minted.
  /// @dev Returns the minimum amount that would be minted by `wrapRebasing`.
  function previewWrapRebasing(uint256 _rebasingTokensToWrap) public view virtual returns (uint256) {
    // Calculate the shares that will be transferred when converting to fixed
    return _calcSharesForStakeUp(_rebasingTokensToWrap) / SHARE_SCALE_FACTOR;
  }

  /// @notice Preview the amount of wrapped tokens that would be minted when wrapping underlying stake tokens.
  /// @param _stakeTokensToWrap The amount of underlying stake tokens to wrap.
  /// @return The minimum amount of wrapped tokens that would be minted.
  /// @dev Simulates the staking process to determine the resulting wrapped token amount.
  function previewWrapUnderlying(uint256 _stakeTokensToWrap) public view virtual returns (uint256) {
    return _calcSharesForStake(_stakeTokensToWrap) / SHARE_SCALE_FACTOR;
  }

  /// @notice Preview the amount of wrapped tokens that would be minted when wrapping fixed liquid staking tokens.
  /// @param _fixedTokensToWrap The amount of fixed liquid staking tokens to wrap.
  /// @return The minimum amount of wrapped tokens that would be minted.
  /// @dev Wrapped tokens maintain 1:1 backing with fixed liquid staking tokens.
  function previewWrapFixed(uint256 _fixedTokensToWrap) public view virtual returns (uint256) {
    return _fixedTokensToWrap;
  }

  /// @notice Preview the amount of rebasing liquid stake tokens that would be received when unwrapping.
  /// @param _wrappedAmount The amount of wrapped tokens to unwrap.
  /// @return The minimum amount of rebasing liquid stake tokens that would be received.
  /// @dev Converts wrapped tokens to shares, then to rebasing tokens. Rounds down to favor the protocol.
  function previewUnwrapToRebasing(uint256 _wrappedAmount) public view virtual returns (uint256) {
    uint256 _shares = _wrappedAmount * SHARE_SCALE_FACTOR;
    return _calcStakeForShares(_shares);
  }

  /// @notice Preview the amount of fixed liquid staking tokens that would be received when unwrapping.
  /// @param _wrappedAmount The amount of wrapped tokens to unwrap.
  /// @return The minimum amount of fixed liquid staking tokens that would be received.
  /// @dev Returns a conservative estimate (1 wei less) to account for potential rounding in `FixedGovLst` transfer.
  /// The actual amount received may be up to 1 wei more due to `FixedGovLst`'s internal rounding behavior.
  function previewUnwrapToFixed(uint256 _wrappedAmount) public view virtual returns (uint256) {
    // At worst 1 wei less than what has been requested will be returned.
    // The preview will return the minimum amount of assets returned.
    return _wrappedAmount - 1;
  }

  /// @notice Deposit liquid stake tokens and receive wrapped tokens in exchange.
  /// @param _lstAmountToWrap The quantity of liquid stake tokens to wrap.
  /// @return _wrappedAmount The quantity of wrapped tokens issued to the caller.
  /// @dev The caller must approve at least the amount of tokens to wrap on the lst contract before calling. Amount to
  /// wrap may not be zero.
  /// @dev When wrapping, `GovLst` tokens are first transferred to `WrappedGovLst`. The transfer can result in the
  /// `WrappedGovLst` balance increasing by at most 1 wei more than the transferred amount. A second transfer of
  /// `GovLst` tokens to the fixed alias address of the `WrappedGovLst` will happen when converting to fixed tokens. This
  /// transfer will use the initial wrapped amount rather than the balance increases from the first transfer, ensuring
  /// that at most 1 extra wei is sent rather than 2.
  function wrapRebasing(uint256 _lstAmountToWrap) external virtual returns (uint256 _wrappedAmount) {
    if (_lstAmountToWrap == 0) {
      revert WrappedGovLst__InvalidAmount();
    }

    LST.transferFrom(msg.sender, address(this), _lstAmountToWrap);
    _wrappedAmount = FIXED_LST.convertToFixed(_lstAmountToWrap);
    _mint(msg.sender, _wrappedAmount);

    emit RebasingWrapped(msg.sender, _lstAmountToWrap, _wrappedAmount);
  }

  /// @notice Deposit underlying stake tokens and receive wrapped tokens in exchange.
  /// @param _stakeTokensToWrap The quantity of underlying stake tokens to wrap.
  /// @return _wrappedAmount The quantity of wrapped tokens issued to the caller.
  /// @dev The caller must approve at least the amount of tokens to wrap on the stake token contract before calling.
  /// Amount to wrap may not be zero.
  function wrapUnderlying(uint256 _stakeTokensToWrap) public virtual returns (uint256) {
    if (_stakeTokensToWrap == 0) {
      revert WrappedGovLst__InvalidAmount();
    }

    STAKE_TOKEN.safeTransferFrom(msg.sender, address(this), _stakeTokensToWrap);

    uint256 _wrappedAmount = FIXED_LST.stake(_stakeTokensToWrap);
    _mint(msg.sender, _wrappedAmount);

    emit UnderlyingWrapped(msg.sender, _stakeTokensToWrap, _wrappedAmount);

    return _wrappedAmount;
  }

  /// @notice Deposit `FixedGovLST` tokens and receive wrapped tokens in exchange.
  /// @param _fixedTokensToWrap The quantity of `FixedGoLst` tokens to wrap.
  /// @return _wrappedAmount The quantity of wrapped tokens issued to the caller.
  /// @dev The caller must approve at least the amount of tokens to wrap on the `FixedGovLst` contract before calling.
  /// Amount to wrap may not be zero.
  /// @dev When transferring using the `FixedGovLst` at most the `WrappedGoLst` may receive 1 wei less
  /// than the tokens sent. This is due to the conversion of shares into tokens in the underlying
  /// `GovLst`. Shares will be rounded down to tokens and the smaller amount of tokens will be
  /// converted back into shares leading to an up to 1 wei difference between the sent amount and
  /// the received amount.
  function wrapFixed(uint256 _fixedTokensToWrap) external virtual returns (uint256) {
    if (_fixedTokensToWrap == 0) {
      revert WrappedGovLst__InvalidAmount();
    }

    FIXED_LST.transferFrom(msg.sender, address(this), _fixedTokensToWrap);

    _mint(msg.sender, _fixedTokensToWrap);

    emit FixedWrapped(msg.sender, _fixedTokensToWrap, _fixedTokensToWrap);

    return _fixedTokensToWrap;
  }

  /// @notice Burn wrapped tokens to receive liquid stake tokens in return.
  /// @param _wrappedAmount The quantity of wrapped tokens to burn.
  /// @return _lstAmountUnwrapped The quantity of liquid staked tokens received in exchange for the wrapped tokens.
  /// @dev The caller must approve at least the amount wrapped tokens on the wrapper token contract.
  /// @dev When unwrapping wrapped tokens to rebasing tokens, the shares are converted to tokens, resulting in an at
  /// most loss of 1 wei. Unwrapping requires two transfers by the `GovLst`, one transfer to the non-aliased
  /// `WrappedGovLst` and another transfer to the caller. Each transfer will send the amount and at most 1 extra wei. We
  /// avoid sending 2 extra wei to the caller by using the same token amount in the second transfer as the first.
  function unwrapToRebasing(uint256 _wrappedAmount) external virtual returns (uint256) {
    if (_wrappedAmount == 0) {
      revert WrappedGovLst__InvalidAmount();
    }

    _burn(msg.sender, _wrappedAmount);

    FIXED_LST.convertToRebasing(_wrappedAmount);
    uint256 _lstAmountUnwrapped = _calcStakeForShares(_wrappedAmount * SHARE_SCALE_FACTOR);

    LST.transfer(msg.sender, _lstAmountUnwrapped);

    emit RebasingUnwrapped(msg.sender, _wrappedAmount, _lstAmountUnwrapped);
    return _lstAmountUnwrapped;
  }

  /// @notice Burn wrapped tokens to receive `FixedGovLst` tokens.
  /// @param _wrappedAmount The quantity of wrapped tokens to burn.
  /// @return _fixedTokensUnwrapped The quantity of `FixedGovLst` tokens received.
  /// @dev Since wrapped tokens are 1:1 with `FixedGovLst` tokens, this is a simple transfer.
  /// @dev May transfer up to 1 extra wei due to FixedGovLst's internal rounding.
  /// @dev When transferring using the `FixedGovLst` at most the receiver may receive one wei less
  /// than the tokens sent. This is due to the conversion of shares into tokens in the underlying
  /// `GovLst`. Shares will be rounded down to tokens and the smaller amount of tokens will be
  /// converted back into shares leading to an up to 1 wei difference between the sent amount and
  /// the received amount.
  function unwrapToFixed(uint256 _wrappedAmount) external virtual returns (uint256 _fixedTokensUnwrapped) {
    if (_wrappedAmount == 0) {
      revert WrappedGovLst__InvalidAmount();
    }

    _burn(msg.sender, _wrappedAmount);

    uint256 _wrapperBalanceBefore = FIXED_LST.balanceOf(address(this));
    FIXED_LST.transfer(msg.sender, _wrappedAmount);
    uint256 _wrapperBalanceAfter = FIXED_LST.balanceOf(address(this));
    _fixedTokensUnwrapped = _wrapperBalanceBefore - _wrapperBalanceAfter;

    emit FixedUnwrapped(msg.sender, _fixedTokensUnwrapped, _wrappedAmount);

    return _fixedTokensUnwrapped;
  }

  /// @notice Method that can be called only by the owner to update the address to which all the wrapped token's voting
  /// weight will be delegated.
  function setDelegatee(address _newDelegatee) public virtual {
    _checkOwner();
    _setDelegatee(_newDelegatee);
  }

  /// @notice Calculate the number of shares that would be created from staking a given amount.
  /// @param _amount The amount of stake tokens to convert to shares.
  /// @return The number of shares that would be created.
  /// @dev Mirrors the share calculation logic from GovLst. Rounds down.
  function _calcSharesForStake(uint256 _amount) internal view virtual returns (uint256) {
    if (LST.totalSupply() == 0) {
      return SHARE_SCALE_FACTOR * _amount;
    }

    return (_amount * LST.totalShares()) / LST.totalSupply();
  }

  /// @notice Calculate the amount of stake tokens that correspond to a given number of shares.
  /// @param _shares The number of shares to convert to stake tokens.
  /// @return The amount of stake tokens that the shares represent.
  /// @dev Converts shares back to stake token amounts. Rounds down to favor the protocol.
  function _calcStakeForShares(uint256 _shares) internal view virtual returns (uint256) {
    if (LST.totalShares() == 0) {
      return _shares / SHARE_SCALE_FACTOR;
    }

    // Rounds down, favoring the protocol
    return (_shares * LST.totalSupply()) / LST.totalShares();
  }

  /// @notice Calculate the number of shares for a stake amount, rounding up.
  /// @param _amount The amount of stake tokens to convert to shares.
  /// @return The number of shares that would be created, rounded up.
  /// @dev Similar to _calcSharesForStake but rounds up if there's any remainder.
  /// This ensures no value is lost when converting rebasing tokens to fixed tokens.
  function _calcSharesForStakeUp(uint256 _amount) internal view virtual returns (uint256) {
    uint256 _result = _calcSharesForStake(_amount);
    if (LST.totalSupply() == 0) {
      return _result;
    }

    // Add 1 if there's any remainder from the division
    if (mulmod(_amount, LST.totalShares(), LST.totalSupply()) > 0) {
      _result += 1;
    }

    return _result;
  }

  /// @notice Internal method that sets the deposit identifier for the delegate specified on the LST.
  function _setDelegatee(address _newDelegatee) internal virtual {
    emit DelegateeSet(delegatee(), _newDelegatee);
    depositId = LST.fetchOrInitializeDepositForDelegatee(_newDelegatee);
    FIXED_LST.updateDeposit(depositId);
  }
}
"
    },
    "lib/staker/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Permit.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/ERC20Permit.sol)

pragma solidity ^0.8.20;

import {IERC20Permit} from "./IERC20Permit.sol";
import {ERC20} from "../ERC20.sol";
import {ECDSA} from "../../../utils/cryptography/ECDSA.sol";
import {EIP712} from "../../../utils/cryptography/EIP712.sol";
import {Nonces} from "../../../utils/Nonces.sol";

/**
 * @dev Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 */
abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712, Nonces {
    bytes32 private constant PERMIT_TYPEHASH =
        keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");

    /**
     * @dev Permit deadline has expired.
     */
    error ERC2612ExpiredSignature(uint256 deadline);

    /**
     * @dev Mismatched signature.
     */
    error ERC2612InvalidSigner(address signer, address owner);

    /**
     * @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`.
     *
     * It's a good idea to use the same `name` that is defined as the ERC20 token name.
     */
    constructor(string memory name) EIP712(name, "1") {}

    /**
     * @inheritdoc IERC20Permit
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual {
        if (block.timestamp > deadline) {
            revert ERC2612ExpiredSignature(deadline);
        }

        bytes32 structHash = keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline));

        bytes32 hash = _hashTypedDataV4(structHash);

        address signer = ECDSA.recover(hash, v, r, s);
        if (signer != owner) {
            revert ERC2612InvalidSigner(signer, owner);
        }

        _approve(owner, spender, value);
    }

    /**
     * @inheritdoc IERC20Permit
     */
    function nonces(address owner) public view virtual override(IERC20Permit, Nonces) returns (uint256) {
        return super.nonces(owner);
    }

    /**
     * @inheritdoc IERC20Permit
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view virtual returns (bytes32) {
        return _domainSeparatorV4();
    }
}
"
    },
    "lib/staker/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/ERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol";

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * The default value of {decimals} is 18. To change this, you should override
 * this function so it returns a different value.
 *
 * We have followed general OpenZeppelin Contracts guidelines: functions revert
 * instead returning `false` on failure. This behavior is nonetheless
 * conventional and does not conflict with the expectations of ERC20
 * applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 */
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
    mapping(address account => uint256) private _balances;

    mapping(address account => mapping(address spender => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * All two of these values are immutable: they can only be set once during
     * construction.
     */
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view virtual returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5.05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the default value returned by this function, unless
     * it's overridden.
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual returns (uint8) {
        return 18;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - the caller must have a balance of at least `value`.
     */
    function transfer(address to, uint256 value) public virtual returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, value);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
     * `transferFrom`. This is semantically equivalent to an infinite approval.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 value) public virtual returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, value);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20}.
     *
     * NOTE: Does not update the allowance if the current allowance
     * is the maximum `uint256`.
     *
     * Requirements:
     *
     * - `from` and `to` cannot be the zero address.
     * - `from` must have a balance of at least `value`.
     * - the caller must have allowance for ``from``'s tokens of at least
     * `value`.
     */
    function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, value);
        _transfer(from, to, value);
        return true;
    }

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to`.
     *
     * This internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead.
     */
    function _transfer(address from, address to, uint256 value) internal {
        if (from == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        if (to == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(from, to, value);
    }

    /**
     * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
     * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
     * this function.
     *
     * Emits a {Transfer} event.
     */
    function _update(address from, address to, uint256 value) internal virtual {
        if (from == address(0)) {
            // Overflow check required: The rest of the code assumes that totalSupply never overflows
            _totalSupply += value;
        } else {
            uint256 fromBalance = _balances[from];
            if (fromBalance < value) {
                revert ERC20InsufficientBalance(from, fromBalance, value);
            }
            unchecked {
                // Overflow not possible: value <= fromBalance <= totalSupply.
                _balances[from] = fromBalance - value;
            }
        }

        if (to == address(0)) {
            unchecked {
                // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
                _totalSupply -= value;
            }
        } else {
            unchecked {
                // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
                _balances[to] += value;
            }
        }

        emit Transfer(from, to, value);
    }

    /**
     * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
     * Relies on the `_update` mechanism
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead.
     */
    function _mint(address account, uint256 value) internal {
        if (account == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(address(0), account, value);
    }

    /**
     * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
     * Relies on the `_update` mechanism.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead
     */
    function _burn(address account, uint256 value) internal {
        if (account == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        _update(account, address(0), value);
    }

    /**
     * @dev Sets `value` as the allowance of `spender` over the `owner` s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     *
     * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
     */
    function _approve(address owner, address spender, uint256 value) internal {
        _approve(owner, spender, value, true);
    }

    /**
     * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
     *
     * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
     * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
     * `Approval` event during `transferFrom` operations.
     *
     * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
     * true using the following override:
     * ```
     * function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
     *     super._approve(owner, spender, value, true);
     * }
     * ```
     *
     * Requirements are the same as {_approve}.
     */
    function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
        if (owner == address(0)) {
            revert ERC20InvalidApprover(address(0));
        }
        if (spender == address(0)) {
            revert ERC20InvalidSpender(address(0));
        }
        _allowances[owner][spender] = value;
        if (emitEvent) {
            emit Approval(owner, spender, value);
        }
    }

    /**
     * @dev Updates `owner` s allowance for `spender` based on spent `value`.
     *
     * Does not update the allowance value in case of infinite allowance.
     * Revert if not enough allowance is available.
     *
     * Does not emit an {Approval} event.
     */
    function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            if (currentAllowance < value) {
                revert ERC20InsufficientAllowance(spender, currentAllowance, value);
            }
            unchecked {
                _approve(owner, spender, currentAllowance - value, false);
            }
        }
    }
}
"
    },
    "lib/staker/lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the value of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 value) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}
"
    },
    "lib/staker/lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../utils/Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using Address for address;

    /**
     * @dev An operation with an ERC20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Indicates a failed `decreaseAllowance` request.
     */
    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        forceApprove(token, spender, oldAllowance + value);
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
     * value, non-reverting calls are assumed to be successful.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
        unchecked {
            uint256 currentAllowance = token.allowance(address(this), spender);
            if (currentAllowance < requestedDecrease) {
                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
            }
            forceApprove(token, spender, currentAllowance - requestedDecrease);
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data);
        if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
        // and not revert is the subcall reverts.

        (bool success, bytes memory returndata) = address(token).call(data);
        return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
    }
}
"
    },
    "lib/staker/lib/openzeppelin-contracts/contracts/access/Ownable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {Context} from "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is set to the address provided by the deployer. This can
 * later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}
"
    },
    "lib/staker/src/Staker.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.23;

import {DelegationSurrogate} from "./DelegationSurrogate.sol";
import {INotifiableRewardReceiver} from "./interfaces/INotifiableRewardReceiver.sol";
import {IEarningPowerCalculator} from "./interfaces/IEarningPowerCalculator.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Multicall} from "@openzeppelin/contracts/utils/Multicall.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";

/// @title Staker
/// @author [ScopeLift](https://scopelift.co)
/// @notice This contract manages the distribution of rewards to stakers. Rewards are denominated
/// in an ERC20 token and sent to the contract by authorized reward notifiers. To stake means to
/// deposit a designated, delegable ERC20 governance token and leave it over a period of time.
/// The contract allows stakers to delegate the voting power of the tokens they stake to any
/// governance delegatee on a per deposit basis. The contract also allows stakers to designate the
/// claimer address that earns rewards for the associated deposit.
///
/// The staking mechanism of this contract is directly inspired by the Synthetix StakingRewards.sol
/// implementation. The core mechanic involves the streaming of rewards over a designated period
/// of time. Each staker earns rewards proportional to their share of the total stake, and each
/// staker earns only while their tokens are staked. Stakers may add or withdraw their stake at any
/// point. Claimers can claim the rewards they've earned at any point. When a new reward is
/// received, the reward duration restarts, and the rate at which rewards are streamed is updated
/// to include the newly received rewards along with any remaining rewards that have finished
/// streaming since the last time a reward was received.
///
/// The rate at which a depositor earns rewards is proportional to their earning power. Earning
/// power is based on the amount the depositor has staked and the activity of their delegatee.
/// The calculation of earning power is handled by a separate module called the earning power
/// calculator. This module is set by the owner, and can be updated by the owner. If the owner of
/// the Staker contract is a DAO, which is the expected common case, this means the DAO has
/// the ability to define and iterate on its own definition of active, aligned participation,
/// and to decide how to reward it.
abstract contract Staker is INotifiableRewardReceiver, Multicall {
  using SafeCast for uint256;

  /// @notice A unique identifier assigned to each deposit.
  type DepositIdentifier is uint256;

  /// @notice Emitted when stake is deposited by a depositor, either to a new deposit or one that
  /// already exists.
  event StakeDeposited(
    address owner,
    DepositIdentifier indexed depositId,
    uint256 amount,
    uint256 depositBalance,
    uint256 earningPower
  );

  /// @notice Emitted when a depositor withdraws some portion of stake from a given deposit.
  event StakeWithdrawn(
    address owner,
    DepositIdentifier indexed depositId,
    uint256 amount,
    uint256 depositBalance,
    uint256 earningPower
  );

  /// @notice Emitted when a deposit's delegatee is changed.
  event DelegateeAltered(
    DepositIdentifier indexed depositId,
    address oldDelegatee,
    address newDelegatee,
    uint256 earningPower
  );

  /// @notice Emitted when a deposit's claimer is changed.
  event ClaimerAltered(
    DepositIdentifier indexed depositId,
    address indexed oldClaimer,
    address indexed newClaimer,
    uint256 earningPower
  );

  /// @notice Emitted when a claimer claims their earned reward.
  event RewardClaimed(
    DepositIdentifier indexed depositId,
    address indexed claimer,
    uint256 amount,
    uint256 earningPower
  );

  /// @notice Emitted when this contract is notified of a new reward.
  event RewardNotified(uint256 amount, address notifier);

  /// @notice Emitted when the admin address is set.
  event AdminSet(address indexed oldAdmin, address indexed newAdmin);

  /// @notice Emitted when the earning power calculator address is set.
  event EarningPowerCalculatorSet(
    address indexed oldEarningPowerCalculator, address indexed newEarningPowerCalculator
  );

  /// @notice Emitted when the max bump tip is modified.
  event MaxBumpTipSet(uint256 oldMaxBumpTip, uint256 newMaxBumpTip);

  /// @notice Emitted when the claim fee parameters are modified.
  event ClaimFeeParametersSet(
    uint96 oldFeeAmount, uint96 newFeeAmount, address oldFeeCollector, address newFeeCollector
  );

  /// @notice Emitted when a reward notifier address is enabled or disabled.
  event RewardNotifierSet(address indexed account, bool isEnabled);

  /// @notice Emitted when a deposit's earning power is changed via bumping.
  event EarningPowerBumped(
    DepositIdentifier indexed depositId,
    uint256 oldEarningPower,
    uint256 newEarningPower,
    address bumper,
    address tipReceiver,
    uint256 tipAmount
  );

  /// @notice Thrown when an account attempts a call for which it lacks appropriate permission.
  /// @param reason Human readable code explaining why the call is unauthorized.
  /// @param caller The address that attempted the unauthorized call.
  error Staker__Unauthorized(bytes32 reason, address caller);

  /// @notice Thrown if the new rate after a reward notification would be zero.
  error Staker__InvalidRewardRate();

  /// @notice Thrown if the following invariant is broken after a new reward: the contract should
  /// always have a reward balance sufficient to distribute at the reward rate across the reward
  /// duration.
  error Staker__InsufficientRewardBalance();

  /// @notice Thrown if the unclaimed rewards are insufficient to cover a bumper's requested tip,
  /// or in the case of an earning power decrease the tip of a subsequent earning power increase.
  error Staker__InsufficientUnclaimedRewards();

  /// @notice Thrown if a caller attempts to specify address zero for certain designated addresses.
  error Staker__InvalidAddress();

  /// @notice Thrown if a bumper's requested tip is invalid.
  error Staker__InvalidTip();

  /// @notice Thrown if the claim fee parameters are outside permitted bounds.
  error Staker__InvalidClaimFeeParameters();

  /// @notice Thrown when an onBehalf method is called with a deadline that has expired.
  error Staker__ExpiredDeadline();

  /// @notice Thrown if a caller supplies an invalid signature to a method that requires one.
  error Staker__InvalidSignature();

  /// @notice Thrown if an earning power update is unqualified to be bumped.
  /// @param score The would-be new earning power which did not qualify.
  error Staker__Unqualified(uint256 score);

  /// @notice Metadata associated with a discrete staking deposit.
  /// @param balance The deposit's staked balance.
  /// @param owner The owner of this deposit.
  /// @param delegatee The governance delegate who receives the voting weight for this deposit.
  /// @param claimer The address which has the right to withdraw rewards earned by this
  /// deposit.
  /// @param earningPower The "power" this deposit has as it pertains to earning rewards, which
  /// accrue to this deposit at a rate proportional to its share of the total earning power of the
  /// system.
  /// @param rewardPerTokenCheckpoint Checkpoint of the reward per token accumulator for this
  /// deposit. It represents the value of the global accumulator at the last time a given deposit's
  /// rewards were calculated and stored. The difference between the global value and this value
  /// can be used to calculate the interim rewards earned by given deposit.
  /// @param scaledUnclaimedRewardCheckpoint Checkpoint of the unclaimed rewards earned by a given
  /// deposit with the scale factor included. This value is stored any time an action is taken that
  /// specifically impacts the rate at which rewards are earned by a given deposit. Total unclaimed
  /// rewards for a deposit are thus this value plus all rewards earned after this checkpoint was
  /// taken. This value is reset to zero when the deposit's rewards are claimed.
  struct Deposit {
    uint96 balance;
    address owner;
    uint96 earningPower;
    address delegatee;
    address claimer;
    uint256 rewardPerTokenCheckpoint;
    uint256 scaledUnclaimedRewardCheckpoint;
  }

  /// @notice Parameters associated with the fee assessed when rewards are claimed.
  /// @param feeAmount The absolute amount of the reward token that is taken as a fee when rewards
  /// are claimed for a given deposit.
  /// @param feeCollector The address to which reward token fees are sent.
  struct ClaimFeeParameters {
    uint96 feeAmount;
    address feeCollector;
  }

  /// @notice ERC20 token in which rewards are denominated and distributed.
  IERC20 public immutable REWARD_TOKEN;

  /// @notice Delegable governance token which users stake to earn rewards.
  IERC20 public immutable STAKE_TOKEN;

  /// @notice Length of time over which rewards sent to this contract are distributed to stakers.
  uint256 public constant REWARD_DURATION = 30 days;

  /// @notice Scale factor used in reward calculation math to reduce rounding errors caused by
  /// truncation during division.
  uint256 public constant SCALE_FACTOR = 1e36;

  /// @notice The maximum value to which the claim fee can be set.
  /// @dev For anything other than a zero value, this immutable parameter should be set in the
  /// constructor of a concrete implementation inheriting from Staker.
  uint256 public immutable MAX_CLAIM_FEE;

  /// @dev Unique identifier that will be used for the next deposit.
  DepositIdentifier private nextDepositId;

  /// @notice Permissioned actor that can enable/disable `rewardNotifier` addresses, set the max
  /// bump tip, set the claim fee parameters, and update the earning power calculator.
  address public admin;

  /// @notice Maximum tip a bumper can request.
  uint256 public maxBumpTip;

  /// @notice Global amount currently staked across all deposits.
  uint256 public totalStaked;

  /// @notice Global amount of earning power for all deposits.
  uint256 public totalEarningPower;

  /// @notice Contract that determines a deposit's earning power based on their delegatee.
  /// @dev An earning power calculator should take into account that a deposit's earning power is a
  /// uint96. There may be overflow issues within governance staker if this is not taken into
  /// account. Also, there should be some mechanism to prevent the deposit from frequently being
  /// bumpable: if earning power changes frequently, this will eat into a users unclaimed rewards.
  IEarningPowerCalculator public earningPowerCalculator;

  /// @notice Tracks the total staked by a depositor across all unique deposits.
  mapping(address depositor => uint256 amount) public depositorTotalStaked;

  /// @notice Tracks the total earning power by a depositor across all unique deposits.
  mapping(address depositor => uint256 earningPower) public depositorTotalEarningPower;

  /// @notice Stores the metadata associated with a given deposit.
  mapping(DepositIdentifier depositId => Deposit deposit) public deposits;

  /// @notice Time at which rewards distribution will complete if there are no new rewards.
  uint256 public rewardEndTime;

  /// @notice Last time at which the global rewards accumulator was updated.
  uint256 public lastCheckpointTime;

  /// @notice Global rate at which rewards are currently being distributed to stakers,
  /// denominated in scaled reward tokens per second, using the SCALE_FACTOR.
  uint256 public scaledRewardRate;

  /// @notice Checkpoint value of the global reward per token accumulator.
  uint256 public rewardPerTokenAccumulatedCheckpoint;

  /// @notice Maps addresses to whether they are authorized to call `notifyRewardAmount`.
  mapping(address rewardNotifier => bool) public isRewardNotifier;

  /// @notice Current configuration parameters for the fee assessed on claiming.
  ClaimFeeParameters public claimFeeParameters;

  /// @param _rewardToken ERC20 token in which rewards will be denominated.
  /// @param _stakeToken Delegable governance token which users will stake to earn rewards.
  /// @param _earningPowerCalculator The contract that will serve as the initial calculator of
  /// earning power for the staker system.
  /// @param _admin Address which will have permission to manage reward notifiers, claim fee
  /// parameters, the max bump tip, and the reward calculator.
  constructor(
    IERC20 _rewardToken,
    IERC20 _stakeToken,
    IEarningPowerCalculator _earningPowerCalculator,
    uint256 _maxBumpTip,
    address _admin
  ) {
    REWARD_TOKEN = _rewardToken;
    STAKE_TOKEN = _stakeToken;
    _setAdmin(_admin);
    _setMaxBumpTip(_maxBumpTip);
    _setEarningPowerCalculator(address(_earningPowerCalculator));
  }

  /// @notice Set the admin address.
  /// @param _newAdmin Address of the new admin.
  /// @dev Caller must be the current admin.
  function setAdmin(address _newAdmin) external virtual {
    _revertIfNotAdmin();
    _setAdmin(_newAdmin);
  }

  /// @notice Set the earning power calculator address.
  function setEarningPowerCalculator(address _newEarningPowerCalculator) external virtual {
    _revertIfNotAdmin();
    _setEarningPowerCalculator(_newEarningPowerCalculator);
  }

  /// @notice Set the max bump tip.
  /// @param _newMaxBumpTip Value of the new max bump tip.
  /// @dev Caller must be the current admin.
  function setMaxBumpTip(uint256 _newMaxBumpTip) external virtual {
    _revertIfNotAdmin();
    _setMaxBumpTip(_newMaxBumpTip);
  }

  /// @notice Enables or disables a reward notifier address.
  /// @param _rewardNotifier Address of the reward notifier.
  /// @param _isEnabled `true` to enable the `_rewardNotifier`, or `false` to disable.
  /// @dev Caller must be the current admin.
  function setRewardNotifier(address _rewardNotifier, bool _isEnabled) external virtual {
    _revertIfNotAdmin();
    isRewardNotifier[_rewardNotifier] = _isEnabled;
    emit RewardNotifierSet(_rewardNotifier, _isEnabled);
  }

  /// @notice Updates the parameters related to the claim fee.
  /// @param _params The new fee parameters.
  /// @dev Caller must be current admin.
  function setClaimFeeParameters(ClaimFeeParameters memory _params) external virtual {
    _revertIfNotAdmin();
    _setClaimFeeParameters(_params);
  }

  /// @notice A method to get the delegation surrogate contract for a given delegate.
  /// @param _delegatee The address to which the delegation surrogate is delegating voting power.
  /// @return The delegation surrogate.
  /// @dev A concrete implementation should return a delegate surrogate address for a given
  /// delegatee. In practice this may be as simple as returning an address stored in a mapping or
  /// computing its create2 address.
  function surrogates(address _delegatee) public view virtual returns (DelegationSurrogate);

  /// @notice Timestamp representing the last time at which rewards have been distributed, which is
  /// either the current timestamp (because rewards are still actively being streamed) or the time
  /// at which the reward duration ended (because all rewards to date have already been streamed).
  /// @return Timestamp representing the last time at which rewards have been distributed.
  function lastTimeRewardDistributed() public view virtual returns (uint256) {
    if (rewardEndTime <= block.timestamp) return rewardEndTime;
    else return block.timestamp;
  }

  /// @notice Live value of the global reward per token accumulator. It is the sum of the last
  /// checkpoint value with the live calculation of the value that has accumulated in the interim.
  /// This number should monotonically increase over time as more rewards are distributed.
  /// @return Live value of the global reward per token accumulator.
  function rewardPerTokenAccumulated() public view virtual returns (uint256) {
    if (totalEarningPower == 0) return rewardPerTokenAccumulatedCheckpoint;

    return rewardPerTokenAccumulatedCheckpoint
      + (scaledRewardRate * (lastTimeRewardDistributed() - lastCheckpointTime)) / totalEarningPower;
  }

  /// @notice Live value of the unclaimed rewards earned by a given deposit. It is the
  /// sum of the last checkpoint value of the unclaimed rewards with the live calculation of the
  /// rewards that have accumulated for this account in the interim. This value can only increase,
  /// until it is reset to zero once the unearned rewards are claimed.
  ///
  /// Note that the contract tracks the unclaimed rewards internally with the scale factor
  /// included, in order to avoid the accrual of precision losses as users takes actions that
  /// cause rewards to be checkpointed. This external helper method is useful for integrations, and
  /// returns the value after it has been scaled down to the reward token's raw decimal amount.
  /// @param _depositId Identifier of the deposit in question.
  /// @return Live value of the unclaimed rewards earned by a given deposit.
  function unclaimedReward(DepositIdentifier _depositId) external view virtual returns (uint256) {
    return _scaledUnclaimedReward(deposits[_depositId]) / SCALE_FACTOR;
  }

  /// @notice Stake tokens to a new deposit. The caller must pre-approve the staking contract to
  /// spend at least the would-be staked amount of the token.
  /// @param _amount The amount of the staking token to stake.
  /// @param _delegatee The address to assign the governance voting weight of the staked tokens.
  /// @return _depositId The unique identifier for this deposit.
  /// @dev The delegatee may not be the zero address. The deposit will be owned by the message
  /// sender, and the claimer will also be the message sender.
  function stake(uint256 _amount, address _delegatee)
    external
    virtual
    returns (DepositIdentifier _depositId)
  {
    _depositId = _stake(msg.sender, _amount, _delegatee, msg.sender);
  }

  /// @notice Method to stake tokens to a new deposit. The caller must pre-approve the staking
  /// contract to spend at least the would-be staked amount of the token.
  /// @param _amount Quantity of the staking token to stake.
  /// @param _delegatee Address to assign the governance voting weight of the staked tokens.
  /// @param _claimer Address that will have the right to claim rewards for this stake.
  /// @return _depositId Unique identifier for this deposit.
  /// @dev Neither the delegatee nor the claimer may be the zero address. The deposit will be
  /// owned by the message sender.
  function stake(uint256 _amount, address _delegatee, address _claimer)
    external
    virtual
    returns (DepositIdentifier _depositId)
  {
    _depositId = _stake(msg.sender, _amount, _delegatee, _claimer);
  }

  /// @notice Add more staking tokens to an existing deposit. A staker should call this method when
  /// they have an existing deposit, and wish to stake more while retaining the same delegatee and
  /// claimer.
  /// @param _depositId Unique identifier of the deposit to which stake will be added.
  /// @param _amount Quantity of stake to be added.
  /// @dev The message sender must be the owner of the deposit.
  function stakeMore(DepositIdentifier _depositId, uint256 _amount) external virtual {
    Deposit storage deposit = deposits[_depositId];
    _revertIfNotDepositOwner(deposit, msg.sender);
    _stakeMore(deposit, _depositId, _amount);
  }

  /// @notice For an existing deposit, change the address to which governance voting power is
  /// assigned.
  /// @param _depositId Unique identifier of the deposit which will have its delegatee altered.
  /// @param _newDelegatee Address of the new governance delegate.
  /// @dev The new delegatee may not be the zero address. The message sender must be the owner of
  /// the deposit.
  function alterDelegatee(DepositIdentifier _depositId, address _newDelegatee) external virtual {
    Deposit storage deposit = deposits[_depositId];
    _revertIfNotDepositOwner(deposit, msg.sender);
    _alterDelegatee(deposit, _depositId, _newDelegatee);
  }

  /// @notice For an existing deposit, change the claimer account which has the right to
  /// withdraw staking rewards.
  /// @param _depositId Unique identifier of the deposit which will have its claimer altered.
  /// @param _newClaimer Address of the new claimer.
  /// @dev The new claimer may not be the zero address. The message sender must be the owner of
  /// the deposit.
  function alterClaimer(DepositIdentifier _depositId, address _newClaimer) external virtual {
    Deposit storage deposit = deposits[_depositId];
    _revertIfNotDepositOwner(deposit, msg.sender);
    _alterClaimer(deposit, _depositId, _newClaimer);
  }

  /// @notice Withdraw staked tokens from an existing deposit.
  /// @param _depositId Unique identifier of the deposit from which stake will be withdrawn.
  /// @param _amount Quantity of staked token to withdraw.
  /// @dev The message sender must be the owner of the deposit. Stake is withdrawn to the message
  /// sender's account.
  function withdraw(DepositIdentifier _depositId, uint256 _amount) external virtual {
    Deposit storage deposit = deposits[_depositId];
    _revertIfNotDepositOwner(deposit, msg.sender);
    _withdraw(deposit, _depositId, _amount);
  }

  /// @notice Claim reward tokens earned by a given deposit. Message sender must be the claimer
  /// address of the deposit or the owner of the deposit. Tokens are sent to the caller.\

Tags:
ERC20, Multisig, Staking, Yield, Voting, Timelock, Upgradeable, Multi-Signature, Factory|addr:0x33e4a7d15de9923c680542cb10d76ea4868123fc|verified:true|block:23492191|tx:0x682a8df6a5df4548d89b06f90951a6aa4aa310195492601af8f960b9984a4c7b|first_check:1759475962

Submitted on: 2025-10-03 09:19:23

Comments

Log in to comment.

No comments yet.