ETH Price: $1,974.83 (+0.05%)
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

More Info

Private Name Tags

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Unstake245105592026-02-22 6:16:474 hrs ago1771741007IN
0x6590cBBC...7B02c2fCC
0 ETH0.000009450.0303508
Unstake245061732026-02-21 15:35:3518 hrs ago1771688135IN
0x6590cBBC...7B02c2fCC
0 ETH0.000014490.04409665
Unstake245059592026-02-21 14:52:3519 hrs ago1771685555IN
0x6590cBBC...7B02c2fCC
0 ETH0.000014530.05018261
Unstake245055922026-02-21 13:38:5920 hrs ago1771681139IN
0x6590cBBC...7B02c2fCC
0 ETH0.000017020.05939763
Unstake245051642026-02-21 12:13:2322 hrs ago1771676003IN
0x6590cBBC...7B02c2fCC
0 ETH0.000014870.04460264
Unstake245034002026-02-21 6:19:2327 hrs ago1771654763IN
0x6590cBBC...7B02c2fCC
0 ETH0.000014220.04496368
Unstake245031552026-02-21 5:29:5928 hrs ago1771651799IN
0x6590cBBC...7B02c2fCC
0 ETH0.000012090.03824011
Unstake245030632026-02-21 5:11:3529 hrs ago1771650695IN
0x6590cBBC...7B02c2fCC
0 ETH0.000011290.03569876
Unstake245026262026-02-21 3:43:4730 hrs ago1771645427IN
0x6590cBBC...7B02c2fCC
0 ETH0.0000140.04200722
Unstake244983452026-02-20 13:24:4744 hrs ago1771593887IN
0x6590cBBC...7B02c2fCC
0 ETH0.000015860.04828037
Unstake244982062026-02-20 12:56:5945 hrs ago1771592219IN
0x6590cBBC...7B02c2fCC
0 ETH0.000014930.047203
Unstake244980862026-02-20 12:32:5945 hrs ago1771590779IN
0x6590cBBC...7B02c2fCC
0 ETH0.000015870.05097027
Unstake244972822026-02-20 9:51:232 days ago1771581083IN
0x6590cBBC...7B02c2fCC
0 ETH0.000018440.05613104
Unstake244968312026-02-20 8:20:352 days ago1771575635IN
0x6590cBBC...7B02c2fCC
0 ETH0.000014180.04254803
Unstake244961262026-02-20 5:58:592 days ago1771567139IN
0x6590cBBC...7B02c2fCC
0 ETH0.000728352.04233801
Unstake244912602026-02-19 13:41:472 days ago1771508507IN
0x6590cBBC...7B02c2fCC
0 ETH0.000355131.15685479
Unstake244901762026-02-19 10:04:353 days ago1771495475IN
0x6590cBBC...7B02c2fCC
0 ETH0.000019710.05913264
Unstake244896002026-02-19 8:08:593 days ago1771488539IN
0x6590cBBC...7B02c2fCC
0 ETH0.00001710.05130628
Unstake244894972026-02-19 7:48:233 days ago1771487303IN
0x6590cBBC...7B02c2fCC
0 ETH0.000016930.05077785
Unstake244888562026-02-19 5:39:473 days ago1771479587IN
0x6590cBBC...7B02c2fCC
0 ETH0.0000110.0334754
Unstake244883452026-02-19 3:56:593 days ago1771473419IN
0x6590cBBC...7B02c2fCC
0 ETH0.000028590.09179799
Unstake244883112026-02-19 3:50:113 days ago1771473011IN
0x6590cBBC...7B02c2fCC
0 ETH0.000017980.05773573
Unstake244807802026-02-18 2:37:594 days ago1771382279IN
0x6590cBBC...7B02c2fCC
0 ETH0.000012060.0387155
Unstake244792162026-02-17 21:23:594 days ago1771363439IN
0x6590cBBC...7B02c2fCC
0 ETH0.000016470.05367313
Unstake244790542026-02-17 20:51:234 days ago1771361483IN
0x6590cBBC...7B02c2fCC
0 ETH0.000021330.07102918
View all transactions

Latest 1 internal transaction

Advanced mode:
Parent Transaction Hash Method Block
From
To
0x6101c080223183922025-04-21 15:51:59306 days ago1745250719  Contract Creation0 ETH
Loading...
Loading
Loading...
Loading
Cross-Chain Transactions

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
FixedStakedObol

Compiler Version
v0.8.28+commit.7893614a

Optimization Enabled:
Yes with 5083 runs

Other Settings:
cancun EvmVersion
File 1 of 33 : FixedStakedObol.sol
//  SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import {FixedGovLst, IERC20} from "stGOV/FixedGovLst.sol";
import {FixedGovLstPermitAndStake} from "stGOV/extensions/FixedGovLstPermitAndStake.sol";
import {FixedGovLstOnBehalf} from "stGOV/extensions/FixedGovLstOnBehalf.sol";
import {GovLst} from "stGOV/GovLst.sol";

/// @title FixedStakedObol
/// @author [ScopeLift](https://scopelift.co)
/// @notice The fixed balance variant of the liquid staked OBOL token, i.e. stOBOL. This contract
/// works in tandem with the rebasing staked OBOL token, i.e. rstOBOL. While this contract is the
/// canonical liquid representation of staked OBOL, the two use the same underlying accounting
/// system, which is located in the rebasing token contract. This contract is deployed by, and
/// interacts with, the rstOBOL contract. Unlike rstOBOL, this contract maintains a fixed balance
/// for stakers, even as rewards are distributed. While the user's balance of stOBOL stays fixed,
/// the number of underlying OBOL tokens they have a claim to grows over time. As such, 1 stOBOL
/// will be worth more and more OBOL over time.
contract FixedStakedObol is FixedGovLst, FixedGovLstPermitAndStake, FixedGovLstOnBehalf {
  /// @notice Initializes the fixed balance staked OBOL contract.
  /// @param _name The name for the fixed balance liquid stake token.
  /// @param _symbol The symbol for the fixed balance liquid stake token.
  /// @param _lst The rebasing LST for which this contract will serve as the fixed balance
  /// counterpart.
  /// @param _stakeToken The ERC20 token that acts as the staking token.
  /// @param _shareScaleFactor The scale factor applied to shares in the rebasing contract.
  constructor(
    string memory _name,
    string memory _symbol,
    string memory _version,
    GovLst _lst,
    IERC20 _stakeToken,
    uint256 _shareScaleFactor
  ) FixedGovLst(_name, _symbol, _version, _lst, _stakeToken, _shareScaleFactor) {}
}

File 2 of 33 : FixedGovLst.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import {GovLst} from "./GovLst.sol";
import {FixedLstAddressAlias} from "./FixedLstAddressAlias.sol";
import {Staker} from "staker/Staker.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import {Nonces} from "@openzeppelin/contracts/utils/Nonces.sol";
import {Multicall} from "@openzeppelin/contracts/utils/Multicall.sol";

/// @title FixedGovLst
/// @author [ScopeLift](https://scopelift.co)
/// @notice This contract creates a fixed balance counterpart LST to the rebasing LST implemented in `GovLst.sol`.
/// In most ways, it can be thought of as a peer to the rebasing LST with a different accounting system. Whereas the
/// rebasing LST token is 1:1 with the underlying governance token, this fixed LST has an exchange rate. Whereas the
/// total supply and holder balances of the rebasing LST token increase automatically as rewards are distributed, this
/// fixed LST token has balances that stay fixed when rewards are distributed. Instead, the exchange rate changes such
/// that the same number of fixed LST tokens are now worth more of the underlying governance tokens.
///
/// While peers in most respects, the fixed LST ultimately hooks into the rebasing LST's accounting system under
/// the hood. One practical effect of this is slightly higher gas costs for operations using the fixed LST. Another
/// effect is that governance tokens staked in the fixed LST show up in the total supply of the of rebasing LST, but
/// not vice versa. In other words, the total supply of the rebasing LST is the sum of tokens staked in the rebasing
/// LST _and_ the fixed LST. The total supply of the fixed LST is isolated to itself. Note that this is *not* true of
/// user balances. A holder's fixed LST balance and rebasing LST balance are independent. All of these accounting
/// properties are effectively the same as the wrapped version of the rebasing LST.
///
/// One very important way in which the fixed LST is different from a wrapped version of the rebasing LST is with
/// regards to delegation. Holders of a wrapped LST tokens are not able to specify their own delegatee, instead all
/// tokens are delegated to the default. Holders of the fixed LST do not have to make this tradeoff. They are able to
/// specify a delegate in the same way as holders of the rebasing LST.
contract FixedGovLst is IERC20, IERC20Metadata, IERC20Permit, Multicall, EIP712, Nonces {
  using FixedLstAddressAlias for address;
  using SafeERC20 for IERC20;

  /// @notice Emitted when a holder updates their deposit identifier, which determines the delegatee of their voting
  /// weight.
  /// @dev This event must be combined with the `DepositUpdated` event on the UniLst for an accurate picture all deposit
  /// ids for a given holder.
  /// @param holder The address of the account updating their deposit.
  /// @param oldDepositId The old deposit identifier that loses the holder's voting weight.
  /// @param newDepositId The new deposit identifier that will receive the holder's voting weight.
  event DepositUpdated(
    address indexed holder, Staker.DepositIdentifier oldDepositId, Staker.DepositIdentifier newDepositId
  );

  /// @notice Emitted when governance tokens are staked to receive fixed LST tokens.
  /// @param account The address of the account staking tokens.
  /// @param amount The number of governance tokens staked.
  event Staked(address indexed account, uint256 amount);

  /// @notice Emitted when rebasing LST tokens are converted to fixed LST tokens.
  /// @param account The address of the account converting their tokens.
  /// @param amount The number of rebasing LST tokens converted to fixed LST tokens.
  event Fixed(address indexed account, uint256 amount);

  /// @notice Emitted when fixed LST tokens are converted to rebasing LST tokens.
  /// @param account The address of the account converting their tokens.
  /// @param amount The number of rebasing LST tokens received.
  event Unfixed(address indexed account, uint256 amount);

  /// @notice Emitted when rebasing LST tokens mistakenly sent to a fixed holder alias address are rescued.
  /// @param account The address of the account rescuing their tokens.
  /// @param amount The number of rebasing LST tokens received from the rescue.
  event Rescued(address indexed account, uint256 amount);

  /// @notice Thrown when a holder attempts to transfer more tokens than they hold.
  error FixedGovLst__InsufficientBalance();

  /// @notice Thrown by signature-based "onBehalf" methods when a signature is past its expiry date.
  error FixedGovLst__SignatureExpired();

  /// @notice Thrown by signature-based "onBehalf" methods when a signature is invalid.
  error FixedGovLst__InvalidSignature();

  /// @notice The corresponding rebasing LST token for which this contract serves as a fixed balance counterpart.
  GovLst public immutable LST;

  /// @notice The underlying governance token which is staked.
  IERC20 public immutable STAKE_TOKEN;

  /// @notice The factor by which scales are multiplied in the underlying rebasing LST.
  uint256 public immutable SHARE_SCALE_FACTOR;

  /// @notice The ERC20 Metadata compliant name of the fixed LST token.
  string private NAME;

  /// @notice The ERC20 Metadata compliant symbol of the fixed LST token.
  string private SYMBOL;

  /// @notice The number of decimals for the fixed LST token.
  uint8 private constant DECIMALS = 18;

  /// @notice Type hash used when encoding data for `permit` calls.
  bytes32 public constant PERMIT_TYPEHASH =
    keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");

  /// @notice The number of rebasing LST shares a given fixed LST token holder controls via their fixed LST holdings.
  /// @dev The fixed LST `balanceOf` the holder is this number scaled down by the `SHARE_SCALE_FACTOR`
  mapping(address _holder => uint256 _balance) private shareBalances;

  /// @notice The total number of rebasing LST shares controlled across all fixed LST token holders.
  /// @dev The fixed LST `totalSupply` is this number scaled down by the `SHARE_SCALE_FACTOR`.
  uint256 private totalShares;

  /// @notice Mapping used to determine the amount of Fixed LST tokens the spender has been approved to transfer on
  /// the holder's behalf.
  mapping(address holder => mapping(address spender => uint256 amount)) public allowance;

  /// @param _name The name for the fixed balance liquid stake token.
  /// @param _symbol The symbol for the fixed balance liquid stake token.
  /// @param _lst The rebasing LST for which this contract will serve as the fixed balance counterpart.
  constructor(
    string memory _name,
    string memory _symbol,
    string memory _version,
    GovLst _lst,
    IERC20 _stakeToken,
    uint256 _shareScaleFactor
  ) EIP712(_name, _version) {
    NAME = _name;
    SYMBOL = _symbol;
    LST = _lst;
    SHARE_SCALE_FACTOR = _shareScaleFactor;
    STAKE_TOKEN = _stakeToken;
  }

  /// @notice The decimal precision with which the fixed LST token stores its balances.
  function decimals() external pure virtual returns (uint8) {
    return DECIMALS;
  }

  /// @inheritdoc IERC20Metadata
  function name() external view virtual returns (string memory) {
    return NAME;
  }

  /// @inheritdoc IERC20Metadata
  function symbol() external view virtual returns (string memory) {
    return SYMBOL;
  }

  /// @notice The EIP712 signing version of the contract.
  function version() external view virtual returns (string memory) {
    return _EIP712Version();
  }

  /// @notice The balance of the holder in fixed LST tokens. Unlike the rebasing LST, this balance is stable even as
  /// rewards accrue. As a result, a fixed LST token does not map 1:1 with the balance of the underlying staked tokens.
  /// Instead, the holder's fixed LST balance remains the same, but the number of stake tokens he would receive if he
  /// were to unstake increases.
  /// @param _holder The account whose balance is being queried.
  /// @return The balance of the holder in fixed tokens.
  function balanceOf(address _holder) public view virtual returns (uint256) {
    return _scaleDown(shareBalances[_holder]);
  }

  /// @notice The total number of fixed LST tokens in existence. As with a holder's balance, this number does not
  /// change when rewards are distributed.
  function totalSupply() public view virtual returns (uint256) {
    return _scaleDown(totalShares);
  }

  /// @notice Get the current nonce for an owner
  /// @dev This function explicitly overrides both Nonces and IERC20Permit to allow compatibility
  /// @param _owner The address of the owner
  /// @return The current nonce for the owner
  function nonces(address _owner) public view virtual override(Nonces, IERC20Permit) returns (uint256) {
    return Nonces.nonces(_owner);
  }

  /// @notice The domain separator used by this contract for all EIP712 signature based methods.
  function DOMAIN_SEPARATOR() external view virtual returns (bytes32) {
    return _domainSeparatorV4();
  }

  /// @notice The delegatee to whom the voting weight in the default deposit is delegated.
  /// @dev This method is a pass through to the method of the same name and signature on the Rebasing LST. It is
  /// provided here as a convenience for integrators.
  function defaultDelegatee() external view virtual returns (address) {
    return LST.defaultDelegatee();
  }

  /// @notice The stake deposit identifier associated with a given delegatee address.
  /// @param _delegatee The delegatee in question.
  /// @return The deposit identifier of the deposit in question.
  /// @dev This method is a pass through to the method of the same name and signature on the Rebasing LST. It is
  /// provided here as a convenience for integrators.
  function depositForDelegatee(address _delegatee) external view virtual returns (Staker.DepositIdentifier) {
    return LST.depositForDelegatee(_delegatee);
  }

  /// @notice The delegatee to whom a given holder's stake is currently delegated. This will be the delegatee to whom
  /// the user has chosen to assign their voting weight OR the default delegatee, if the user's deposit has been
  /// moved to the override state.
  /// @param _holder The holder in question.
  /// @return _delegatee The address to which this holder's voting weight is currently delegated.
  function delegateeForHolder(address _holder) public view virtual returns (address _delegatee) {
    return LST.delegateeForHolder(_holder.fixedAlias());
  }

  /// @notice The delegatee to whom a given holder's stake is currently delegated. This will be the delegatee to whom
  /// the user has chosen to assign their voting weight OR the default delegatee, if the user's deposit has been
  /// moved to the override state.
  /// @param _holder The holder in question.
  /// @return The address to which this holder's voting weight is currently delegated.
  /// @dev This method is included for partial compatibility with the `IVotes` interface. It returns the same data as
  /// the `delegateeForHolder` method.
  function delegates(address _holder) external view virtual returns (address) {
    return LST.delegateeForHolder(_holder.fixedAlias());
  }

  /// @notice Returns the deposit identifier managed by the LST for a given delegatee. If that deposit does not yet
  /// exist, it initializes it. A depositor can call this method if the deposit for their chosen delegatee has not been
  /// previously initialized.
  /// @param _delegatee The address of the delegatee.
  /// @return The deposit identifier of the existing, or newly created, stake deposit for this delegatee.
  /// @dev This method is a pass through to the method of the same name and signature on the Rebasing LST. It is
  /// provided here as a convenience for integrators.
  function fetchOrInitializeDepositForDelegatee(address _delegatee) external virtual returns (Staker.DepositIdentifier) {
    return LST.fetchOrInitializeDepositForDelegatee(_delegatee);
  }

  /// @notice Sets the delegatee which will receive the voting weight of the caller's tokens staked in the fixed LST
  /// by specifying the deposit identifier associated with that delegatee.
  /// @param _newDepositId The identifier of a deposit which must be one owned by the rebasing LST. Underlying tokens
  /// staked in the fixed LST will be moved into this deposit.
  function updateDeposit(Staker.DepositIdentifier _newDepositId) public virtual {
    _updateDeposit(msg.sender, _newDepositId);
  }

  /// @notice Stake tokens and receive fixed balance LST tokens directly.
  /// @param _stakeTokens The number of governance tokens that will be staked.
  /// @return _fixedTokens The number of fixed balance LST tokens received upon staking. These tokens are *not*
  /// exchanged 1:1 with the stake tokens.
  /// @dev The caller must approve the Fixed LST contract to transfer at least the number of stake tokens being
  /// staked before calling this method.
  function stake(uint256 _stakeTokens) public virtual returns (uint256) {
    return _stake(msg.sender, _stakeTokens);
  }

  /// @notice Convert existing rebasing LST tokens to fixed balance LST tokens.
  /// @param _lstTokens The number of rebasing LST tokens that will be converted to fixed balance LST tokens.
  /// @return _fixedTokens The number of fixed balance LST tokens received upon fixing. These tokens are *not*
  /// exchanged 1:1 with the stake tokens.
  function convertToFixed(uint256 _lstTokens) external virtual returns (uint256) {
    return _convertToFixed(msg.sender, _lstTokens);
  }

  /// @notice Move fixed LST tokens held by the caller to another account.
  /// @param _to The address that will receive the transferred tokens.
  /// @param _fixedTokens The number of tokens to send.
  /// @return Whether the transfer was successful or not.
  /// @dev This method will always return true. It reverts in conditions where the transfer was not successful.
  function transfer(address _to, uint256 _fixedTokens) external virtual returns (bool) {
    _transfer(msg.sender, _to, _fixedTokens);
    return true;
  }

  /// @notice Move fixed LST tokens from one account to another, where the sender has provided an allowance to move
  /// tokens to the caller.
  /// @param _from The address that will send the transferred tokens.
  /// @param _to The address that will receive the transferred tokens.
  /// @param _fixedTokens The number of tokens to transfer.
  /// @return Whether the transfer was successful or not.
  /// @dev This method will always return true. It reverts in conditions where the transfer was not successful.
  function transferFrom(address _from, address _to, uint256 _fixedTokens) external virtual returns (bool) {
    _checkAndUpdateAllowance(_from, _fixedTokens);
    _transfer(_from, _to, _fixedTokens);
    return true;
  }

  /// @notice Convert fixed LST tokens to rebasing LST tokens.
  /// @param _fixedTokens The number of fixed LST tokens to convert.
  /// @return _lstTokens The number of rebasing LST tokens received.
  function convertToRebasing(uint256 _fixedTokens) external virtual returns (uint256) {
    return _convertToRebasing(msg.sender, _fixedTokens);
  }

  /// @notice Unstake fixed LST tokens and receive underlying staked tokens back. If a withdrawal delay is being
  /// enforced by the rebasing LST, tokens will be moved into the withdrawal gate.
  /// @param _fixedTokens The number of fixed LST tokens to unstake.
  /// @return _stakeTokens The number of underlying governance tokens received in exchange.
  function unstake(uint256 _fixedTokens) external virtual returns (uint256 _stakeTokens) {
    return _unstake(msg.sender, _fixedTokens);
  }

  /// @notice Allow a depositor to change the address they are delegating their staked tokens.
  /// @param _delegatee The address where voting is delegated.
  /// @dev This operation can be completed in a more gas efficient manner by calling `updateDeposit` with the depositId
  /// of the user's chosen delegatee, assuming it has already been initialized. This method is included primarily for
  /// partial compatibility with the `IVotes` interface.
  function delegate(address _delegatee) public virtual {
    Staker.DepositIdentifier _depositId = LST.fetchOrInitializeDepositForDelegatee(_delegatee);
    updateDeposit(_depositId);
  }

  /// @notice Save rebasing LST tokens that were mistakenly sent to the fixed holder alias address. Each fixed LST
  /// holder has an alias in the rebasing LST contract that manages the fixed holder's position. This alias is purely
  /// an implementation detail of the system, and not meant to be interacted with my regular users in anyway. However,
  /// if the holder of the rebasing LST token mistakenly sends tokens to a fixed LST alias address, this method allows
  /// the receiver of those tokens to reclaim them as part of their balance here in the LST.
  /// @return _fixedTokens The number of fixed LST tokens rescued by reclaiming rebasing LST tokens sent the caller's
  /// alias address.
  function rescue() external virtual returns (uint256 _fixedTokens) {
    return _rescue(msg.sender);
  }

  /// @notice Grant an allowance to the spender to transfer up to a certain amount of fixed LST tokens on behalf of the
  /// message sender.
  /// @param _spender The address which is granted the allowance to transfer from the message sender.
  /// @param _amount The total amount of the message sender's fixed LST tokens that the spender will be permitted to
  /// transfer.
  function approve(address _spender, uint256 _amount) external virtual returns (bool) {
    allowance[msg.sender][_spender] = _amount;
    emit Approval(msg.sender, _spender, _amount);
    return true;
  }

  /// @notice Grant an allowance to the spender to transfer up to a certain amount of fixed LST tokens on behalf of a
  /// user who has signed a message testifying to their intent to grant this allowance.
  /// @param _owner The account which is granting the allowance.
  /// @param _spender The address which is granted the allowance to transfer from the holder.
  /// @param _value The total amount of fixed LST tokens the spender will be permitted to transfer from the holder.
  /// @param _deadline The timestamp after which the signature should expire.
  /// @param _v ECDSA signature component: Parity of the `y` coordinate of point `R`
  /// @param _r ECDSA signature component: x-coordinate of `R`
  /// @param _s ECDSA signature component: `s` value of the signature
  function permit(address _owner, address _spender, uint256 _value, uint256 _deadline, uint8 _v, bytes32 _r, bytes32 _s)
    external
    virtual
  {
    if (block.timestamp > _deadline) {
      revert FixedGovLst__SignatureExpired();
    }

    bytes32 _structHash;
    // Unchecked because the only math done is incrementing
    // the owner's nonce which cannot realistically overflow.
    unchecked {
      _structHash = keccak256(abi.encode(PERMIT_TYPEHASH, _owner, _spender, _value, _useNonce(_owner), _deadline));
    }

    bytes32 _hash = _hashTypedDataV4(_structHash);

    address _recoveredAddress = ecrecover(_hash, _v, _r, _s);

    if (_recoveredAddress == address(0) || _recoveredAddress != _owner) {
      revert FixedGovLst__InvalidSignature();
    }

    allowance[_recoveredAddress][_spender] = _value;

    emit Approval(_owner, _spender, _value);
  }

  /// @notice Internal convenience method which performs transfer operations.
  /// @dev This method must only be called after proper authorization has been completed.
  /// @dev See public transfer methods for additional documentation.
  function _transfer(address _from, address _to, uint256 _fixedTokens) internal virtual {
    if (balanceOf(_from) < _fixedTokens) {
      revert FixedGovLst__InsufficientBalance();
    }

    (uint256 _senderShares, uint256 _receiverShares) = LST.transferFixed(_from, _to, _scaleUp(_fixedTokens));
    shareBalances[_from] -= _senderShares;
    shareBalances[_to] += _receiverShares;

    emit IERC20.Transfer(_from, _to, _fixedTokens);
  }

  /// @notice Internal helper method for updating the deposit identifier associated with a holder's account.
  /// @dev The deposit identifier determines which delegatee receives the voting weight of the holder's staked tokens.
  /// @param _newDepositId The identifier of a deposit which must be one owned by the rebasing LST. Underlying tokens
  /// staked in the fixed LST will be moved into this deposit.
  function _updateDeposit(address _account, Staker.DepositIdentifier _newDepositId) internal virtual {
    Staker.DepositIdentifier _oldDepositId = LST.updateFixedDeposit(_account, _newDepositId);
    emit DepositUpdated(_account, _oldDepositId, _newDepositId);
  }

  /// @notice Internal convenience method which performs the stake operation.
  /// @param _account The account to perform the stake action.
  /// @param _stakeTokens The amount of governance tokens to stake.
  /// @return The number of fixed tokens after staking.
  function _stake(address _account, uint256 _stakeTokens) internal virtual returns (uint256) {
    // Send the stake tokens to the LST.
    STAKE_TOKEN.safeTransferFrom(_account, address(LST), _stakeTokens);
    uint256 _shares = LST.stakeAndConvertToFixed(_account, _stakeTokens);
    shareBalances[_account] += _shares;
    totalShares += _shares;
    uint256 _fixedTokens = _scaleDown(_shares);
    emit IERC20.Transfer(address(0), _account, _fixedTokens);
    emit Fixed(_account, _stakeTokens);
    return _fixedTokens;
  }

  /// @notice Internal convenience method which performs the unstake operation.
  /// @param _account The account to perform the unstake action.
  /// @param _amount The amount of fixed tokens to unstake.
  /// @return The number of governance tokens after unstaking.
  function _unstake(address _account, uint256 _amount) internal virtual returns (uint256) {
    uint256 _shares = _scaleUp(_amount);
    // revert on overflow prevents unfixing more than balance
    shareBalances[_account] -= _shares;
    totalShares -= _shares;
    emit IERC20.Transfer(_account, address(0), _amount);
    uint256 _stakeTokens = LST.convertToRebasingAndUnstake(_account, _shares);
    emit Unfixed(_account, _stakeTokens);
    return _stakeTokens;
  }

  /// @notice Internal convenience method which performs the convert to fixed tokens operation.
  /// @param _account The account to perform the conversion action.
  /// @param _lstTokens The amount of rebasing tokens to convert.
  /// @return The number of fixed tokens.
  function _convertToFixed(address _account, uint256 _lstTokens) internal virtual returns (uint256) {
    uint256 _shares = LST.convertToFixed(_account, _lstTokens);
    shareBalances[_account] += _shares;
    totalShares += _shares;
    uint256 _fixedTokens = _scaleDown(_shares);
    emit IERC20.Transfer(address(0), _account, _fixedTokens);
    emit Fixed(_account, _lstTokens);
    return _fixedTokens;
  }

  /// @notice Internal convenience method which performs the convert to rebasing tokens operation.
  /// @param _account The account to perform the conversion action.
  /// @param _fixedTokens The amount of rebasing tokens to convert.
  /// @return The number of rebasing tokens.
  function _convertToRebasing(address _account, uint256 _fixedTokens) internal virtual returns (uint256) {
    uint256 _shares = _scaleUp(_fixedTokens);
    // revert on overflow prevents unfixing more than balance
    shareBalances[_account] -= _shares;
    totalShares -= _shares;
    emit IERC20.Transfer(_account, address(0), _fixedTokens);
    uint256 _lstTokens = LST.convertToRebasing(_account, _shares);
    emit Unfixed(_account, _lstTokens);
    return _lstTokens;
  }

  /// @notice Internal convenience method which performs the rescue operation.
  /// @param _account The account to perform the rescue action.
  /// @return The number of fixed tokens rescued.
  function _rescue(address _account) internal virtual returns (uint256) {
    // Shares not accounted for inside this Fixed LST accounting system are the ones to rescue.
    uint256 _sharesToRescue = LST.sharesOf(_account.fixedAlias()) - shareBalances[_account];

    // We intentionally scale down then scale up. The method is not intended for reclaiming dust below
    // the precision of the Fixed LST, but only for tokens accidentally sent to the alias address inside
    // the Rebasing LST contract.
    uint256 _fixedTokens = _scaleDown(_sharesToRescue);
    _sharesToRescue = _scaleUp(_fixedTokens);

    shareBalances[_account] += _sharesToRescue;
    totalShares += _sharesToRescue;
    emit IERC20.Transfer(address(0), _account, _fixedTokens);
    uint256 _stakeTokens = LST.stakeForShares(_sharesToRescue);
    emit Rescued(_account, _stakeTokens);
    return _fixedTokens;
  }

  /// @notice Internal helper that updates the allowance of the from address for the message sender, and reverts if the
  /// message sender does not have sufficient allowance.
  /// @param _from The address for which the message sender's allowance should be checked & updated.
  /// @param _fixedTokens The amount of the allowance to check and decrement.
  function _checkAndUpdateAllowance(address _from, uint256 _fixedTokens) internal virtual {
    uint256 allowed = allowance[_from][msg.sender];
    if (allowed != type(uint256).max) {
      allowance[_from][msg.sender] = allowed - _fixedTokens;
    }
  }

  /// @notice Internal helper that converts fixed LST tokens up to rebasing LST shares.
  /// @param _fixedTokens The number of fixed LST tokens.
  /// @return _lstShares The number of LST shares.
  function _scaleUp(uint256 _fixedTokens) internal view virtual returns (uint256 _lstShares) {
    _lstShares = _fixedTokens * SHARE_SCALE_FACTOR;
  }

  /// @notice Internal helper that converts rebasing LST shares down to fixed LST tokens
  /// @param _lstShares The number of LST shares.
  /// @return _fixedTokens The number of fixed LST tokens.
  function _scaleDown(uint256 _lstShares) internal view virtual returns (uint256 _fixedTokens) {
    _fixedTokens = _lstShares / SHARE_SCALE_FACTOR;
  }
}

File 3 of 33 : FixedGovLstPermitAndStake.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import {FixedGovLst} from "../FixedGovLst.sol";
import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";

/// @title FixedGovLstPermitAndStake
/// @author [ScopeLift](https://scopelift.co)
/// @notice This contract extension adds permit-based staking functionality to the FixedGovLst base contract,
/// allowing token approvals to happen via signatures rather than requiring a separate transaction.
/// The permit functionality is used in conjunction with the stake operation, improving UX by
/// enabling users to approve and stake tokens in a single transaction. Note that this extension
/// requires the stake token to support EIP-2612 permit functionality.
abstract contract FixedGovLstPermitAndStake is FixedGovLst {
  /// @notice Stake tokens to receive fixed liquid stake tokens. Before the staking operation occurs, a signature is
  /// passed to the token contract's permit method to spend the would-be staked amount of the token.
  /// @param _amount The quantity of fixed tokens that will be staked.
  /// @param _deadline The timestamp after which the signature should expire.
  /// @param _v ECDSA signature component: Parity of the `y` coordinate of point `R`
  /// @param _r ECDSA signature component: x-coordinate of `R`
  /// @param _s ECDSA signature component: `s` value of the signature
  /// @return The number of fixed tokens after staking.
  function permitAndStake(uint256 _amount, uint256 _deadline, uint8 _v, bytes32 _r, bytes32 _s)
    public
    returns (uint256)
  {
    try IERC20Permit(address(STAKE_TOKEN)).permit(msg.sender, address(this), _amount, _deadline, _v, _r, _s) {} catch {}
    return stake(_amount);
  }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import {FixedGovLst} from "../FixedGovLst.sol";
import {Staker} from "staker/Staker.sol";
import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";

/// @title FixedGovLstOnBehalf
/// @author [ScopeLift](https://scopelift.co)
/// @notice This contract extension adds signature execution functionality to the FixedGovLstOnBehalf
/// base contract, allowing key operations to be executed via signatures rather than requiring the
/// owner or claimer to execute transactions directly. This includes staking, unstaking, converting to and from the
/// rebasing LST, and altering the holder's delegatee via updating the LST owned deposit in which their tokens are
/// held. Each operation requires a unique signature that is validated against the appropriate signer before
/// execution.
abstract contract FixedGovLstOnBehalf is FixedGovLst {
  /// @notice Type hash used when encoding data for `updateDepositOnBehalf` calls.
  bytes32 public constant UPDATE_DEPOSIT_TYPEHASH =
    keccak256("UpdateDeposit(address account,uint256 newDepositId,uint256 nonce,uint256 deadline)");

  /// @notice Type hash used when encoding data for `stakeOnBehalf` calls.
  bytes32 public constant STAKE_TYPEHASH =
    keccak256("Stake(address account,uint256 amount,uint256 nonce,uint256 deadline)");

  /// @notice Type hash used when encoding data for `convertToFixedOnBehalf` calls.
  bytes32 public constant CONVERT_TO_FIXED_TYPEHASH =
    keccak256("ConvertToFixed(address account,uint256 amount,uint256 nonce,uint256 deadline)");

  /// @notice Type hash used when encoding data for `convertToRebasingOnBehalf` calls.
  bytes32 public constant CONVERT_TO_REBASING_TYPEHASH =
    keccak256("ConvertToRebasing(address account,uint256 amount,uint256 nonce,uint256 deadline)");

  /// @notice Type hash used when encoding data for `unstakeOnBehalf` calls.
  bytes32 public constant UNSTAKE_TYPEHASH =
    keccak256("Unstake(address account,uint256 amount,uint256 nonce,uint256 deadline)");

  /// @notice Type hash used when encoding data for `rescueOnBehalf` calls.
  bytes32 public constant RESCUE_TYPEHASH = keccak256("Rescue(address account,uint256 nonce,uint256 deadline)");

  /// @notice Updates the deposit identifier for an account using a signed message for authorization. The deposit
  /// identifier determines which delegatee receives the voting weight of the account's staked tokens.
  /// @param _account The address of the account whose deposit identifier is being updated.
  /// @param _newDepositId The new deposit identifier to associate with the account. Must be a deposit owned by the
  /// rebasing LST. The underlying tokens staked in the fixed LST will be moved into this deposit.
  /// @param _nonce The nonce being consumed by this operation to prevent replay attacks.
  /// @param _deadline The timestamp after which the signature should expire.
  /// @param _signature The signed message authorizing this deposit update, signed by the account.
  function updateDepositOnBehalf(
    address _account,
    Staker.DepositIdentifier _newDepositId,
    uint256 _nonce,
    uint256 _deadline,
    bytes memory _signature
  ) external {
    _validateSignature(
      _account, Staker.DepositIdentifier.unwrap(_newDepositId), _nonce, _deadline, _signature, UPDATE_DEPOSIT_TYPEHASH
    );
    _updateDeposit(_account, _newDepositId);
  }

  /// @notice Stake tokens to receive fixed liquid stake tokens on behalf of a user, using a signature to validate the
  /// user's intent. The staking address must pre-approve the LST contract to spend at least the would-be amount
  /// of tokens.
  /// @param _account The address on behalf of whom the staking is being performed.
  /// @param _amount The quantity of tokens that will be staked.
  /// @param _nonce The nonce being consumed by this operation.
  /// @param _deadline The timestamp after which the signature should expire.
  /// @param _signature Signature of the user authorizing this stake.
  /// @dev The increase in the holder's balance after staking may be slightly less than the amount staked due to
  /// rounding.
  /// @return The amount of fixed tokens created after staking.
  function stakeOnBehalf(address _account, uint256 _amount, uint256 _nonce, uint256 _deadline, bytes memory _signature)
    external
    returns (uint256)
  {
    _validateSignature(_account, _amount, _nonce, _deadline, _signature, STAKE_TYPEHASH);
    return _stake(_account, _amount);
  }

  /// @notice Destroy liquid staked tokens, to receive the underlying token in exchange, on behalf of a user. Use a
  /// signature to validate the user's  intent. Tokens are removed first from the default deposit, if any are present,
  /// then from holder's specified deposit if any are needed.
  /// @param _account The address on behalf of whom the unstaking is being performed.
  /// @param _amount The amount of tokens to unstake.
  /// @param _nonce The nonce being consumed by this operation.
  /// @param _deadline The timestamp after which the signature should expire.
  /// @param _signature Signature of the user authorizing this stake.
  /// @return The amount of stake tokens created after unstaking.
  function unstakeOnBehalf(
    address _account,
    uint256 _amount,
    uint256 _nonce,
    uint256 _deadline,
    bytes memory _signature
  ) external returns (uint256) {
    _validateSignature(_account, _amount, _nonce, _deadline, _signature, UNSTAKE_TYPEHASH);
    return _unstake(_account, _amount);
  }

  /// @notice Convert existing rebasing LST tokens to fixed balance LST tokens on behalf of an account.
  /// @param _account The address on behalf of whom the conversion is being performed.
  /// @param _amount The amount of rebasing LST tokens to convert.
  /// @param _nonce The nonce being consumed by this operation.
  /// @param _deadline The timestamp after which the signature should expire.
  /// @param _signature Signature of the user authorizing this stake.
  /// @return The amount of fixed tokens.
  function convertToFixedOnBehalf(
    address _account,
    uint256 _amount,
    uint256 _nonce,
    uint256 _deadline,
    bytes memory _signature
  ) external returns (uint256) {
    _validateSignature(_account, _amount, _nonce, _deadline, _signature, CONVERT_TO_FIXED_TYPEHASH);
    return _convertToFixed(_account, _amount);
  }

  /// @notice Convert fixed LST tokens to rebasing LST tokens on behalf of an account.
  /// @param _account The address on behalf of whom the conversion is being performed.
  /// @param _amount The amount of fixed LST tokens to convert.
  /// @param _nonce The nonce being consumed by this operation.
  /// @param _deadline The timestamp after which the signature should expire.
  /// @param _signature Signature of the user authorizing this stake.
  /// @return The amount of rebasing tokens.
  function convertToRebasingOnBehalf(
    address _account,
    uint256 _amount,
    uint256 _nonce,
    uint256 _deadline,
    bytes memory _signature
  ) external returns (uint256) {
    _validateSignature(_account, _amount, _nonce, _deadline, _signature, CONVERT_TO_REBASING_TYPEHASH);
    return _convertToRebasing(_account, _amount);
  }

  /// @notice Save rebasing LST tokens that were mistakenly sent to the fixed holder alias address on behalf of an
  /// account.
  /// @param _account The address on behalf of whom the rescue is being performed.
  /// @param _nonce The nonce being consumed by this operation.
  /// @param _deadline The timestamp after which the signature should expire.
  /// @param _signature Signature of the user authorizing this stake.
  /// @return The amount of fixed tokens rescued.
  function rescueOnBehalf(address _account, uint256 _nonce, uint256 _deadline, bytes memory _signature)
    external
    returns (uint256)
  {
    _validateSignature(_account, _nonce, _deadline, _signature, RESCUE_TYPEHASH);
    return _rescue(_account);
  }

  /// @notice Internal helper method which reverts with FixedGovLst__SignatureExpired if the signature
  /// is invalid.
  /// @param _account The address of the signer.
  /// @param _amount The amount of tokens involved in this operation.
  /// @param _nonce The nonce being consumed by this operation.
  /// @param _deadline The timestamp after which the signature should expire.
  /// @param _signature Signature of the user authorizing this stake.
  /// @param _typeHash The typehash being signed over for this operation.
  function _validateSignature(
    address _account,
    uint256 _amount,
    uint256 _nonce,
    uint256 _deadline,
    bytes memory _signature,
    bytes32 _typeHash
  ) internal {
    _useCheckedNonce(_account, _nonce);
    if (block.timestamp > _deadline) {
      revert FixedGovLst__SignatureExpired();
    }
    bytes32 _structHash = keccak256(abi.encode(_typeHash, _account, _amount, _nonce, _deadline));
    bytes32 _hash = _hashTypedDataV4(_structHash);
    if (!SignatureChecker.isValidSignatureNow(_account, _hash, _signature)) {
      revert FixedGovLst__InvalidSignature();
    }
  }

  /// @notice Internal helper method which reverts with FixedGovLst__SignatureExpired if the signature
  /// is invalid.
  /// @param _account The address of the signer.
  /// @param _nonce The nonce being consumed by this operation.
  /// @param _deadline The timestamp after which the signature should expire.
  /// @param _signature Signature of the user authorizing this stake.
  /// @param _typeHash The typehash being signed over for this operation.
  function _validateSignature(
    address _account,
    uint256 _nonce,
    uint256 _deadline,
    bytes memory _signature,
    bytes32 _typeHash
  ) internal {
    _useCheckedNonce(_account, _nonce);
    if (block.timestamp > _deadline) {
      revert FixedGovLst__SignatureExpired();
    }
    bytes32 _structHash = keccak256(abi.encode(_typeHash, _account, _nonce, _deadline));
    bytes32 _hash = _hashTypedDataV4(_structHash);
    if (!SignatureChecker.isValidSignatureNow(_account, _hash, _signature)) {
      revert FixedGovLst__InvalidSignature();
    }
  }
}

File 5 of 33 : GovLst.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol";
import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import {Nonces} from "@openzeppelin/contracts/utils/Nonces.sol";
import {Multicall} from "@openzeppelin/contracts/utils/Multicall.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {Staker} from "staker/Staker.sol";
import {WithdrawGate} from "./WithdrawGate.sol";
import {FixedGovLst} from "./FixedGovLst.sol";
import {FixedLstAddressAlias} from "./FixedLstAddressAlias.sol";

/// @title GovLst
/// @author [ScopeLift](https://scopelift.co)
/// @notice A liquid staking token implemented on top of Staker. Users can deposit a governance token and receive
/// a liquid staked governance token in exchange. Holders can specify a delegatee to which staked tokens' voting weight
/// will be delegated. 1 staked token is equivalent to 1 underlying governance token. As rewards are distributed,
/// holders' balances automatically increase to reflect their share of the rewards earned. Reward balances are delegated
/// to a default delegatee set by the token owner. Holders can consolidate their voting weight back to their chosen
/// delegate. Holders who don't specify a custom delegatee also have their stake's voting weight assigned to the default
/// delegatee.
///
/// To enable delegation functionality, the LST must manage an individual stake deposit for each delegatee,
/// including one for the default delegatee. As tokens are staked, unstaked, or transferred, the LST must move tokens
/// between these deposits to reflect the changing state. Because a holder balance is a dynamic calculation based on
/// its share of the total staked supply, the balance is subject to truncation. Care must be taken to ensure all
/// deposits remain solvent. Where a deposit might be left short due to truncation, we aim to accumulate these
/// shortfalls in the default deposit, which can be subsidized to remain solvent.
/// @dev Not all tokens are compatible with GovLST.
abstract contract GovLst is IERC20, IERC20Metadata, IERC20Permit, Ownable, Multicall, EIP712, Nonces {
  using FixedLstAddressAlias for address;
  using SafeCast for uint256;
  using SafeERC20 for IERC20;

  /// @notice Emitted when the LST owner updates the payout amount required for the MEV reward game in
  /// `claimAndDistributeReward`.
  event PayoutAmountSet(uint256 oldPayoutAmount, uint256 newPayoutAmount);

  /// @notice Emitted when the LST owner updates the reward parameters.
  event RewardParametersSet(uint256 payoutAmount, uint256 feeBips, address feeCollector);

  /// @notice Emitted when the default delegatee is updated by the owner or guardian.
  event DefaultDelegateeSet(address oldDelegatee, address newDelegatee);

  /// @notice Emitted when the delegatee guardian is updated by the owner or guardian itself.
  event DelegateeGuardianSet(address oldDelegatee, address newDelegatee);

  /// @notice Emitted when the minimum qualifying earning power bips is set.
  event MinQualifyingEarningPowerBipsSet(
    uint256 _oldMinQualifyingEarningPowerBips, uint256 _newMinQualifyingEarningPowerBips
  );

  /// @notice Emitted when a stake deposit is initialized for a new delegatee.
  event DepositInitialized(address indexed delegatee, Staker.DepositIdentifier depositId);

  /// @notice Emitted when a user updates their stake deposit, moving their staked tokens accordingly.
  /// @dev This event must be combined with the `DepositUpdated` event on the FixedGovLst for an accurate picture all
  /// deposit ids for a given holder.
  event DepositUpdated(
    address indexed holder, Staker.DepositIdentifier oldDepositId, Staker.DepositIdentifier newDepositId
  );

  /// @notice Emitted when a user stakes tokens in exchange for liquid staked tokens.
  event Staked(address indexed account, uint256 amount);

  /// @notice Emitted when a user exchanges their liquid staked tokens for the underlying staked token.
  event Unstaked(address indexed account, uint256 amount);

  /// @notice Emitted when a deposit delegatee is overridden to the default delegatee.
  event OverrideEnacted(Staker.DepositIdentifier depositId);

  /// @notice Emitted when an overridden deposit delegatee is set back to the original delegatee.
  event OverrideRevoked(Staker.DepositIdentifier depositId);

  /// @notice Emitted when an overridden deposit is migrated to a new default delegatee.
  event OverrideMigrated(Staker.DepositIdentifier depositId, address oldDelegatee, address newDelegatee);

  ///@notice Emitted when a reward is distributed by an MEV searcher who claims the LST's stake rewards in exchange
  /// for providing the payout amount of the stake token to the LST.
  event RewardDistributed(
    address indexed claimer,
    address indexed recipient,
    uint256 rewardsClaimed,
    uint256 payoutAmount,
    uint256 feeAmount,
    address feeCollector
  );

  /// @notice Struct to encapsulate reward-related parameters.
  struct RewardParameters {
    /// @notice The amount of stake token that an MEV searcher must provide in order to earn the right to claim the
    /// stake rewards earned by the LST. Can be set by the LST owner.
    uint80 payoutAmount;
    /// @notice The amount of stake token issued to the fee collector, expressed in basis points.
    /// @dev Fee in basis points (1 bips = 0.01%)
    uint16 feeBips;
    /// @notice The address that receives the fees when rewards are distributed.
    address feeCollector;
  }

  /// @notice Emitted when a user stakes and attributes their staking action to a referrer address.
  event StakedWithAttribution(Staker.DepositIdentifier _depositId, uint256 _amount, address indexed _referrer);

  /// @notice Emitted when someone irrevocably adds stake tokens to a deposit without receiving liquid tokens.
  event DepositSubsidized(Staker.DepositIdentifier indexed depositId, uint256 amount);

  /// @notice Thrown when an operation to change the default delegatee or its guardian is attempted by an account that
  /// does not have permission to alter it.
  error GovLst__Unauthorized();

  /// @notice Thrown when an operation is not possible because the holder's balance is insufficient.
  error GovLst__InsufficientBalance();

  /// @notice Thrown when a caller (likely an MEV searcher) would receive an insufficient payout in
  /// `claimAndDistributeReward`.
  error GovLst__InsufficientRewards();

  /// @notice Thrown when the LST owner attempts to set invalid fee parameters.
  error GovLst__InvalidFeeParameters();

  /// @notice Thrown by signature-based "onBehalf" methods when a signature is invalid.
  error GovLst__InvalidSignature();

  /// @notice Thrown by signature-based "onBehalf" methods when a signature is past its expiry date.
  error GovLst__SignatureExpired();

  /// @notice Thrown when the fee bips exceed the maximum allowed value.
  error GovLst__FeeBipsExceedMaximum(uint16 feeBips, uint16 maxFeeBips);

  /// @notice Thrown when attempting to set the fee collector to the zero address.
  error GovLst__FeeCollectorCannotBeZeroAddress();

  /// @notice Thrown when attempting to improperly override a deposit's delegatee.
  error GovLst__InvalidOverride();

  /// @notice Thrown when attempting to update a parameter with an invalid value.
  error GovLst__InvalidParameter();

  /// @notice Thrown when a deposit does not have the required amount of earning power for a certain action to be taken.
  /// An example of this is an attempted override of a deposit that has an earning power above the minimum earning power
  /// threshold.
  error GovLst__EarningPowerNotQualified(uint256 earningPower, uint256 thresholdEarningPower);

  /// @notice Thrown when a holder tries to update their deposit to an invalid deposit.
  error GovLst__InvalidDeposit();

  /// @notice The Staker instance in which staked tokens will be deposited to earn rewards.
  Staker public immutable STAKER;

  /// @notice The governance token used by the staking system.
  /// @dev Tokens greater than 18 decimals have a higher potential for overflow issues. Tokens with transfer fees,
  /// ERC-777 logic, or unorthodox logic are likely incompatible with this system. Only compliant ERC20
  /// tokens should be used as the stake token.
  IERC20 public immutable STAKE_TOKEN;

  /// @notice The token distributed as rewards by the staking instance.
  /// @dev Tokens greater than 18 decimals have a higher potential for overflow issues. Tokens with transfer fees,
  /// ERC-777 logic or, unorthodox logic are likely incompatible with this system. Only compliant ERC20
  /// tokens should be used as the reward token.
  IERC20 public immutable REWARD_TOKEN;

  /// @notice A coupled contract used by the LST to enforce an optional delay when withdrawing staked tokens from the
  /// LST. Can be used to prevent users from frontrunning rewards by staking and withdrawing repeatedly at opportune
  /// times. Said strategy would likely be unprofitable due to gas fees, but we eliminate the possibility via a delay.
  WithdrawGate public immutable WITHDRAW_GATE;

  /// @notice A coupled ERC20 contract that represents a fixed balance version of the LST. Whereas this  LST has
  /// dynamic, rebasing balances, the Fixed LST is deployed alongside of it but has balances that remain fixed. To
  /// achieve this, the Fixed LST contract is privileged to make special calls on behalf if its holders, allowing the
  /// Fixed LST to use the same accounting system as this rebasing LST.
  FixedGovLst public immutable FIXED_LST;

  /// @notice The deposit identifier of the default deposit.
  Staker.DepositIdentifier public immutable DEFAULT_DEPOSIT_ID;

  /// @notice Scale factor applied to the stake token before converting it to shares, which are tracked internally and
  /// used to
  /// calculate holders' balances dynamically as rewards are accumulated.
  uint256 public constant SHARE_SCALE_FACTOR = 1e10;

  /// @notice Data structure for global totals for the LST.
  /// @param supply The total staked tokens in the whole system, which by definition also represents the total supply
  /// of the LST token itself.
  /// @param shares The total shares that have been issued to all token holders, representing their proportional claim
  /// on the total supply.
  /// @dev The data types chosen for each parameter are meant to enable the data to pack into a single slot, while
  /// ensuring that real values occurring in the system are safe from overflow.
  struct Totals {
    uint96 supply;
    uint160 shares;
  }

  /// @notice Data structure for data pertaining to a given LST holder.
  /// @param depositId The staking system deposit identifier corresponding to the holder's delegatee of choice.
  /// @param balanceCheckpoint The portion of the holder's balance that is currently delegated to the delegatee of
  /// their choosing. LST tokens are assigned to this delegatee when a user stakes or receives tokens via transfer.
  /// When rewards are distributed, they accrue to the default delegatee unless the holder chooses to consolidate them.
  /// Holders who leave their delegatee set to the default have a balance checkpoint of zero by definition.
  /// @param shares The number of shares held by this holder, used to calculate the holder's balance dynamically, based
  /// on their proportion of the total shares, and thus the total staked supply.
  /// @dev The data types chosen for each parameter are meant to enable the data to pack into a single slot while still
  /// being safe from overflow for real values that can occur in the system.
  struct HolderState {
    uint32 depositId;
    uint96 balanceCheckpoint;
    uint128 shares;
  }

  /// @notice Data structure for deploying the `GovLst`.
  /// @param _fixedLstName The name for the fixed liquid stake token.
  /// @param _fixedLstSymbol The symbol for the fixed liquid stake token.
  /// @param _rebasingLstName The name for the rebasing liquid stake token.
  /// @param _rebasingLstSymbol The symbol for the rebasing liquid stake token.
  /// @param _staker The staker deployment where tokens will be staked.
  /// @param _initialDefaultDelegatee The initial delegatee to which the default deposit will be delegated.
  /// @param _initialOwner The address of the initial LST owner.
  /// @param _initialPayoutAmount The initial amount that must be provided to win the MEV race and claim the LST's
  /// stake rewards.
  /// @param _stakeToBurn The stake amount to burn in order to avoid divide by 0 errors. A reasonable value for this
  /// would be 1e15.
  /// @param _minQualifyingEarningPowerBips The minimum qualifying earning power amount in BIPs (1/10,000) for a deposit
  /// to not be overridden.
  struct ConstructorParams {
    string fixedLstName;
    string fixedLstSymbol;
    string rebasingLstName;
    string rebasingLstSymbol;
    string version;
    Staker staker;
    address initialDefaultDelegatee;
    address initialOwner;
    uint80 initialPayoutAmount;
    address initialDelegateeGuardian;
    uint256 stakeToBurn;
    uint256 minQualifyingEarningPowerBips;
  }

  /// @notice Type hash used when encoding data for `permit` calls.
  bytes32 public constant PERMIT_TYPEHASH =
    keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");

  /// @notice The name of the LST token.
  string private NAME;

  /// @notice The symbol of the LST token.
  string private SYMBOL;

  /// @notice The denominator for a basis point which is 1/100 of a percentage point.
  uint16 public constant BIPS = 1e4;

  /// @notice Maximum allowable fee in basis points (20%).
  uint16 public constant MAX_FEE_BIPS = 2000;

  /// @notice Maximum BIPs value for minimum qualifying earning power BIPs.
  uint256 public immutable MINIMUM_QUALIFYING_EARNING_POWER_BIPS_CAP = 20_000;

  /// @notice The global total supply and total shares for the LST.
  Totals internal totals;

  /// @notice The delegatee to whom the voting weight in the default deposit is delegated. Can be set by the LST owner,
  /// or the delegatee guardian. Once the guardian sets it, only the guardian can change it moving forward. The owner
  /// is no longer able to update it.
  address public defaultDelegatee;

  /// @notice Address which has the right to update the default delegatee assigned to the default deposit. Once this
  /// address takes an action, it can no longer be changed or overridden by the LST owner.
  address public delegateeGuardian;

  /// @notice One way switch that flips to true when the delegatee guardian takes its first action. Once set to true,
  /// the default delegatee and the guardian address can only be changed by the guardian itself.
  bool public isGuardianControlled;

  /// @notice The minimum qualifying earning amount in bips (1/10,000).
  uint256 public minQualifyingEarningPowerBips;

  /// @notice Struct to store reward-related parameters.
  RewardParameters internal rewardParams;

  /// @notice Mapping of delegatee address to the delegate's GovLST-created Staker deposit identifier. The
  /// delegatee for a given deposit can not change. All LST holders who choose the same delegatee will have their
  /// tokens staked in the corresponding deposit. Each delegatee can only have a single deposit.
  mapping(address delegatee => Staker.DepositIdentifier depositId) internal storedDepositIdForDelegatee;

  /// @notice Mapping of holder addresses to the data pertaining to their holdings.
  mapping(address holder => HolderState state) private holderStates;

  /// @notice Mapping used to determine the amount of LST tokens the spender has been approved to transfer on the
  /// holder's behalf.
  mapping(address holder => mapping(address spender => uint256 amount)) public allowance;

  /// @notice A mapping used to determine if a deposit's delegatee has been overridden to the default delegatee.
  mapping(Staker.DepositIdentifier depositId => bool isOverridden) public isOverridden;

  constructor(ConstructorParams memory _params)
    Ownable(_params.initialOwner)
    EIP712(_params.rebasingLstName, _params.version)
  {
    STAKER = _params.staker;
    STAKE_TOKEN = IERC20(_params.staker.STAKE_TOKEN());
    REWARD_TOKEN = IERC20(_params.staker.REWARD_TOKEN());
    NAME = _EIP712Name();
    SYMBOL = _params.rebasingLstSymbol;

    _setDefaultDelegatee(_params.initialDefaultDelegatee);
    _setRewardParams(_params.initialPayoutAmount, 0, _params.initialOwner);
    _setDelegateeGuardian(_params.initialDelegateeGuardian);
    _setMinQualifyingEarningPowerBips(_params.minQualifyingEarningPowerBips);

    STAKE_TOKEN.approve(address(_params.staker), type(uint256).max);
    // Create initial deposit for default so other methods can assume it exists.
    DEFAULT_DEPOSIT_ID = STAKER.stake(0, _params.initialDefaultDelegatee);
    STAKE_TOKEN.safeTransferFrom(msg.sender, address(this), _params.stakeToBurn);
    _stake(address(this), _params.stakeToBurn);
    _emitStakedEvent(address(this), _params.stakeToBurn);
    _emitTransferEvent(address(0), address(this), _params.stakeToBurn);

    // Deploy the WithdrawGate
    WITHDRAW_GATE = new WithdrawGate(_params.initialOwner, address(this), address(STAKE_TOKEN), 0);
    FIXED_LST = _deployFixedGovLst(
      _params.fixedLstName, _params.fixedLstSymbol, _params.version, this, STAKE_TOKEN, SHARE_SCALE_FACTOR
    );
  }

  /// @notice The name of the liquid stake token.
  function name() external view virtual override returns (string memory) {
    return NAME;
  }

  /// @notice The symbol for the liquid stake token.
  function symbol() external view virtual override returns (string memory) {
    return SYMBOL;
  }

  /// @notice The decimal precision which the LST tokens stores its balances with.
  /// @dev Make sure this matches the Stake token's decimals as an incompatibility
  /// will create an unintuitive UX where 1 LST token does not equal 1 stake token.
  function decimals() external pure virtual override returns (uint8) {
    return 18;
  }

  /// @notice The EIP712 signing version of the contract.
  function version() external view virtual returns (string memory) {
    return _EIP712Version();
  }

  /// @notice The total amount of LST token supply, also equal to the total number of stake tokens in the system.
  function totalSupply() external view virtual returns (uint256) {
    return uint256(totals.supply);
  }

  /// @notice The total number of outstanding shares issued to LST token holders. Each shares represents a proportional
  /// claim on the LST's total supply. As rewards are distributed, each share becomes worth proportionally more.
  function totalShares() external view virtual returns (uint256) {
    return uint256(totals.shares);
  }

  /// @notice Returns the number of shares that are valued at a given amount of stake token. Note that shares have a
  /// scale factor of `SHARE_SCALE_FACTOR` applied to minimize precision loss due to truncation.
  /// @param _amount The quantity of stake token that will be converted to a number of shares.
  /// @return The quantity of shares that is worth the requested quantity of stake token.
  function sharesForStake(uint256 _amount) external view virtual returns (uint256) {
    Totals memory _totals = totals;
    return _calcSharesForStakeUp(_amount, _totals);
  }

  /// @notice Returns the quantity of stake tokens that a given number of shares is valued at. In other words,
  /// ownership of a given number of shares translates to a claim on the quantity of stake tokens returned.
  /// @param _shares The quantity of shares that will be converted to stake tokens.
  /// @return The quantity of stake tokens which backs the provided quantity of shares.
  function stakeForShares(uint256 _shares) public view virtual returns (uint256) {
    Totals memory _totals = totals;
    return _calcStakeForShares(_shares, _totals);
  }

  /// @notice The current balance of LST tokens owned by the holder. Unlike a standard ERC20, this amount is calculated
  /// dynamically based on the holder's shares and the total supply of the LST. As rewards are distributed, a holder's
  /// balance will increase, even if they take no actions. In certain circumstances, a holder's balance can also
  /// decrease by tiny amounts without any action taken by the holder. This is due to changes in the global number of
  /// shares and supply resulting in a slightly different balance calculation after rounding.
  function balanceOf(address _holder) external view virtual returns (uint256) {
    HolderState memory _holderState = holderStates[_holder];
    Totals memory _totals = totals;

    return _calcBalanceOf(_holderState, _totals);
  }

  /// @notice The number of shares a given holder owns. Unlike a holder's balance, shares are stored statically and do
  /// not change unless the user is subject to some action, such as staking, unstaking, or transferring. The user's
  /// balance is calculated based on their proportion of the total outstanding shares.
  function sharesOf(address _holder) external view virtual returns (uint256 _sharesOf) {
    _sharesOf = holderStates[_holder].shares;
  }

  /// @notice The portion of the holder's balance that is currently delegated to the delegatee of their
  /// choosing. When a user stakes or receives LST tokens via transfer, they are a assigned to their delegatee, and
  /// accounted for in the balance checkpoint. This means the tokens are held in the corresponding deposit, and the
  /// voting weight for these tokens is assigned to the holder's chosen delegatee. When rewards are distributed, they
  /// accrue to the default delegatee unless the holder chooses to consolidate them. Therefore, the difference between
  /// the user's live balance and their balance checkpoint represents the number of tokens the holder has claim to that
  /// are currently held in the default deposit. Holders who leave their delegatee set to the default have a balance
  /// checkpoint of zero by definition.
  function balanceCheckpoint(address _holder) external view virtual returns (uint256 _balanceCheckpoint) {
    _balanceCheckpoint = holderStates[_holder].balanceCheckpoint;
  }

  /// @notice The delegatee to whom a given holder's stake is currently delegated. This will be the delegatee to whom
  /// the user has chosen to assign their voting weight OR the default delegatee, if the user's deposit has been
  /// moved to the override state.
  /// @param _holder The holder in question.
  /// @return _delegatee The address to which this holder's voting weight is currently delegated.
  function delegateeForHolder(address _holder) public view virtual returns (address _delegatee) {
    HolderState memory _holderState = holderStates[_holder];
    (,,, _delegatee,,,) = STAKER.deposits(_calcDepositId(_holderState));
  }

  /// @notice The delegatee to whom a given holder's stake is currently delegated. This will be the delegatee to whom
  /// the user has chosen to assign their voting weight OR the default delegatee, if the user's deposit has been
  /// moved to the override state.
  /// @param _holder The holder in question.
  /// @return The address to which this holder's voting weight is currently delegated.
  /// @dev This method is included for partial compatibility with the `IVotes` interface. It returns the same data as
  /// the `delegateeForHolder` method.
  function delegates(address _holder) external view virtual returns (address) {
    return delegateeForHolder(_holder);
  }

  /// @notice The stake deposit identifier associated with a given delegatee address.
  /// @param _delegatee The delegatee in question.
  /// @return The deposit identifier of the deposit in question.
  function depositForDelegatee(address _delegatee) public view virtual returns (Staker.DepositIdentifier) {
    if (_delegatee == defaultDelegatee || _delegatee == address(0)) {
      return DEFAULT_DEPOSIT_ID;
    } else {
      return storedDepositIdForDelegatee[_delegatee];
    }
  }

  /// @notice Returns the stake deposit identifier a given LST holder address is currently assigned to. If the
  /// address has not set a deposit identifier, it returns the default deposit.
  function depositIdForHolder(address _holder) external view virtual returns (Staker.DepositIdentifier) {
    HolderState memory _holderState = holderStates[_holder];
    return _calcDepositId(_holderState);
  }

  /// @notice Returns the current fee amount based on feeBips and payoutAmount.
  function feeAmount() external view virtual returns (uint256) {
    return _calcFeeAmount(rewardParams);
  }

  /// @notice Returns the current fee collector address.
  function feeCollector() external view virtual returns (address) {
    return rewardParams.feeCollector;
  }

  /// @notice Returns the current payout amount.
  function payoutAmount() external view virtual returns (uint256) {
    return uint256(rewardParams.payoutAmount);
  }

  /// @notice Get the current nonce for an owner
  /// @dev This function explicitly overrides both Nonces and IERC20Permit to allow compatibility
  /// @param _owner The address of the owner
  /// @return The current nonce for the owner
  function nonces(address _owner) public view virtual override(Nonces, IERC20Permit) returns (uint256) {
    return Nonces.nonces(_owner);
  }

  /// @notice The domain separator used by this contract for all EIP712 signature based methods.
  function DOMAIN_SEPARATOR() external view virtual returns (bytes32) {
    return _domainSeparatorV4();
  }

  /// @notice Returns the deposit identifier managed by the LST for a given delegatee. If that deposit does not yet
  /// exist, it initializes it. A depositor can call this method if the deposit for their chosen delegatee has not been
  /// previously initialized.
  /// @param _delegatee The address of the delegatee.
  /// @return The deposit identifier of the existing, or newly created, stake deposit for this delegatee.
  function fetchOrInitializeDepositForDelegatee(address _delegatee) public virtual returns (Staker.DepositIdentifier) {
    Staker.DepositIdentifier _depositId = depositForDelegatee(_delegatee);

    if (Staker.DepositIdentifier.unwrap(_depositId) != 0) {
      return _depositId;
    }

    // Create a new deposit for this delegatee if one is not yet managed by the LST
    _depositId = STAKER.stake(0, _delegatee);
    storedDepositIdForDelegatee[_delegatee] = _depositId;
    emit DepositInitialized(_delegatee, _depositId);
    return _depositId;
  }

  /// @notice Sets the deposit to which the message sender is choosing to assign their staked tokens. By using offchain
  /// indexing to find the deposit identifier corresponding with the delegatee of their choice, LST holders can choose
  /// to which address they want to assign the voting weight of their staked tokens. Additional staked tokens, or
  /// tokens transferred to the holder, will be moved into this deposit. Tokens distributed as rewards will remain in
  /// the default deposit, however holders may consolidate their reward tokens back to their preferred delegatee by
  /// calling this method again, even with their existing deposit identifier.
  /// @param _newDepositId The stake deposit identifier to which this holder's staked tokens will be moved to and
  /// kept in henceforth.
  function updateDeposit(Staker.DepositIdentifier _newDepositId) public virtual {
    Staker.DepositIdentifier _oldDepositId = _updateDeposit(msg.sender, _newDepositId);
    _emitDepositUpdatedEvent(msg.sender, _oldDepositId, _newDepositId);
  }

  /// @notice Stake tokens to receive liquid stake tokens. The caller must pre-approve the LST contract to spend at
  /// least the would-be amount of tokens.
  /// @param _amount The quantity of tokens that will be staked.
  /// @dev The increase in the holder's balance after staking may be slightly less than the amount staked due to
  /// rounding.
  function stake(uint256 _amount) external virtual returns (uint256) {
    STAKE_TOKEN.safeTransferFrom(msg.sender, address(this), _amount);
    _emitStakedEvent(msg.sender, _amount);
    _emitTransferEvent(address(0), msg.sender, _amount);
    return _stake(msg.sender, _amount);
  }

  /// @notice Stake tokens to receive liquid stake tokens, while also declaring the address that is responsible for
  /// referring the holder to the LST. This can be, for example, the owner of the frontend client who allowed the
  /// holder to interact with the contracts onchain. The call must pre-approve the LST contract to spend at least the
  /// would-be amount of tokens.
  /// @param _amount The quantity of tokens that will be staked.
  /// @param _referrer The address the holder is declaring has referred them to the LST. It will be emitted in an
  /// attribution event, but not otherwise used.
  function stakeWithAttribution(uint256 _amount, address _referrer) external virtual returns (uint256) {
    Staker.DepositIdentifier _depositId = _calcDepositId(holderStates[msg.sender]);
    emit StakedWithAttribution(_depositId, _amount, _referrer);
    STAKE_TOKEN.safeTransferFrom(msg.sender, address(this), _amount);
    _emitStakedEvent(msg.sender, _amount);
    _emitTransferEvent(address(0), msg.sender, _amount);
    return _stake(msg.sender, _amount);
  }

  /// @notice Destroy liquid staked tokens to receive the underlying token in exchange. Tokens are removed first from
  /// the default deposit, if any are present, then from holder's specified deposit if any are needed.
  /// @param _amount The amount of tokens to unstake.
  /// @dev The amount of tokens actually unstaked may be slightly less than the amount specified due to rounding.
  function unstake(uint256 _amount) external virtual returns (uint256) {
    _emitUnstakedEvent(msg.sender, _amount);
    _emitTransferEvent(msg.sender, address(0), _amount);
    return _unstake(msg.sender, _amount);
  }

  /// @notice Grant an allowance to the spender to transfer up to a certain amount of LST tokens on behalf of the
  /// message sender.
  /// @param _spender The address which is granted the allowance to transfer from the message sender.
  /// @param _amount The total amount of the message sender's LST tokens that the spender will be permitted to transfer.
  function approve(address _spender, uint256 _amount) external virtual returns (bool) {
    allowance[msg.sender][_spender] = _amount;
    emit Approval(msg.sender, _spender, _amount);
    return true;
  }

  /// @notice Grant an allowance to the spender to transfer up to a certain amount of LST tokens on behalf of a user
  /// who has signed a message testifying to their intent to grant this allowance.
  /// @param _owner The account which is granting the allowance.
  /// @param _spender The address which is granted the allowance to transfer from the holder.
  /// @param _value The total amount of LST tokens the spender will be permitted to transfer from the holder.
  /// @param _deadline The timestamp after which the signature should expire.
  /// @param _v ECDSA signature component: Parity of the `y` coordinate of point `R`
  /// @param _r ECDSA signature component: x-coordinate of `R`
  /// @param _s ECDSA signature component: `s` value of the signature
  function permit(address _owner, address _spender, uint256 _value, uint256 _deadline, uint8 _v, bytes32 _r, bytes32 _s)
    external
    virtual
  {
    if (block.timestamp > _deadline) {
      revert GovLst__SignatureExpired();
    }

    bytes32 _structHash;
    // Unchecked because the only math done is incrementing
    // the owner's nonce which cannot realistically overflow.
    unchecked {
      _structHash = keccak256(abi.encode(PERMIT_TYPEHASH, _owner, _spender, _value, _useNonce(_owner), _deadline));
    }

    bytes32 _hash = _hashTypedDataV4(_structHash);

    address _recoveredAddress = ecrecover(_hash, _v, _r, _s);

    if (_recoveredAddress == address(0) || _recoveredAddress != _owner) {
      revert GovLst__InvalidSignature();
    }

    allowance[_recoveredAddress][_spender] = _value;

    emit Approval(_owner, _spender, _value);
  }

  /// @notice Send liquid stake tokens from the message sender to the receiver.
  /// @param _to The address that will receive the message sender's tokens.
  /// @param _value The quantity of liquid stake tokens to send.
  /// @dev The sender's underlying tokens are moved first from the default deposit, if any are present, then from
  /// sender's specified deposit, if any are needed. All tokens are moved into the receiver's specified deposit.
  /// @dev The amount of tokens received by the user can be slightly less than the amount lost by the sender.
  /// Furthermore, both amounts can be less the value requested by the sender. All such effects are due to truncation.
  function transfer(address _to, uint256 _value) external virtual returns (bool) {
    _emitTransferEvent(msg.sender, _to, _value);
    _transfer(msg.sender, _to, _value);
    return true;
  }

  /// @notice Send liquid stake tokens from the message sender to the receiver, returning the changes in balances of
  /// each. Primarily intended for use by integrators, who might need to know the exact balance changes for internal
  /// accounting in other contracts.
  /// @param _receiver The address that will receive the message sender's tokens.
  /// @param _value The quantity of liquid stake tokens to send.
  /// @return _senderBalanceDecrease The amount by which the sender's balance of lst tokens decreased.
  /// @return _receiverBalanceIncrease The amount by which the receiver's balance of lst tokens increased.
  /// @dev The amount of tokens received by the user can be slightly less than the amount lost by the sender.
  /// Furthermore, both amounts can be less the value requested by the sender. All such effects are due to truncation.
  function transferAndReturnBalanceDiffs(address _receiver, uint256 _value)
    external
    virtual
    returns (uint256 _senderBalanceDecrease, uint256 _receiverBalanceIncrease)
  {
    _emitTransferEvent(msg.sender, _receiver, _value);
    return _transfer(msg.sender, _receiver, _value);
  }

  /// @notice Send liquid stake tokens from one account to the another on behalf of a user who has granted the
  /// message sender an allowance to do so.
  /// @param _from The address from where tokens will be transferred, which has previously granted the message sender
  /// an allowance of at least the quantity of tokens being transferred.
  /// @param _to The address that will receive the sender's tokens.
  /// @param _value The quantity of liquid stake tokens to send.
  /// @dev The sender's underlying tokens are moved first from the default deposit, if any are present, then from
  /// sender's specified deposit, if any are needed. All tokens are moved into the receiver's specified deposit.
  /// @dev The amount of tokens received by the receiver can be slightly less than the amount lost by the sender.
  /// Furthermore, both amounts can be less the value requested by the sender. All such effects are due to truncation.
  function transferFrom(address _from, address _to, uint256 _value) external virtual returns (bool) {
    _checkAndUpdateAllowance(_from, _value);
    _emitTransferEvent(_from, _to, _value);
    _transfer(_from, _to, _value);
    return true;
  }

  /// @notice Send liquid stake tokens from one account to the another on behalf of a user who has granted
  /// the message sender an allowance to do so, returning the changes in balances of each. Primarily intended for use
  /// by integrators, who might need to know the exact balance changes for internal accounting in other contracts.
  /// @param _from The address from where tokens will be transferred, which has previously granted the message sender
  /// an allowance of at least the quantity of tokens being transferred.
  /// @param _to The address that will receive the message sender's tokens.
  /// @param _value The quantity of liquid stake tokens to send.
  /// @return _senderBalanceDecrease The amount by which the sender's balance of lst tokens decreased.
  /// @return _receiverBalanceIncrease The amount by which the receiver's balance of lst tokens increased.
  /// @dev The amount of tokens received by the user can be slightly less than the amount lost by the sender.
  /// Furthermore, both amounts can be less the value requested by the sender. All such effects are due to truncation.
  function transferFromAndReturnBalanceDiffs(address _from, address _to, uint256 _value)
    external
    virtual
    returns (uint256 _senderBalanceDecrease, uint256 _receiverBalanceIncrease)
  {
    _checkAndUpdateAllowance(_from, _value);
    _emitTransferEvent(_from, _to, _value);
    return _transfer(_from, _to, _value);
  }

  /// @notice Public method that allows any caller to claim the stake rewards earned by the LST. Caller must pre-
  /// approve the LST on the stake token contract for at least the payout amount, which is transferred from the caller
  /// to the LST, added to the total supply, and sent to the default deposit. The effect of this is to distribute the
  /// reward proportionally to all LST holders, whose underlying shares will now be worth more of the stake token due
  /// the addition of the reward to the total supply. Because all holders' balances change simultaneously, transfer
  /// events cannot be emitted for all users. This makes the LST a non-standard ERC20, similar in nature to stETH.
  ///
  /// A quick example can help illustrate why an external party, such as an MEV searcher, would be incentivized to call
  /// this method. Imagine, purely for the sake of example, that the LST contract has accrued rewards of 1 ETH in the
  /// staking contract, and the payout amount here in the LST is set to 500 governance tokens. Imagine ETH is trading at
  /// $2,500 and the governance token is trading at $5. At this point, the value of ETH available to be claimed is equal
  /// to the value of the payout amount required in staking token. Once a bit more ETH accrues, it will be profitable
  /// for a searcher to trade the 500 staking tokens in exchange for the accrued ETH rewards. (This ignores other
  /// details, which real searchers would take into consideration, such as the gas/builder fee they would pay to call
  /// the method).
  ///
  /// Note that `payoutAmount` may be changed by the admin (governance). Any proposal that changes this amount is
  /// expected to be subject to the governance process, including a timelocked execution, and so it's unlikely that a
  /// caller would be surprised by a change in this value. Still, callers should be aware of the edge case where:
  /// 1. The caller grants a higher-than-necessary payout token approval to this LST.
  /// 2. Caller's claimAndDistributeReward transaction is in the mempool.
  /// 3. The payoutAmount is changed.
  /// 4. The claimAndDistributeReward transaction is now included in a block.
  ///
  /// The number of deposits owned by the LST may grow to such a size that all deposits cannot be included in a single
  /// call. This can cause the total payout of the LST's deposits to be processed with slight lag temporarily reducing
  /// the value of liquid staking tokens compared to direct staking.
  /// @param _recipient The address that will receive the stake reward payout.
  /// @param _minExpectedReward The minimum reward payout, in the reward token of the underlying staker contract, that
  /// the caller will accept in exchange for providing the payout amount of stake token. If the amount claimed is less
  /// than this, the transaction will revert. This parameter is a last line of defense against the MEV caller losing
  /// funds because they've been frontrun by another searcher.
  /// @param _depositIds List of deposits owned by the LST from which rewards will be claimed by the caller.
  function claimAndDistributeReward(
    address _recipient,
    uint256 _minExpectedReward,
    Staker.DepositIdentifier[] calldata _depositIds
  ) external virtual {
    RewardParameters memory _rewardParams = rewardParams;

    uint256 _feeAmount = _calcFeeAmount(_rewardParams);

    Totals memory _totals = totals;

    // By increasing the total supply by the amount of tokens that are distributed as part of the reward, the balance
    // of every holder increases proportional to the underlying shares which they hold.
    uint96 _newTotalSupply = _totals.supply + _rewardParams.payoutAmount; // payoutAmount is assumed safe

    uint160 _feeShares;
    if (_feeAmount > 0) {
      // Our goal is to issue shares to the fee collector such that the new shares the fee collector receives are
      // worth `feeAmount` of `stakeToken` after the reward is distributed. This can be expressed mathematically
      // as feeAmount = (feeShares * newTotalSupply) / newTotalShares, where the newTotalShares is equal to the sum of
      // the fee shares and the total existing shares. In this equation, all the terms are known except the fee shares.
      // Solving for the fee shares yields the following calculation.
      _feeShares = _calcFeeShares(_feeAmount, _newTotalSupply, _totals.shares);

      // By issuing these new shares to the `feeCollector` we effectively give the it `feeAmount` of the reward by
      // slightly diluting all other LST holders.
      holderStates[rewardParams.feeCollector].shares += uint128(_feeShares);
    }

    totals = Totals({supply: _newTotalSupply, shares: _totals.shares + _feeShares});

    // Transfer stake token to the LST
    STAKE_TOKEN.safeTransferFrom(msg.sender, address(this), _rewardParams.payoutAmount);
    // Stake the rewards with the default delegatee
    STAKER.stakeMore(DEFAULT_DEPOSIT_ID, _rewardParams.payoutAmount);

    // Claim the reward tokens earned by the LST for each deposit
    uint256 _rewards;
    for (uint256 _index = 0; _index < _depositIds.length; _index++) {
      _rewards += STAKER.claimReward(_depositIds[_index]);
    }

    // Ensure rewards distributed meet the claimers expectations; provides protection from frontrunning resulting in
    // loss of funds for the MEV racers.
    if (_rewards < _minExpectedReward) {
      revert GovLst__InsufficientRewards();
    }
    // Transfer the reward tokens to the recipient
    REWARD_TOKEN.safeTransfer(_recipient, _rewards);

    emit RewardDistributed(
      msg.sender, _recipient, _rewards, rewardParams.payoutAmount, _feeAmount, rewardParams.feeCollector
    );
  }

  /// @notice Allow a depositor to change the address they are delegating their staked tokens.
  /// @param _delegatee The address where voting is delegated.
  /// @return _depositId The deposit identifier for the delegatee.
  /// @dev This operation can be completed in a more gas efficient manner by calling `updateDeposit` with the depositId
  /// of the user's chosen delegatee, assuming it has already been initialized. This method is included primarily for
  /// partial compatibility with the `IVotes` interface.
  function delegate(address _delegatee) public virtual returns (Staker.DepositIdentifier _depositId) {
    _depositId = fetchOrInitializeDepositForDelegatee(_delegatee);
    updateDeposit(_depositId);
  }

  /// @notice Open method which allows anyone to add funds to a stake deposit owned by the LST. These funds are not
  /// added to the LST's supply and no tokens or shares are issues to the caller. The  purpose of this method is to
  /// provide buffer funds for shortfalls in deposits due to rounding errors. In particular, the system is designed
  /// such that rounding errors are known to accrue to the default deposit. Being able to provably provide a buffer for
  /// the default deposit is the primary intended use case for this method. That said, if other unknown issues were to
  /// arise, it could also be used to ensure the internal solvency of the other stake deposits as well.
  /// @param _depositId The stake deposit identifier that is being subsidized.
  /// @param _amount The quantity of stake tokens that will be sent to the deposit.
  /// @dev Caller must approve the LST contract for at least the `_amount` on the stake token before calling this
  /// method.
  function subsidizeDeposit(Staker.DepositIdentifier _depositId, uint256 _amount) external virtual {
    STAKE_TOKEN.safeTransferFrom(msg.sender, address(this), _amount);

    // This will revert if the deposit is not owned by this contract
    STAKER.stakeMore(_depositId, _amount);

    emit DepositSubsidized(_depositId, _amount);
  }

  /// @notice An open method which allows anyone to override the delegatee of a deposit to the default delegatee
  /// if the deposit's earning power is below the minimum qualifying earning power.
  /// @param _depositId The id of the deposit to override the delegatee.
  function enactOverride(Staker.DepositIdentifier _depositId) external virtual {
    (uint96 _balance,, uint96 _earningPower,,,,) = STAKER.deposits(_depositId);

    if (_isSameDepositId(_depositId, DEFAULT_DEPOSIT_ID) || isOverridden[_depositId] || _balance == 0) {
      revert GovLst__InvalidOverride();
    }

    bool _isAboveMin = uint256(_earningPower) * BIPS >= minQualifyingEarningPowerBips * _balance;
    if (_isAboveMin) {
      revert GovLst__EarningPowerNotQualified(
        uint256(_earningPower) * BIPS, uint256(minQualifyingEarningPowerBips) * _balance
      );
    }

    // Move the deposit delegatee to the default delegatee
    STAKER.alterDelegatee(_depositId, defaultDelegatee);

    isOverridden[_depositId] = true;

    emit OverrideEnacted(_depositId);
  }

  /// @notice An open method which allows anyone to reset a deposit with an overridden delegatee to the original
  /// deposit delegatee if the deposit's earning power is above the minimum qualifying earning power.
  /// @param _depositId The id of the deposit in the override state.
  /// @param _originalDelegatee The address of the delegatee that is supposed to be associated with this deposit, when
  /// it is not in the override state.
  function revokeOverride(Staker.DepositIdentifier _depositId, address _originalDelegatee) external virtual {
    if (!_isSameDepositId(storedDepositIdForDelegatee[_originalDelegatee], _depositId) || !isOverridden[_depositId]) {
      revert GovLst__InvalidOverride();
    }

    // Move the deposit's delegatee back to the original
    STAKER.alterDelegatee(_depositId, _originalDelegatee);

    (uint96 _balance,, uint96 _earningPower,,,,) = STAKER.deposits(_depositId);
    if (_balance == 0) {
      revert GovLst__InvalidOverride();
    }

    // Make sure earning power is above min earning power
    bool _isBelowMin = uint256(_earningPower) * BIPS < minQualifyingEarningPowerBips * _balance;
    if (_isBelowMin) {
      revert GovLst__EarningPowerNotQualified(
        uint256(_earningPower) * BIPS, uint256(minQualifyingEarningPowerBips) * _balance
      );
    }

    isOverridden[_depositId] = false;

    emit OverrideRevoked(_depositId);
  }

  /// @notice An open method that allows anyone to migrate an overridden deposit to a new default delegatee. This method
  /// handles cases where the `GovLst` owner sets a new default delegatee while some existing deposits are overridden,
  /// still referencing the old default delegatee.
  /// @param _depositId The id of the deposit in the override state.
  function migrateOverride(Staker.DepositIdentifier _depositId) external virtual {
    // Deposit must be overridden
    if (!isOverridden[_depositId]) {
      revert GovLst__InvalidOverride();
    }

    (,,, address _currentDelegatee,,,) = STAKER.deposits(_depositId);
    // Deposit cannot be the current default delegatee
    if (_currentDelegatee == defaultDelegatee) {
      revert GovLst__InvalidOverride();
    }

    // Move the deposit's delegatee back to the current default delegatee.
    STAKER.alterDelegatee(_depositId, defaultDelegatee);

    // Emit event
    emit OverrideMigrated(_depositId, _currentDelegatee, defaultDelegatee);
  }

  /// @notice Sets the reward parameters including payout amount, fee in bips, and fee collector.
  /// @param _params The new reward parameters.
  function setRewardParameters(RewardParameters memory _params) external virtual {
    _checkOwner();
    _setRewardParams(_params.payoutAmount, _params.feeBips, _params.feeCollector);
  }

  /// @notice Sets the minimum qualifying earning power amount in bips (1/10,000). This value determines whether a
  /// deposits delegatee needs to be overridden because it isn't earning enough of its possible staking rewards.
  /// @param _minQualifyingEarningPowerBips The new minimum qualifying earning power amount in bips (1/10,000).
  function setMinQualifyingEarningPowerBips(uint256 _minQualifyingEarningPowerBips) external virtual {
    _checkOwner();
    _setMinQualifyingEarningPowerBips(_minQualifyingEarningPowerBips);
  }

  /// @notice Update the default delegatee. Can only be called by the delegatee guardian or by the LST owner. Once the
  /// guardian takes an action on the LST, the owner can no longer override it.
  /// @param _newDelegatee The address which will be assigned as the delegatee for the default staker deposit.
  function setDefaultDelegatee(address _newDelegatee) external virtual {
    _checkAndToggleGuardianControlOrOwner();
    _setDefaultDelegatee(_newDelegatee);
    STAKER.alterDelegatee(DEFAULT_DEPOSIT_ID, _newDelegatee);
  }

  /// @notice Update the delegatee guardian. Can only be called by the delegatee guardian or by the LST owner. Once the
  /// guardian takes an action on the LST, the owner can no longer override it.
  /// @param _newDelegateeGuardian The address which will become the new delegatee guardian.
  function setDelegateeGuardian(address _newDelegateeGuardian) external virtual {
    _checkAndToggleGuardianControlOrOwner();
    _setDelegateeGuardian(_newDelegateeGuardian);
  }

  //---------------------------------------- Begin Fixed LST Helper Methods ------------------------------------------/

  /// @notice Permissioned fixed LST helper method which updates the deposit of the holder's fixed LST alias address.
  /// @param _account The holder setting their deposit in the fixed LST.
  /// @param _newDepositId The stake deposit identifier to which this holder's fixed LST staked tokens will be
  /// moved to and kept in henceforth.
  /// @return _oldDepositId The stake deposit identifier from which this holder's fixed LST staked tokens were
  /// moved.
  function updateFixedDeposit(address _account, Staker.DepositIdentifier _newDepositId)
    external
    virtual
    returns (Staker.DepositIdentifier _oldDepositId)
  {
    _revertIfNotFixedLst();
    _oldDepositId = _updateDeposit(_account.fixedAlias(), _newDepositId);
  }

  /// @notice Permissioned fixed LST helper method which performs the staking operation on behalf of the holder's fixed
  /// LST alias address, allowing the holder to stake in the fixed LST directly.
  /// @param _account The holder staking in the fixed LST.
  /// @param _amount The quantity of tokens that will be staked in the fixed LST.
  /// @return The number of _shares_ received by the holder's fixed alias address.
  function stakeAndConvertToFixed(address _account, uint256 _amount) external virtual returns (uint256) {
    _revertIfNotFixedLst();
    uint256 _initialShares = holderStates[_account.fixedAlias()].shares;

    // Externally, we model this as the Fixed LST contract staking on behalf of the account in question, so we emit
    // an event that shows the Fixed LST contract as the staker.
    _emitStakedEvent(address(FIXED_LST), _amount);
    _emitTransferEvent(address(0), address(FIXED_LST), _amount);

    // We assume that the stake tokens have already been transferred to this contract by the FixedLst.
    _stake(_account.fixedAlias(), _amount);
    return holderStates[_account.fixedAlias()].shares - _initialShares;
  }

  /// @notice Permissioned fixed LST helper method which moves a holder's rebasing LST tokens into its fixed alias
  /// address, effectively converting rebasing LST tokens to fixed LST tokens.
  /// @param _account The holder converting rebasing LST tokens into fixed LST tokens.
  /// @param _amount The number of rebasing LST tokens to convert.
  /// @return The number of _shares_ received by the holder's fixed alias address.
  function convertToFixed(address _account, uint256 _amount) external virtual returns (uint256) {
    _revertIfNotFixedLst();
    uint256 _initialShares = holderStates[_account.fixedAlias()].shares;

    // Externally, we model this as the holder moving rebasing LST tokens into the Fixed LST contract, so we emit
    // an event that reflects this transfer to the Fixed LST contract.
    _emitTransferEvent(_account, address(FIXED_LST), _amount);

    _transfer(_account, _account.fixedAlias(), _amount);
    return holderStates[_account.fixedAlias()].shares - _initialShares;
  }

  /// @notice Permissioned fixed LST helper method which transfers tokens between fixed alias addresses.
  /// @param _sender The address of the fixed LST holder sending fixed LST tokens.
  /// @param _receiver The address receiving fixed LST tokens.
  /// @param _shares The number of rebasing LST _shares_ to move between sender and receiver aliases.
  /// @return _senderSharesDecrease The decrease in the sender alias address' shares.
  /// @return _receiverSharesIncrease The increase in the receiver alias address' shares.
  function transferFixed(address _sender, address _receiver, uint256 _shares)
    external
    virtual
    returns (uint256 _senderSharesDecrease, uint256 _receiverSharesIncrease)
  {
    _revertIfNotFixedLst();
    uint256 _senderInitialShares = holderStates[_sender.fixedAlias()].shares;
    uint256 _receiverInitialShares = holderStates[_receiver.fixedAlias()].shares;
    uint256 _amount = stakeForShares(_shares);
    _transfer(_sender.fixedAlias(), _receiver.fixedAlias(), _amount);
    uint256 _senderFinalShares = holderStates[_sender.fixedAlias()].shares;
    uint256 _receiverFinalShares = holderStates[_receiver.fixedAlias()].shares;
    return (_senderInitialShares - _senderFinalShares, _receiverFinalShares - _receiverInitialShares);
  }

  /// @notice Permissioned fixed LST helper method which moves rebasing LST tokens from a holder's alias back to his
  /// standard address, effectively converting fixed LST tokens back to rebasing LST tokens.
  /// @param _account The holder converting fixed LST tokens into rebasing LST tokens.
  /// @param _shares The number of _shares_ worth of rebasing LST tokens to be moved from the holder's fixed alias
  /// to their standard address.
  /// @return The number of rebasing LST tokens moved back into the holder's address.
  function convertToRebasing(address _account, uint256 _shares) external virtual returns (uint256) {
    _revertIfNotFixedLst();
    uint256 _amount = stakeForShares(_shares);
    uint256 _amountUnfixed;
    (, _amountUnfixed) = _transfer(_account.fixedAlias(), _account, _amount);

    // Externally, we model this as the fixed LST sending rebasing LST tokens back to the holder, so we emit an
    // that reflects this.
    _emitTransferEvent(address(FIXED_LST), _account, _amountUnfixed);

    return _amountUnfixed;
  }

  /// @notice Permissioned fixed LST helper method which transfers rebasing LST tokens from the holder's fixed alias
  /// address then unstakes them on his behalf, allowing the holder to unstake from the fixed LST directly.
  /// @param _account The holder unstaking his fixed LST tokens.
  /// @param _shares The number of _shares_ worth of rebasing LST tokens to be unstaked.
  /// @return The number of governance tokens unstaked.
  /// @dev If their is a withdrawal delay being enforced, the tokens will be moved into the withdrawal gate on behalf
  /// of the holder's account, not his alias.
  function convertToRebasingAndUnstake(address _account, uint256 _shares) external virtual returns (uint256) {
    _revertIfNotFixedLst();
    uint256 _amount = stakeForShares(_shares);
    uint256 _amountUnfixed;
    (, _amountUnfixed) = _transfer(_account.fixedAlias(), _account, _amount);

    // Externally, we model this as the fixed LST unstaking on behalf of the account in question, so we emit
    // an event that shows the Fixed LST contract as the unstaker.
    _emitUnstakedEvent(address(FIXED_LST), _amountUnfixed);
    _emitTransferEvent(address(FIXED_LST), address(0), _amount);

    return _unstake(_account, _amountUnfixed);
  }

  //------------------------------------------ End Fixed LST Helper Methods ------------------------------------------/

  /// @notice Method called in the GovLst constructor which deploys the corresponding FixedGovLst
  /// instance that accompanies this instance of the rebasing GovLst. This is a virtual method that
  /// must be implemented by each concrete instance of GovLst.
  /// @dev The parameters called in this method match 1:1 the parameters in the constructor of the
  /// FixedGovLst contract.
  function _deployFixedGovLst(
    string memory _name,
    string memory _symbol,
    string memory _version,
    GovLst _lst,
    IERC20 _stakeToken,
    uint256 _shareScaleFactor
  ) internal virtual returns (FixedGovLst _fixedLst);

  /// @notice Internal helper method that takes an amount of stake tokens and metadata representing the global state of
  /// the LST and returns the quantity of shares that is worth the requested quantity of stake token. All data for the
  /// calculation is provided in memory and the calculation is performed there, making it a pure function.
  /// @param _amount The quantity of stake token that will be converted to a number of shares.
  /// @param _totals The metadata representing current global conditions.
  /// @return The quantity of shares that is worth the provided quantity of stake token.
  function _calcSharesForStake(uint256 _amount, Totals memory _totals) internal pure virtual returns (uint256) {
    if (_totals.supply == 0) {
      return SHARE_SCALE_FACTOR * _amount;
    }

    return (_amount * _totals.shares) / _totals.supply;
  }

  /// @notice Internal helper method that takes an amount of stake tokens and metadata representing the global state of
  /// the LST and returns the quantity of shares that is worth the requested quantity of stake token, __rounded up__.
  /// All data for the calculation is provided in memory and the calculation is performed there, making it a pure
  /// function.
  /// @param _amount The quantity of stake token that will be converted to a number of shares.
  /// @param _totals The metadata representing current global conditions.
  /// @return The quantity of shares that is worth the provided quantity of stake token, __rounded up__.
  function _calcSharesForStakeUp(uint256 _amount, Totals memory _totals) internal pure virtual returns (uint256) {
    uint256 _result = _calcSharesForStake(_amount, _totals);

    if (mulmod(_amount, _totals.shares, _totals.supply) > 0) {
      _result += 1;
    }

    return _result;
  }

  /// @notice Internal helper method that takes an amount of shares, and metadata representing the global state of
  /// the LST, and returns the quantity of stake tokens that the requested shares are worth. All data for the
  /// calculation is provided in memory and the calculation is performed there, making it a pure function.
  /// @param _shares The quantity of shares that will be converted to stake tokens.
  /// @param _totals The metadata representing current global conditions.
  /// @return The quantity of stake tokens which backs the provide quantity of shares.
  function _calcStakeForShares(uint256 _shares, Totals memory _totals) internal pure virtual returns (uint256) {
    if (_totals.shares == 0) {
      return _shares / SHARE_SCALE_FACTOR;
    }

    return (_shares * _totals.supply) / _totals.shares;
  }

  /// @notice Internal method that takes a holder's state and the global state and calculates the holder's would-be
  /// balance in such conditions.
  /// @param _holder The metadata associated with a given holder.
  /// @param _totals The metadata representing current global conditions.
  /// @return The calculated balance of the holder given the global conditions.
  function _calcBalanceOf(HolderState memory _holder, Totals memory _totals) internal pure virtual returns (uint256) {
    if (_holder.shares == 0) {
      return 0;
    }

    return _calcStakeForShares(_holder.shares, _totals);
  }

  /// @notice Internal helper method that takes the metadata representing an LST holder and returns the staker
  /// deposit identifier that holder has assigned his voting weight to.
  function _calcDepositId(HolderState memory _holder) internal view virtual returns (Staker.DepositIdentifier) {
    if (_holder.depositId == 0) {
      return DEFAULT_DEPOSIT_ID;
    } else {
      return Staker.DepositIdentifier.wrap(_holder.depositId);
    }
  }

  function _calcFeeShares(uint256 _feeAmount, uint256 _newTotalSupply, uint256 _totalShares)
    internal
    pure
    virtual
    returns (uint160)
  {
    return SafeCast.toUint160((uint256(_feeAmount) * _totalShares) / (_newTotalSupply - _feeAmount));
  }

  /// @notice Internal convenience method which performs deposit update operations.
  /// @dev This method must only be called after proper authorization has been completed.
  /// @dev See public updateDeposit methods for additional documentation.
  function _updateDeposit(address _account, Staker.DepositIdentifier _newDepositId)
    internal
    virtual
    returns (Staker.DepositIdentifier _oldDepositId)
  {
    // Read required state from storage once.
    Totals memory _totals = totals;
    HolderState memory _holderState = holderStates[_account];

    _oldDepositId = _calcDepositId(_holderState);

    uint256 _balanceOf = _calcBalanceOf(_holderState, _totals);

    // If the user's deposit is currently zero, and the deposit identifier specified is indeed owned by the LST as it
    // must be, we can simply update their deposit identifier and avoid actions on the underlying Staker.
    if (_balanceOf == 0) {
      (, address _owner,,,,,) = STAKER.deposits(_newDepositId);
      if (_owner == address(this)) {
        holderStates[_account].depositId = _depositIdToUInt32(_newDepositId);
        _revertIfInvalidDeposit(_newDepositId);
        return _oldDepositId;
      }
    }

    uint256 _delegatedBalance = _holderState.balanceCheckpoint;
    // This is the number of tokens in the default pool that the account has claim to
    uint256 _undelegatedBalance = _balanceOf - _delegatedBalance;

    // Make internal state updates.
    if (_isSameDepositId(_oldDepositId, _newDepositId) && _isSameDepositId(_newDepositId, DEFAULT_DEPOSIT_ID)) {
      // do nothing and return
      return _oldDepositId;
    } else if (_isSameDepositId(_oldDepositId, _newDepositId)) {
      _holderState.balanceCheckpoint = uint96(_balanceOf);
      STAKER.withdraw(DEFAULT_DEPOSIT_ID, _undelegatedBalance);
      STAKER.stakeMore(_newDepositId, _undelegatedBalance);
    } else if (_isSameDepositId(_newDepositId, DEFAULT_DEPOSIT_ID)) {
      _holderState.balanceCheckpoint = 0;
      _holderState.depositId = 0;
      STAKER.withdraw(_oldDepositId, _delegatedBalance);
      STAKER.stakeMore(_newDepositId, _delegatedBalance);
    } else if ((_isSameDepositId(_oldDepositId, DEFAULT_DEPOSIT_ID))) {
      _holderState.balanceCheckpoint = uint96(_balanceOf);
      _holderState.depositId = _depositIdToUInt32(_newDepositId);
      STAKER.withdraw(DEFAULT_DEPOSIT_ID, _balanceOf);
      STAKER.stakeMore(_newDepositId, _balanceOf);
      _revertIfInvalidDeposit(_newDepositId);
    } else {
      _holderState.balanceCheckpoint = uint96(_balanceOf);
      _holderState.depositId = _depositIdToUInt32(_newDepositId);
      if (_undelegatedBalance > 0) {
        STAKER.withdraw(DEFAULT_DEPOSIT_ID, _undelegatedBalance);
      }
      STAKER.withdraw(_oldDepositId, _delegatedBalance);
      STAKER.stakeMore(_newDepositId, _balanceOf);
      _revertIfInvalidDeposit(_newDepositId);
    }

    // Write updated states back to storage.
    holderStates[_account] = _holderState;
  }

  /// @notice Internal helper method that emits a DepositUpdated event with the parameters provided.
  function _emitDepositUpdatedEvent(
    address _account,
    Staker.DepositIdentifier _oldDepositId,
    Staker.DepositIdentifier _newDepositId
  ) internal virtual {
    emit DepositUpdated(_account, _oldDepositId, _newDepositId);
  }

  /// @notice Internal convenience method which performs staking operations.
  /// @dev This method must only be called after proper authorization has been completed.
  /// @dev See public stake methods for additional documentation.
  /// @return The difference in LST token balance of the account after the stake operation.
  function _stake(address _account, uint256 _amount) internal virtual returns (uint256) {
    // Read required state from storage once.
    Totals memory _totals = totals;
    HolderState memory _holderState = holderStates[_account];

    uint256 _initialBalance = _calcBalanceOf(_holderState, _totals);
    uint256 _newShares = _calcSharesForStake(_amount, _totals);

    // cast is safe because we have transferred token amount
    _totals.supply = _totals.supply + SafeCast.toUint96(_amount);
    // _newShares cast to uint128 later would fail if overflowed
    _totals.shares = _totals.shares + uint160(_newShares);

    _holderState.shares = _holderState.shares + _newShares.toUint128();
    uint256 _balanceDiff = _calcBalanceOf(_holderState, _totals) - _initialBalance;
    Staker.DepositIdentifier _holderDepositId = _calcDepositId(_holderState);
    if (!_isSameDepositId(_holderDepositId, DEFAULT_DEPOSIT_ID)) {
      _holderState.balanceCheckpoint =
        _min(_holderState.balanceCheckpoint + uint96(_amount), uint96(_calcBalanceOf(_holderState, _totals)));
    }

    // Write updated states back to storage.
    totals = _totals;
    holderStates[_account] = _holderState;

    STAKER.stakeMore(_holderDepositId, _amount);
    return _balanceDiff;
  }

  /// @notice Internal helper method that emits a Staked event with the parameters provided.
  function _emitStakedEvent(address _account, uint256 _amount) internal virtual {
    emit Staked(_account, _amount);
  }

  /// @notice Internal convenience method which performs unstaking operations.
  /// @dev This method must only be called after proper authorization has been completed.
  /// @dev See public unstake methods for additional documentation.
  /// @return The amount of LST tokens unstaked and either transferred to the user directly or placed in the withdrawal
  /// gate.
  function _unstake(address _account, uint256 _amount) internal virtual returns (uint256) {
    // Read required state from storage once.
    Totals memory _totals = totals;
    HolderState memory _holderState = holderStates[_account];

    uint256 _initialBalanceOf = _calcBalanceOf(_holderState, _totals);

    if (_amount > _initialBalanceOf) {
      revert GovLst__InsufficientBalance();
    }

    // Decreases the holder's balance by the amount being withdrawn
    uint256 _sharesDestroyed = _calcSharesForStakeUp(_amount, _totals);
    _holderState.shares -= _sharesDestroyed.toUint128();

    // cast is safe because we've validated user has sufficient balance
    _totals.supply = _totals.supply - SafeCast.toUint96(_amount);
    // cast is safe because shares fits into uint128
    _totals.shares = _totals.shares - uint160(_sharesDestroyed);

    uint256 _delegatedBalance = _holderState.balanceCheckpoint;
    uint256 _undelegatedBalance = _initialBalanceOf - _delegatedBalance;
    uint256 _undelegatedBalanceToWithdraw;

    if (_amount > _undelegatedBalance) {
      // Since the amount needed is more than the full undelegated balance, we'll withdraw all of it, plus some from
      // the delegated balance.
      _undelegatedBalanceToWithdraw = _undelegatedBalance;
      uint256 _delegatedBalanceToWithdraw = _amount - _undelegatedBalanceToWithdraw;
      STAKER.withdraw(_calcDepositId(_holderState), _delegatedBalanceToWithdraw);
      _holderState.balanceCheckpoint = uint96(_delegatedBalance - _delegatedBalanceToWithdraw);
    } else {
      // Since the amount is less than or equal to the undelegated balance, we'll source all of it from said balance.
      _undelegatedBalanceToWithdraw = _amount;
    }

    // If the staker had zero undelegated balance, we won't waste gas executing the withdraw call.
    if (_undelegatedBalanceToWithdraw > 0) {
      STAKER.withdraw(DEFAULT_DEPOSIT_ID, _undelegatedBalanceToWithdraw);
    }

    // Ensure the holder's balance checkpoint is updated if it has decreased due to truncation.
    _holderState.balanceCheckpoint = _min(_holderState.balanceCheckpoint, uint96(_calcBalanceOf(_holderState, _totals)));

    // Write updated states back to storage.
    totals = _totals;
    holderStates[_account] = _holderState;

    // At this point, the LST holds _amount of stakeToken

    address _withdrawalTarget;
    if (WITHDRAW_GATE.delay() == 0) {
      // If there's currently a 0-delay on withdraws, just send the tokens straight to the user.
      _withdrawalTarget = _account;
    } else {
      _withdrawalTarget = address(WITHDRAW_GATE);
      WITHDRAW_GATE.initiateWithdrawal(uint96(_amount), _account);
    }

    STAKE_TOKEN.safeTransfer(_withdrawalTarget, _amount);
    return _amount;
  }

  /// @notice Internal helper method that emits an Unstaked event with the parameters provided.
  function _emitUnstakedEvent(address _account, uint256 _amount) internal virtual {
    emit Unstaked(_account, _amount);
  }

  /// @notice Internal convenience method which performs transfer operations.
  /// @dev This method must only be called after proper authorization has been completed.
  /// @dev See public transfer methods for additional documentation.
  /// @return A tuple containing the sender's balance decrease and the receiver's balance increase in that order.
  function _transfer(address _sender, address _receiver, uint256 _value) internal virtual returns (uint256, uint256) {
    // Early check for self-transfer
    if (_sender == _receiver) {
      emit Transfer(_sender, _receiver, _value);
      return (0, 0);
    }

    // Read required state from storage once.
    Totals memory _totals = totals;
    HolderState memory _senderState = holderStates[_sender];
    HolderState memory _receiverState = holderStates[_receiver];

    // Record initial balances.
    uint256 _senderInitBalance = _calcBalanceOf(_senderState, _totals);
    uint256 _receiverInitBalance = _calcBalanceOf(_receiverState, _totals);
    uint256 _senderDelegatedBalance = _senderState.balanceCheckpoint;
    uint256 _senderUndelegatedBalance = _senderInitBalance - _senderDelegatedBalance;

    if (_value > _senderInitBalance) {
      revert GovLst__InsufficientBalance();
    }

    // Move underlying shares.
    {
      uint256 _shares = _calcSharesForStakeUp(_value, _totals);
      _senderState.shares -= SafeCast.toUint128(_shares);
      _receiverState.shares += uint128(_shares);
    }

    uint256 _receiverBalanceIncrease = _calcBalanceOf(_receiverState, _totals) - _receiverInitBalance;
    uint256 _senderBalanceDecrease = _senderInitBalance - _calcBalanceOf(_senderState, _totals);

    // Knowing the sender's balance has decreased by at least as much as the receiver's has increased, we now base the
    // calculation of how much to move between deposits on the greater number, i.e. the sender's decrease. However,
    // when we update the receiver's balance checkpoint, we use the smaller number—the receiver's balance change.
    // As a result, extra wei may be lost, i.e. no longer controlled by either the sender or the receiver,
    // but are instead stuck permanently in the receiver's deposit. This is ok, as the amount lost is miniscule, but
    // we've ensured the solvency of each underlying Staker deposit.

    if (!_isSameDepositId(_calcDepositId(_receiverState), DEFAULT_DEPOSIT_ID)) {
      _receiverState.balanceCheckpoint += uint96(_value);
    }

    // rescoping these vars to avoid stack too deep
    address _senderRescoped = _sender;
    address _receiverRescoped = _receiver;

    // If both the sender and receiver are using the default deposit, then no tokens whatsoever need to move
    // between Staker deposits.
    if (
      _isSameDepositId(_calcDepositId(_receiverState), DEFAULT_DEPOSIT_ID)
        && _isSameDepositId(_calcDepositId(_senderState), DEFAULT_DEPOSIT_ID)
    ) {
      // Write data back to storage once.
      holderStates[_senderRescoped] = _senderState;
      holderStates[_receiverRescoped] = _receiverState;
      return (_senderBalanceDecrease, _receiverBalanceIncrease);
    }

    // Create a new scope for this series of operations to avoid stack to deep.
    {
      // Rescoping these vars to avoid stack too deep.
      uint256 _valueRescoped = _value;
      Totals memory _totalsRescoped = _totals;
      HolderState memory _senderStateRescoped = _senderState;
      HolderState memory _receiverStateRescoped = _receiverState;

      uint256 _undelegatedBalanceToWithdraw;
      uint256 _delegatedBalanceToWithdraw;

      if (_valueRescoped > _senderUndelegatedBalance) {
        // Since the amount needed is more than the full undelegated balance, we'll withdraw all of it, plus some from
        // the delegated balance.
        _undelegatedBalanceToWithdraw = _senderUndelegatedBalance;
        _delegatedBalanceToWithdraw = _valueRescoped - _undelegatedBalanceToWithdraw;
        _senderStateRescoped.balanceCheckpoint = uint96(_senderDelegatedBalance - _delegatedBalanceToWithdraw);

        if (_isSameDepositId(_calcDepositId(_receiverStateRescoped), _calcDepositId(_senderStateRescoped))) {
          // If the sender and receiver are using the same deposit, we don't need to move these tokens, so we skip the
          // Staker withdraw and zero out this value so we don't try to "stakeMore" with it later.
          _delegatedBalanceToWithdraw = 0;
        } else {
          STAKER.withdraw(_calcDepositId(_senderStateRescoped), _delegatedBalanceToWithdraw);
        }
      } else {
        // Since the amount is less than or equal to the undelegated balance, we'll source all of it from said balance.
        _undelegatedBalanceToWithdraw = _valueRescoped;
      }

      // Ensure the sender's balance checkpoint is updated if it has decreased due to truncation.
      _senderStateRescoped.balanceCheckpoint =
        _min(_senderStateRescoped.balanceCheckpoint, uint96(_calcBalanceOf(_senderStateRescoped, _totalsRescoped)));

      // Write data back to storage once.
      holderStates[_senderRescoped] = _senderStateRescoped;
      holderStates[_receiverRescoped] = _receiverStateRescoped;

      // If the staker had zero undelegated balance, we won't waste gas executing the withdraw call.
      if (_undelegatedBalanceToWithdraw > 0) {
        STAKER.withdraw(DEFAULT_DEPOSIT_ID, _undelegatedBalanceToWithdraw);
      }

      // If both the delegated balance to withdraw and the undelegated balance to withdraw were zero, then we didn't
      // have to move any tokens out of Staker deposits, and none need to be put back into the receiver's deposit now.
      if ((_delegatedBalanceToWithdraw + _undelegatedBalanceToWithdraw) > 0) {
        STAKER.stakeMore(
          _calcDepositId(_receiverStateRescoped), _delegatedBalanceToWithdraw + _undelegatedBalanceToWithdraw
        );
      }
    }

    return (_senderBalanceDecrease, _receiverBalanceIncrease);
  }

  /// @notice Internal helper method that emits an IERC20.Transfer event with the parameters provided.
  function _emitTransferEvent(address _sender, address _receiver, uint256 _value) internal virtual {
    emit Transfer(_sender, _receiver, _value);
  }

  /// @notice Internal function to set reward parameters
  /// @param _payoutAmount The new payout amount
  /// @param _feeBips The new fee in basis points
  /// @param _feeCollector The new fee collector address
  function _setRewardParams(uint80 _payoutAmount, uint16 _feeBips, address _feeCollector) internal virtual {
    if (_feeBips > MAX_FEE_BIPS) {
      revert GovLst__FeeBipsExceedMaximum(_feeBips, MAX_FEE_BIPS);
    }

    if (_feeCollector == address(0)) {
      revert GovLst__FeeCollectorCannotBeZeroAddress();
    }

    rewardParams = RewardParameters({payoutAmount: _payoutAmount, feeBips: _feeBips, feeCollector: _feeCollector});

    emit RewardParametersSet(_payoutAmount, _feeBips, _feeCollector);
  }

  /// @notice Internal helper method that sets the min qualifying earning power and emits an event.
  function _setMinQualifyingEarningPowerBips(uint256 _minQualifyingEarningPowerBips) internal virtual {
    if (_minQualifyingEarningPowerBips > MINIMUM_QUALIFYING_EARNING_POWER_BIPS_CAP) {
      revert GovLst__InvalidParameter();
    }
    emit MinQualifyingEarningPowerBipsSet(minQualifyingEarningPowerBips, _minQualifyingEarningPowerBips);
    minQualifyingEarningPowerBips = _minQualifyingEarningPowerBips;
  }

  /// @notice Internal helper method that sets the delegatee and emits an event.
  function _setDefaultDelegatee(address _newDelegatee) internal virtual {
    emit DefaultDelegateeSet(defaultDelegatee, _newDelegatee);
    defaultDelegatee = _newDelegatee;
  }

  /// @notice Internal helper method that sets the guardian and emits an event.
  function _setDelegateeGuardian(address _newDelegateeGuardian) internal virtual {
    emit DelegateeGuardianSet(delegateeGuardian, _newDelegateeGuardian);
    delegateeGuardian = _newDelegateeGuardian;
  }

  /// @notice Internal helper that updates the allowance of the from address for the message sender, and reverts if the
  /// message sender does not have sufficient allowance.
  /// @param _from The address for which the message sender's allowance should be checked & updated.
  /// @param _value The amount of the allowance to check and decrement.
  function _checkAndUpdateAllowance(address _from, uint256 _value) internal virtual {
    uint256 allowed = allowance[_from][msg.sender];
    if (allowed != type(uint256).max) {
      allowance[_from][msg.sender] = allowed - _value;
    }
  }

  /// @notice Internal helper which checks that the message sender is either the delegatee guardian or the owner and
  /// reverts otherwise. If the caller is the owner, it also validates the guardian has never performed an action
  /// before, and reverts if it has. If the caller is the guardian, it toggles the guardian control flag to true if it
  /// hasn't yet been.
  function _checkAndToggleGuardianControlOrOwner() internal virtual {
    if (msg.sender != owner() && msg.sender != delegateeGuardian) {
      revert GovLst__Unauthorized();
    }
    if (msg.sender == owner() && isGuardianControlled) {
      revert GovLst__Unauthorized();
    }
    if (msg.sender == delegateeGuardian && !isGuardianControlled) {
      isGuardianControlled = true;
    }
  }

  /// @notice Internal helper which reverts if the caller is not the fixed lst contract.
  function _revertIfNotFixedLst() internal view virtual {
    if (msg.sender != address(FIXED_LST)) {
      revert GovLst__Unauthorized();
    }
  }

  /// @notice Internal helper which reverts if an action for example, updating a deposit, is taken on an invalid
  /// deposit.
  /// @param _depositId The id of the deposit to check.
  function _revertIfInvalidDeposit(Staker.DepositIdentifier _depositId) internal view {
    if (isOverridden[_depositId]) {
      revert GovLst__InvalidDeposit();
    }
    (uint96 _balance,, uint96 _earningPower,,,,) = STAKER.deposits(_depositId);
    bool _isBelowMin = uint256(_earningPower) * BIPS < minQualifyingEarningPowerBips * _balance;
    if (_isBelowMin) {
      revert GovLst__EarningPowerNotQualified(
        uint256(_earningPower) * BIPS, uint256(minQualifyingEarningPowerBips) * _balance
      );
    }

    if (_balance == 0) {
      revert GovLst__InvalidDeposit();
    }
  }

  /// @notice Internal helper function to calculate the fee amount based on the payout amount and fee percentage.
  /// @param _rewardParams The reward parameters containing payout amount and fee percentage.
  /// @return The calculated fee amount.
  function _calcFeeAmount(RewardParameters memory _rewardParams) internal pure virtual returns (uint256) {
    return (uint256(_rewardParams.payoutAmount) * uint256(_rewardParams.feeBips)) / BIPS;
  }

  /// @notice Internal convenience helper for comparing the equality of two staker DepositIdentifiers.
  /// @param _depositIdA The first deposit identifier.
  /// @param _depositIdB The second deposit identifier.
  /// @return True if the deposit identifiers are equal, false if they are different.
  function _isSameDepositId(Staker.DepositIdentifier _depositIdA, Staker.DepositIdentifier _depositIdB)
    internal
    pure
    virtual
    returns (bool)
  {
    return Staker.DepositIdentifier.unwrap(_depositIdA) == Staker.DepositIdentifier.unwrap(_depositIdB);
  }

  /// @notice Internal helper function to convert a Staker DepositIdentifier to a uint32
  /// @param _depositId The DepositIdentifier to convert
  /// @return The uint32 representation of the DepositIdentifier
  function _depositIdToUInt32(Staker.DepositIdentifier _depositId) internal pure virtual returns (uint32) {
    return SafeCast.toUint32(Staker.DepositIdentifier.unwrap(_depositId));
  }

  /// @notice Internal helper that returns the lesser of the two parameters passed.
  function _min(uint96 _a, uint96 _b) internal pure virtual returns (uint96) {
    return (_a < _b) ? _a : _b;
  }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

/// @title FixedLstAddressAlias
/// @author [ScopeLift](https://scopelift.co)
/// @notice Library code for calculating the alias of an address.
library FixedLstAddressAlias {
  /// @notice The constant value that is added to an existing address to calculate its offset before it is hashed.
  uint160 public constant ALIAS_OFFSET_SALT = 0x1000010100000110011011010010;

  /// @notice Calculates the alias of a given address by adding a fixed constant and hashing it.
  /// @param _account The address for which the alias will be calculated.
  /// @return _alias The calculated alias of the address provided.
  /// @dev Adding a fixed constant before hashing acts as a kind of "salt", ensuring the address produced is not simply
  /// the keccak256 of the address in question, as this might conflict in some unexpected way with some other
  /// application. At the same time, adding the "salt" to the address, rather than concatenating it, avoids adding
  /// extra bytes to the keccak operation, which would incur additional gas costs.
  function fixedAlias(address _account) internal pure returns (address _alias) {
    /// Overflow is desirable as addresses that are close to 0xFF...FF will overflow to a lower alias address.
    unchecked {
      _alias = address(uint160(uint256(keccak256(abi.encode(uint160(_account) + ALIAS_OFFSET_SALT)))));
    }
  }
}

File 7 of 33 : Staker.sol
// 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.
  /// @param _depositId Identifier of the deposit from which accrued rewards will be claimed.
  /// @return Amount of reward tokens claimed, after the fee has been assessed.
  function claimReward(DepositIdentifier _depositId) external virtual returns (uint256) {
    Deposit storage deposit = deposits[_depositId];
    if (deposit.claimer != msg.sender && deposit.owner != msg.sender) {
      revert Staker__Unauthorized("not claimer or owner", msg.sender);
    }
    return _claimReward(_depositId, deposit, msg.sender);
  }

  /// @notice Called by an authorized rewards notifier to alert the staking contract that a new
  /// reward has been transferred to it. It is assumed that the reward has already been
  /// transferred to this staking contract before the rewards notifier calls this method.
  /// @param _amount Quantity of reward tokens the staking contract is being notified of.
  /// @dev It is critical that only well behaved contracts are approved by the admin to call this
  /// method, for two reasons.
  ///
  /// 1. A misbehaving contract could grief stakers by frequently notifying this contract of tiny
  ///    rewards, thereby continuously stretching out the time duration over which real rewards are
  ///    distributed. It is required that reward notifiers supply reasonable rewards at reasonable
  ///    intervals.
  //  2. A misbehaving contract could falsely notify this contract of rewards that were not actually
  ///    distributed, creating a shortfall for those claiming their rewards after others. It is
  ///    required that a notifier contract always transfers the `_amount` to this contract before
  ///    calling this method.
  function notifyRewardAmount(uint256 _amount) external virtual {
    if (!isRewardNotifier[msg.sender]) revert Staker__Unauthorized("not notifier", msg.sender);

    // We checkpoint the accumulator without updating the timestamp at which it was updated,
    // because that second operation will be done after updating the reward rate.
    rewardPerTokenAccumulatedCheckpoint = rewardPerTokenAccumulated();

    if (block.timestamp >= rewardEndTime) {
      scaledRewardRate = (_amount * SCALE_FACTOR) / REWARD_DURATION;
    } else {
      uint256 _remainingReward = scaledRewardRate * (rewardEndTime - block.timestamp);
      scaledRewardRate = (_remainingReward + _amount * SCALE_FACTOR) / REWARD_DURATION;
    }

    rewardEndTime = block.timestamp + REWARD_DURATION;
    lastCheckpointTime = block.timestamp;

    if ((scaledRewardRate / SCALE_FACTOR) == 0) revert Staker__InvalidRewardRate();

    // This check cannot _guarantee_ sufficient rewards have been transferred to the contract,
    // because it cannot isolate the unclaimed rewards owed to stakers left in the balance. While
    // this check is useful for preventing degenerate cases, it is not sufficient. Therefore, it is
    // critical that only safe reward notifier contracts are approved to call this method by the
    // admin.
    if (
      (scaledRewardRate * REWARD_DURATION) > (REWARD_TOKEN.balanceOf(address(this)) * SCALE_FACTOR)
    ) revert Staker__InsufficientRewardBalance();

    emit RewardNotified(_amount, msg.sender);
  }

  /// @notice A function that a bumper can call to update a deposit's earning power when a
  /// qualifying change in the earning power is returned by the earning power calculator. A
  /// deposit's earning power may change as determined by the algorithm of the current earning power
  /// calculator. In order to incentivize bumpers to trigger these updates a portion of deposit's
  /// unclaimed rewards are sent to the bumper.
  /// @param _depositId The identifier for the deposit that needs an updated earning power.
  /// @param _tipReceiver The receiver of the reward for updating a deposit's earning power.
  /// @param _requestedTip The amount of tip requested by the third-party.
  function bumpEarningPower(
    DepositIdentifier _depositId,
    address _tipReceiver,
    uint256 _requestedTip
  ) external virtual {
    if (_requestedTip > maxBumpTip) revert Staker__InvalidTip();

    Deposit storage deposit = deposits[_depositId];

    _checkpointGlobalReward();
    _checkpointReward(deposit);

    uint256 _unclaimedRewards = deposit.scaledUnclaimedRewardCheckpoint / SCALE_FACTOR;

    (uint256 _newEarningPower, bool _isQualifiedForBump) = earningPowerCalculator.getNewEarningPower(
      deposit.balance, deposit.owner, deposit.delegatee, deposit.earningPower
    );
    if (!_isQualifiedForBump || _newEarningPower == deposit.earningPower) {
      revert Staker__Unqualified(_newEarningPower);
    }

    if (_newEarningPower > deposit.earningPower && _unclaimedRewards < _requestedTip) {
      revert Staker__InsufficientUnclaimedRewards();
    }

    // Note: underflow causes a revert if the requested  tip is more than unclaimed rewards
    if (_newEarningPower < deposit.earningPower && (_unclaimedRewards - _requestedTip) < maxBumpTip)
    {
      revert Staker__InsufficientUnclaimedRewards();
    }

    emit EarningPowerBumped(
      _depositId, deposit.earningPower, _newEarningPower, msg.sender, _tipReceiver, _requestedTip
    );

    // Update global earning power & deposit earning power based on this bump
    totalEarningPower =
      _calculateTotalEarningPower(deposit.earningPower, _newEarningPower, totalEarningPower);
    depositorTotalEarningPower[deposit.owner] = _calculateTotalEarningPower(
      deposit.earningPower, _newEarningPower, depositorTotalEarningPower[deposit.owner]
    );
    deposit.earningPower = _newEarningPower.toUint96();

    // Send tip to the receiver
    SafeERC20.safeTransfer(REWARD_TOKEN, _tipReceiver, _requestedTip);
    deposit.scaledUnclaimedRewardCheckpoint =
      deposit.scaledUnclaimedRewardCheckpoint - (_requestedTip * SCALE_FACTOR);
  }

  /// @notice Live value of the unclaimed rewards earned by a given deposit with the
  /// scale factor included. Used internally for calculating reward checkpoints while minimizing
  /// precision loss.
  /// @return Live value of the unclaimed rewards earned by a given deposit with the
  /// scale factor included.
  /// @dev See documentation for the public, non-scaled `unclaimedReward` method for more details.
  function _scaledUnclaimedReward(Deposit storage deposit) internal view virtual returns (uint256) {
    return deposit.scaledUnclaimedRewardCheckpoint
      + (deposit.earningPower * (rewardPerTokenAccumulated() - deposit.rewardPerTokenCheckpoint));
  }

  /// @notice Internal method which finds the existing surrogate contract—or deploys a new one if
  /// none exists—for a given delegatee.
  /// @param _delegatee Account for which a surrogate is sought.
  /// @return _surrogate The address of the surrogate contract for the delegatee.
  /// @dev A concrete implementation would either deploy a new delegate surrogate or return an
  /// existing surrogate for a given delegatee address.
  function _fetchOrDeploySurrogate(address _delegatee)
    internal
    virtual
    returns (DelegationSurrogate _surrogate);

  /// @notice Internal convenience method which calls the `transferFrom` method on the stake token
  /// contract and reverts on failure.
  /// @param _from Source account from which stake token is to be transferred.
  /// @param _to Destination account of the stake token which is to be transferred.
  /// @param _value Quantity of stake token which is to be transferred.
  function _stakeTokenSafeTransferFrom(address _from, address _to, uint256 _value) internal virtual {
    SafeERC20.safeTransferFrom(STAKE_TOKEN, _from, _to, _value);
  }

  /// @notice Internal method which generates and returns a unique, previously unused deposit
  /// identifier.
  /// @return _depositId Previously unused deposit identifier.
  function _useDepositId() internal virtual returns (DepositIdentifier _depositId) {
    _depositId = nextDepositId;
    nextDepositId = DepositIdentifier.wrap(DepositIdentifier.unwrap(_depositId) + 1);
  }

  /// @notice Internal convenience methods which performs the staking operations.
  /// @dev This method must only be called after proper authorization has been completed.
  /// @dev See public stake methods for additional documentation.
  function _stake(address _depositor, uint256 _amount, address _delegatee, address _claimer)
    internal
    virtual
    returns (DepositIdentifier _depositId)
  {
    _revertIfAddressZero(_delegatee);
    _revertIfAddressZero(_claimer);

    _checkpointGlobalReward();

    DelegationSurrogate _surrogate = _fetchOrDeploySurrogate(_delegatee);
    _depositId = _useDepositId();

    uint256 _earningPower = earningPowerCalculator.getEarningPower(_amount, _depositor, _delegatee);

    totalStaked += _amount;
    totalEarningPower += _earningPower;
    depositorTotalStaked[_depositor] += _amount;
    depositorTotalEarningPower[_depositor] += _earningPower;
    deposits[_depositId] = Deposit({
      balance: _amount.toUint96(),
      owner: _depositor,
      delegatee: _delegatee,
      claimer: _claimer,
      earningPower: _earningPower.toUint96(),
      rewardPerTokenCheckpoint: rewardPerTokenAccumulatedCheckpoint,
      scaledUnclaimedRewardCheckpoint: 0
    });
    _stakeTokenSafeTransferFrom(_depositor, address(_surrogate), _amount);
    emit StakeDeposited(_depositor, _depositId, _amount, _amount, _earningPower);
    emit ClaimerAltered(_depositId, address(0), _claimer, _earningPower);
    emit DelegateeAltered(_depositId, address(0), _delegatee, _earningPower);
  }

  /// @notice Internal convenience method which adds more stake to an existing deposit.
  /// @dev This method must only be called after proper authorization has been completed.
  /// @dev See public stakeMore methods for additional documentation.
  function _stakeMore(Deposit storage deposit, DepositIdentifier _depositId, uint256 _amount)
    internal
    virtual
  {
    _checkpointGlobalReward();
    _checkpointReward(deposit);

    DelegationSurrogate _surrogate = surrogates(deposit.delegatee);

    uint256 _newBalance = deposit.balance + _amount;
    uint256 _newEarningPower =
      earningPowerCalculator.getEarningPower(_newBalance, deposit.owner, deposit.delegatee);

    totalEarningPower =
      _calculateTotalEarningPower(deposit.earningPower, _newEarningPower, totalEarningPower);
    totalStaked += _amount;
    depositorTotalStaked[deposit.owner] += _amount;
    depositorTotalEarningPower[deposit.owner] = _calculateTotalEarningPower(
      deposit.earningPower, _newEarningPower, depositorTotalEarningPower[deposit.owner]
    );
    deposit.earningPower = _newEarningPower.toUint96();
    deposit.balance = _newBalance.toUint96();
    _stakeTokenSafeTransferFrom(deposit.owner, address(_surrogate), _amount);
    emit StakeDeposited(deposit.owner, _depositId, _amount, _newBalance, _newEarningPower);
  }

  /// @notice Internal convenience method which alters the delegatee of an existing deposit.
  /// @dev This method must only be called after proper authorization has been completed.
  /// @dev See public alterDelegatee methods for additional documentation.
  function _alterDelegatee(
    Deposit storage deposit,
    DepositIdentifier _depositId,
    address _newDelegatee
  ) internal virtual {
    _revertIfAddressZero(_newDelegatee);
    _checkpointGlobalReward();
    _checkpointReward(deposit);

    DelegationSurrogate _oldSurrogate = surrogates(deposit.delegatee);
    uint256 _newEarningPower =
      earningPowerCalculator.getEarningPower(deposit.balance, deposit.owner, _newDelegatee);

    totalEarningPower =
      _calculateTotalEarningPower(deposit.earningPower, _newEarningPower, totalEarningPower);
    depositorTotalEarningPower[deposit.owner] = _calculateTotalEarningPower(
      deposit.earningPower, _newEarningPower, depositorTotalEarningPower[deposit.owner]
    );

    emit DelegateeAltered(_depositId, deposit.delegatee, _newDelegatee, _newEarningPower);
    deposit.delegatee = _newDelegatee;
    deposit.earningPower = _newEarningPower.toUint96();
    DelegationSurrogate _newSurrogate = _fetchOrDeploySurrogate(_newDelegatee);
    _stakeTokenSafeTransferFrom(address(_oldSurrogate), address(_newSurrogate), deposit.balance);
  }

  /// @notice Internal convenience method which alters the claimer of an existing deposit.
  /// @dev This method must only be called after proper authorization has been completed.
  /// @dev See public alterClaimer methods for additional documentation.
  function _alterClaimer(Deposit storage deposit, DepositIdentifier _depositId, address _newClaimer)
    internal
    virtual
  {
    _revertIfAddressZero(_newClaimer);
    _checkpointGlobalReward();
    _checkpointReward(deposit);

    // Updating the earning power here is not strictly necessary, but if the user is touching their
    // deposit anyway, it seems reasonable to make sure their earning power is up to date.
    uint256 _newEarningPower =
      earningPowerCalculator.getEarningPower(deposit.balance, deposit.owner, deposit.delegatee);
    totalEarningPower =
      _calculateTotalEarningPower(deposit.earningPower, _newEarningPower, totalEarningPower);
    depositorTotalEarningPower[deposit.owner] = _calculateTotalEarningPower(
      deposit.earningPower, _newEarningPower, depositorTotalEarningPower[deposit.owner]
    );

    deposit.earningPower = _newEarningPower.toUint96();

    emit ClaimerAltered(_depositId, deposit.claimer, _newClaimer, _newEarningPower);
    deposit.claimer = _newClaimer;
  }

  /// @notice Internal convenience method which withdraws the stake from an existing deposit.
  /// @dev This method must only be called after proper authorization has been completed.
  /// @dev See public withdraw methods for additional documentation.
  function _withdraw(Deposit storage deposit, DepositIdentifier _depositId, uint256 _amount)
    internal
    virtual
  {
    _checkpointGlobalReward();
    _checkpointReward(deposit);

    // overflow prevents withdrawing more than balance
    uint256 _newBalance = deposit.balance - _amount;
    uint256 _newEarningPower =
      earningPowerCalculator.getEarningPower(_newBalance, deposit.owner, deposit.delegatee);

    totalStaked -= _amount;
    totalEarningPower =
      _calculateTotalEarningPower(deposit.earningPower, _newEarningPower, totalEarningPower);
    depositorTotalStaked[deposit.owner] -= _amount;
    depositorTotalEarningPower[deposit.owner] = _calculateTotalEarningPower(
      deposit.earningPower, _newEarningPower, depositorTotalEarningPower[deposit.owner]
    );

    deposit.balance = _newBalance.toUint96();
    deposit.earningPower = _newEarningPower.toUint96();
    _stakeTokenSafeTransferFrom(address(surrogates(deposit.delegatee)), deposit.owner, _amount);
    emit StakeWithdrawn(deposit.owner, _depositId, _amount, _newBalance, _newEarningPower);
  }

  /// @notice Internal convenience method which claims earned rewards.
  /// @return Amount of reward tokens claimed, after the claim fee has been assessed.
  /// @dev This method must only be called after proper authorization has been completed.
  /// @dev See public claimReward methods for additional documentation.
  function _claimReward(DepositIdentifier _depositId, Deposit storage deposit, address _claimer)
    internal
    virtual
    returns (uint256)
  {
    _checkpointGlobalReward();
    _checkpointReward(deposit);

    uint256 _reward = deposit.scaledUnclaimedRewardCheckpoint / SCALE_FACTOR;
    // Intentionally reverts due to overflow if unclaimed rewards are less than fee.
    uint256 _payout = _reward - claimFeeParameters.feeAmount;
    if (_payout == 0) return 0;

    // retain sub-wei dust that would be left due to the precision loss
    deposit.scaledUnclaimedRewardCheckpoint =
      deposit.scaledUnclaimedRewardCheckpoint - (_reward * SCALE_FACTOR);

    uint256 _newEarningPower =
      earningPowerCalculator.getEarningPower(deposit.balance, deposit.owner, deposit.delegatee);

    emit RewardClaimed(_depositId, _claimer, _payout, _newEarningPower);

    totalEarningPower =
      _calculateTotalEarningPower(deposit.earningPower, _newEarningPower, totalEarningPower);
    depositorTotalEarningPower[deposit.owner] = _calculateTotalEarningPower(
      deposit.earningPower, _newEarningPower, depositorTotalEarningPower[deposit.owner]
    );
    deposit.earningPower = _newEarningPower.toUint96();

    SafeERC20.safeTransfer(REWARD_TOKEN, _claimer, _payout);
    if (claimFeeParameters.feeAmount > 0) {
      SafeERC20.safeTransfer(
        REWARD_TOKEN, claimFeeParameters.feeCollector, claimFeeParameters.feeAmount
      );
    }
    return _payout;
  }

  /// @notice Checkpoints the global reward per token accumulator.
  function _checkpointGlobalReward() internal virtual {
    rewardPerTokenAccumulatedCheckpoint = rewardPerTokenAccumulated();
    lastCheckpointTime = lastTimeRewardDistributed();
  }

  /// @notice Checkpoints the unclaimed rewards and reward per token accumulator of a given
  /// deposit.
  /// @param deposit The deposit for which the reward parameters will be checkpointed.
  /// @dev This is a sensitive internal helper method that must only be called after global rewards
  /// accumulator has been checkpointed. It assumes the global `rewardPerTokenCheckpoint` is up to
  /// date.
  function _checkpointReward(Deposit storage deposit) internal virtual {
    deposit.scaledUnclaimedRewardCheckpoint = _scaledUnclaimedReward(deposit);
    deposit.rewardPerTokenCheckpoint = rewardPerTokenAccumulatedCheckpoint;
  }

  /// @notice Internal helper method which calculates and returns an updated value for total
  /// earning power based on the old and new earning power of a deposit which is being changed.
  /// @param _depositOldEarningPower The earning power of the deposit before a change is applied.
  /// @param _depositNewEarningPower The earning power of the deposit after a change is applied.
  /// @return _newTotalEarningPower The new total earning power.
  function _calculateTotalEarningPower(
    uint256 _depositOldEarningPower,
    uint256 _depositNewEarningPower,
    uint256 _totalEarningPower
  ) internal pure returns (uint256 _newTotalEarningPower) {
    return _totalEarningPower + _depositNewEarningPower - _depositOldEarningPower;
  }

  /// @notice Internal helper method which sets the admin address.
  /// @param _newAdmin Address of the new admin.
  function _setAdmin(address _newAdmin) internal virtual {
    _revertIfAddressZero(_newAdmin);
    emit AdminSet(admin, _newAdmin);
    admin = _newAdmin;
  }

  /// @notice Internal helper method which sets the earning power calculator address.
  function _setEarningPowerCalculator(address _newEarningPowerCalculator) internal virtual {
    _revertIfAddressZero(_newEarningPowerCalculator);
    emit EarningPowerCalculatorSet(address(earningPowerCalculator), _newEarningPowerCalculator);
    earningPowerCalculator = IEarningPowerCalculator(_newEarningPowerCalculator);
  }

  /// @notice Internal helper method which sets the max bump tip.
  /// @param _newMaxTip Value of the new max bump tip.
  function _setMaxBumpTip(uint256 _newMaxTip) internal virtual {
    emit MaxBumpTipSet(maxBumpTip, _newMaxTip);
    maxBumpTip = _newMaxTip;
  }

  /// @notice Internal helper method which sets the claim fee parameters.
  /// @param _params The new fee parameters.
  function _setClaimFeeParameters(ClaimFeeParameters memory _params) internal virtual {
    if (
      _params.feeAmount > MAX_CLAIM_FEE
        || (_params.feeCollector == address(0) && _params.feeAmount > 0)
    ) revert Staker__InvalidClaimFeeParameters();

    emit ClaimFeeParametersSet(
      claimFeeParameters.feeAmount,
      _params.feeAmount,
      claimFeeParameters.feeCollector,
      _params.feeCollector
    );

    claimFeeParameters = _params;
  }

  /// @notice Internal helper method which reverts Staker__Unauthorized if the message
  /// sender is not the admin.
  function _revertIfNotAdmin() internal view virtual {
    if (msg.sender != admin) revert Staker__Unauthorized("not admin", msg.sender);
  }

  /// @notice Internal helper method which reverts Staker__Unauthorized if the alleged
  /// owner is not the true owner of the deposit.
  /// @param deposit Deposit to validate.
  /// @param _owner Alleged owner of deposit.
  function _revertIfNotDepositOwner(Deposit storage deposit, address _owner) internal view virtual {
    if (_owner != deposit.owner) revert Staker__Unauthorized("not owner", _owner);
  }

  /// @notice Internal helper method which reverts with Staker__InvalidAddress if the
  /// account in question is address zero.
  /// @param _account Account to verify.
  function _revertIfAddressZero(address _account) internal pure {
    if (_account == address(0)) revert Staker__InvalidAddress();
  }
}

// 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);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface 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.
 *
 * ==== Security Considerations
 *
 * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
 * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
 * considered as an intention to spend the allowance in any specific way. The second is that because permits have
 * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
 * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
 * generally recommended is:
 *
 * ```solidity
 * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
 *     try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
 *     doThing(..., value);
 * }
 *
 * function doThing(..., uint256 value) public {
 *     token.safeTransferFrom(msg.sender, address(this), value);
 *     ...
 * }
 * ```
 *
 * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
 * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
 * {SafeERC20-safeTransferFrom}).
 *
 * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
 * contracts should have entry points that don't rely on permit.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     *
     * CAUTION: See Security Considerations above.
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}

File 10 of 33 : IERC20Metadata.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20Metadata.sol)

pragma solidity ^0.8.20;

import {IERC20Metadata} from "../token/ERC20/extensions/IERC20Metadata.sol";

// 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;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/EIP712.sol)

pragma solidity ^0.8.20;

import {MessageHashUtils} from "./MessageHashUtils.sol";
import {ShortStrings, ShortString} from "../ShortStrings.sol";
import {IERC5267} from "../../interfaces/IERC5267.sol";

/**
 * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.
 *
 * The encoding scheme specified in the EIP requires a domain separator and a hash of the typed structured data, whose
 * encoding is very generic and therefore its implementation in Solidity is not feasible, thus this contract
 * does not implement the encoding itself. Protocols need to implement the type-specific encoding they need in order to
 * produce the hash of their typed data using a combination of `abi.encode` and `keccak256`.
 *
 * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
 * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
 * ({_hashTypedDataV4}).
 *
 * The implementation of the domain separator was designed to be as efficient as possible while still properly updating
 * the chain id to protect against replay attacks on an eventual fork of the chain.
 *
 * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
 * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
 *
 * NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain
 * separator of the implementation contract. This will cause the {_domainSeparatorV4} function to always rebuild the
 * separator from the immutable values, which is cheaper than accessing a cached version in cold storage.
 *
 * @custom:oz-upgrades-unsafe-allow state-variable-immutable
 */
abstract contract EIP712 is IERC5267 {
    using ShortStrings for *;

    bytes32 private constant TYPE_HASH =
        keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");

    // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
    // invalidate the cached domain separator if the chain id changes.
    bytes32 private immutable _cachedDomainSeparator;
    uint256 private immutable _cachedChainId;
    address private immutable _cachedThis;

    bytes32 private immutable _hashedName;
    bytes32 private immutable _hashedVersion;

    ShortString private immutable _name;
    ShortString private immutable _version;
    string private _nameFallback;
    string private _versionFallback;

    /**
     * @dev Initializes the domain separator and parameter caches.
     *
     * The meaning of `name` and `version` is specified in
     * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]:
     *
     * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
     * - `version`: the current major version of the signing domain.
     *
     * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
     * contract upgrade].
     */
    constructor(string memory name, string memory version) {
        _name = name.toShortStringWithFallback(_nameFallback);
        _version = version.toShortStringWithFallback(_versionFallback);
        _hashedName = keccak256(bytes(name));
        _hashedVersion = keccak256(bytes(version));

        _cachedChainId = block.chainid;
        _cachedDomainSeparator = _buildDomainSeparator();
        _cachedThis = address(this);
    }

    /**
     * @dev Returns the domain separator for the current chain.
     */
    function _domainSeparatorV4() internal view returns (bytes32) {
        if (address(this) == _cachedThis && block.chainid == _cachedChainId) {
            return _cachedDomainSeparator;
        } else {
            return _buildDomainSeparator();
        }
    }

    function _buildDomainSeparator() private view returns (bytes32) {
        return keccak256(abi.encode(TYPE_HASH, _hashedName, _hashedVersion, block.chainid, address(this)));
    }

    /**
     * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
     * function returns the hash of the fully encoded EIP712 message for this domain.
     *
     * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
     *
     * ```solidity
     * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
     *     keccak256("Mail(address to,string contents)"),
     *     mailTo,
     *     keccak256(bytes(mailContents))
     * )));
     * address signer = ECDSA.recover(digest, signature);
     * ```
     */
    function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
        return MessageHashUtils.toTypedDataHash(_domainSeparatorV4(), structHash);
    }

    /**
     * @dev See {IERC-5267}.
     */
    function eip712Domain()
        public
        view
        virtual
        returns (
            bytes1 fields,
            string memory name,
            string memory version,
            uint256 chainId,
            address verifyingContract,
            bytes32 salt,
            uint256[] memory extensions
        )
    {
        return (
            hex"0f", // 01111
            _EIP712Name(),
            _EIP712Version(),
            block.chainid,
            address(this),
            bytes32(0),
            new uint256[](0)
        );
    }

    /**
     * @dev The name parameter for the EIP712 domain.
     *
     * NOTE: By default this function reads _name which is an immutable value.
     * It only reads from storage if necessary (in case the value is too large to fit in a ShortString).
     */
    // solhint-disable-next-line func-name-mixedcase
    function _EIP712Name() internal view returns (string memory) {
        return _name.toStringWithFallback(_nameFallback);
    }

    /**
     * @dev The version parameter for the EIP712 domain.
     *
     * NOTE: By default this function reads _version which is an immutable value.
     * It only reads from storage if necessary (in case the value is too large to fit in a ShortString).
     */
    // solhint-disable-next-line func-name-mixedcase
    function _EIP712Version() internal view returns (string memory) {
        return _version.toStringWithFallback(_versionFallback);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Nonces.sol)
pragma solidity ^0.8.20;

/**
 * @dev Provides tracking nonces for addresses. Nonces will only increment.
 */
abstract contract Nonces {
    /**
     * @dev The nonce used for an `account` is not the expected current nonce.
     */
    error InvalidAccountNonce(address account, uint256 currentNonce);

    mapping(address account => uint256) private _nonces;

    /**
     * @dev Returns the next unused nonce for an address.
     */
    function nonces(address owner) public view virtual returns (uint256) {
        return _nonces[owner];
    }

    /**
     * @dev Consumes a nonce.
     *
     * Returns the current value and increments nonce.
     */
    function _useNonce(address owner) internal virtual returns (uint256) {
        // For each account, the nonce has an initial value of 0, can only be incremented by one, and cannot be
        // decremented or reset. This guarantees that the nonce never overflows.
        unchecked {
            // It is important to do x++ and not ++x here.
            return _nonces[owner]++;
        }
    }

    /**
     * @dev Same as {_useNonce} but checking that `nonce` is the next valid for `owner`.
     */
    function _useCheckedNonce(address owner, uint256 nonce) internal virtual {
        uint256 current = _useNonce(owner);
        if (nonce != current) {
            revert InvalidAccountNonce(owner, current);
        }
    }
}

File 14 of 33 : Multicall.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Multicall.sol)

pragma solidity ^0.8.20;

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

/**
 * @dev Provides a function to batch together multiple calls in a single external call.
 *
 * Consider any assumption about calldata validation performed by the sender may be violated if it's not especially
 * careful about sending transactions invoking {multicall}. For example, a relay address that filters function
 * selectors won't filter calls nested within a {multicall} operation.
 *
 * NOTE: Since 5.0.1 and 4.9.4, this contract identifies non-canonical contexts (i.e. `msg.sender` is not {_msgSender}).
 * If a non-canonical context is identified, the following self `delegatecall` appends the last bytes of `msg.data`
 * to the subcall. This makes it safe to use with {ERC2771Context}. Contexts that don't affect the resolution of
 * {_msgSender} are not propagated to subcalls.
 */
abstract contract Multicall is Context {
    /**
     * @dev Receives and executes a batch of function calls on this contract.
     * @custom:oz-upgrades-unsafe-allow-reachable delegatecall
     */
    function multicall(bytes[] calldata data) external virtual returns (bytes[] memory results) {
        bytes memory context = msg.sender == _msgSender()
            ? new bytes(0)
            : msg.data[msg.data.length - _contextSuffixLength():];

        results = new bytes[](data.length);
        for (uint256 i = 0; i < data.length; i++) {
            results[i] = Address.functionDelegateCall(address(this), bytes.concat(data[i], context));
        }
        return results;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/SignatureChecker.sol)

pragma solidity ^0.8.20;

import {ECDSA} from "./ECDSA.sol";
import {IERC1271} from "../../interfaces/IERC1271.sol";

/**
 * @dev Signature verification helper that can be used instead of `ECDSA.recover` to seamlessly support both ECDSA
 * signatures from externally owned accounts (EOAs) as well as ERC1271 signatures from smart contract wallets like
 * Argent and Safe Wallet (previously Gnosis Safe).
 */
library SignatureChecker {
    /**
     * @dev Checks if a signature is valid for a given signer and data hash. If the signer is a smart contract, the
     * signature is validated against that smart contract using ERC1271, otherwise it's validated using `ECDSA.recover`.
     *
     * NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus
     * change through time. It could return true at block N and false at block N+1 (or the opposite).
     */
    function isValidSignatureNow(address signer, bytes32 hash, bytes memory signature) internal view returns (bool) {
        (address recovered, ECDSA.RecoverError error, ) = ECDSA.tryRecover(hash, signature);
        return
            (error == ECDSA.RecoverError.NoError && recovered == signer) ||
            isValidERC1271SignatureNow(signer, hash, signature);
    }

    /**
     * @dev Checks if a signature is valid for a given signer and data hash. The signature is validated
     * against the signer smart contract using ERC1271.
     *
     * NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus
     * change through time. It could return true at block N and false at block N+1 (or the opposite).
     */
    function isValidERC1271SignatureNow(
        address signer,
        bytes32 hash,
        bytes memory signature
    ) internal view returns (bool) {
        (bool success, bytes memory result) = signer.staticcall(
            abi.encodeCall(IERC1271.isValidSignature, (hash, signature))
        );
        return (success &&
            result.length >= 32 &&
            abi.decode(result, (bytes32)) == bytes32(IERC1271.isValidSignature.selector));
    }
}

// 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);
    }
}

File 17 of 33 : SafeCast.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SafeCast.sol)
// This file was procedurally generated from scripts/generate/templates/SafeCast.js.

pragma solidity ^0.8.20;

/**
 * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow
 * checks.
 *
 * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
 * easily result in undesired exploitation or bugs, since developers usually
 * assume that overflows raise errors. `SafeCast` restores this intuition by
 * reverting the transaction when such an operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 */
library SafeCast {
    /**
     * @dev Value doesn't fit in an uint of `bits` size.
     */
    error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value);

    /**
     * @dev An int value doesn't fit in an uint of `bits` size.
     */
    error SafeCastOverflowedIntToUint(int256 value);

    /**
     * @dev Value doesn't fit in an int of `bits` size.
     */
    error SafeCastOverflowedIntDowncast(uint8 bits, int256 value);

    /**
     * @dev An uint value doesn't fit in an int of `bits` size.
     */
    error SafeCastOverflowedUintToInt(uint256 value);

    /**
     * @dev Returns the downcasted uint248 from uint256, reverting on
     * overflow (when the input is greater than largest uint248).
     *
     * Counterpart to Solidity's `uint248` operator.
     *
     * Requirements:
     *
     * - input must fit into 248 bits
     */
    function toUint248(uint256 value) internal pure returns (uint248) {
        if (value > type(uint248).max) {
            revert SafeCastOverflowedUintDowncast(248, value);
        }
        return uint248(value);
    }

    /**
     * @dev Returns the downcasted uint240 from uint256, reverting on
     * overflow (when the input is greater than largest uint240).
     *
     * Counterpart to Solidity's `uint240` operator.
     *
     * Requirements:
     *
     * - input must fit into 240 bits
     */
    function toUint240(uint256 value) internal pure returns (uint240) {
        if (value > type(uint240).max) {
            revert SafeCastOverflowedUintDowncast(240, value);
        }
        return uint240(value);
    }

    /**
     * @dev Returns the downcasted uint232 from uint256, reverting on
     * overflow (when the input is greater than largest uint232).
     *
     * Counterpart to Solidity's `uint232` operator.
     *
     * Requirements:
     *
     * - input must fit into 232 bits
     */
    function toUint232(uint256 value) internal pure returns (uint232) {
        if (value > type(uint232).max) {
            revert SafeCastOverflowedUintDowncast(232, value);
        }
        return uint232(value);
    }

    /**
     * @dev Returns the downcasted uint224 from uint256, reverting on
     * overflow (when the input is greater than largest uint224).
     *
     * Counterpart to Solidity's `uint224` operator.
     *
     * Requirements:
     *
     * - input must fit into 224 bits
     */
    function toUint224(uint256 value) internal pure returns (uint224) {
        if (value > type(uint224).max) {
            revert SafeCastOverflowedUintDowncast(224, value);
        }
        return uint224(value);
    }

    /**
     * @dev Returns the downcasted uint216 from uint256, reverting on
     * overflow (when the input is greater than largest uint216).
     *
     * Counterpart to Solidity's `uint216` operator.
     *
     * Requirements:
     *
     * - input must fit into 216 bits
     */
    function toUint216(uint256 value) internal pure returns (uint216) {
        if (value > type(uint216).max) {
            revert SafeCastOverflowedUintDowncast(216, value);
        }
        return uint216(value);
    }

    /**
     * @dev Returns the downcasted uint208 from uint256, reverting on
     * overflow (when the input is greater than largest uint208).
     *
     * Counterpart to Solidity's `uint208` operator.
     *
     * Requirements:
     *
     * - input must fit into 208 bits
     */
    function toUint208(uint256 value) internal pure returns (uint208) {
        if (value > type(uint208).max) {
            revert SafeCastOverflowedUintDowncast(208, value);
        }
        return uint208(value);
    }

    /**
     * @dev Returns the downcasted uint200 from uint256, reverting on
     * overflow (when the input is greater than largest uint200).
     *
     * Counterpart to Solidity's `uint200` operator.
     *
     * Requirements:
     *
     * - input must fit into 200 bits
     */
    function toUint200(uint256 value) internal pure returns (uint200) {
        if (value > type(uint200).max) {
            revert SafeCastOverflowedUintDowncast(200, value);
        }
        return uint200(value);
    }

    /**
     * @dev Returns the downcasted uint192 from uint256, reverting on
     * overflow (when the input is greater than largest uint192).
     *
     * Counterpart to Solidity's `uint192` operator.
     *
     * Requirements:
     *
     * - input must fit into 192 bits
     */
    function toUint192(uint256 value) internal pure returns (uint192) {
        if (value > type(uint192).max) {
            revert SafeCastOverflowedUintDowncast(192, value);
        }
        return uint192(value);
    }

    /**
     * @dev Returns the downcasted uint184 from uint256, reverting on
     * overflow (when the input is greater than largest uint184).
     *
     * Counterpart to Solidity's `uint184` operator.
     *
     * Requirements:
     *
     * - input must fit into 184 bits
     */
    function toUint184(uint256 value) internal pure returns (uint184) {
        if (value > type(uint184).max) {
            revert SafeCastOverflowedUintDowncast(184, value);
        }
        return uint184(value);
    }

    /**
     * @dev Returns the downcasted uint176 from uint256, reverting on
     * overflow (when the input is greater than largest uint176).
     *
     * Counterpart to Solidity's `uint176` operator.
     *
     * Requirements:
     *
     * - input must fit into 176 bits
     */
    function toUint176(uint256 value) internal pure returns (uint176) {
        if (value > type(uint176).max) {
            revert SafeCastOverflowedUintDowncast(176, value);
        }
        return uint176(value);
    }

    /**
     * @dev Returns the downcasted uint168 from uint256, reverting on
     * overflow (when the input is greater than largest uint168).
     *
     * Counterpart to Solidity's `uint168` operator.
     *
     * Requirements:
     *
     * - input must fit into 168 bits
     */
    function toUint168(uint256 value) internal pure returns (uint168) {
        if (value > type(uint168).max) {
            revert SafeCastOverflowedUintDowncast(168, value);
        }
        return uint168(value);
    }

    /**
     * @dev Returns the downcasted uint160 from uint256, reverting on
     * overflow (when the input is greater than largest uint160).
     *
     * Counterpart to Solidity's `uint160` operator.
     *
     * Requirements:
     *
     * - input must fit into 160 bits
     */
    function toUint160(uint256 value) internal pure returns (uint160) {
        if (value > type(uint160).max) {
            revert SafeCastOverflowedUintDowncast(160, value);
        }
        return uint160(value);
    }

    /**
     * @dev Returns the downcasted uint152 from uint256, reverting on
     * overflow (when the input is greater than largest uint152).
     *
     * Counterpart to Solidity's `uint152` operator.
     *
     * Requirements:
     *
     * - input must fit into 152 bits
     */
    function toUint152(uint256 value) internal pure returns (uint152) {
        if (value > type(uint152).max) {
            revert SafeCastOverflowedUintDowncast(152, value);
        }
        return uint152(value);
    }

    /**
     * @dev Returns the downcasted uint144 from uint256, reverting on
     * overflow (when the input is greater than largest uint144).
     *
     * Counterpart to Solidity's `uint144` operator.
     *
     * Requirements:
     *
     * - input must fit into 144 bits
     */
    function toUint144(uint256 value) internal pure returns (uint144) {
        if (value > type(uint144).max) {
            revert SafeCastOverflowedUintDowncast(144, value);
        }
        return uint144(value);
    }

    /**
     * @dev Returns the downcasted uint136 from uint256, reverting on
     * overflow (when the input is greater than largest uint136).
     *
     * Counterpart to Solidity's `uint136` operator.
     *
     * Requirements:
     *
     * - input must fit into 136 bits
     */
    function toUint136(uint256 value) internal pure returns (uint136) {
        if (value > type(uint136).max) {
            revert SafeCastOverflowedUintDowncast(136, value);
        }
        return uint136(value);
    }

    /**
     * @dev Returns the downcasted uint128 from uint256, reverting on
     * overflow (when the input is greater than largest uint128).
     *
     * Counterpart to Solidity's `uint128` operator.
     *
     * Requirements:
     *
     * - input must fit into 128 bits
     */
    function toUint128(uint256 value) internal pure returns (uint128) {
        if (value > type(uint128).max) {
            revert SafeCastOverflowedUintDowncast(128, value);
        }
        return uint128(value);
    }

    /**
     * @dev Returns the downcasted uint120 from uint256, reverting on
     * overflow (when the input is greater than largest uint120).
     *
     * Counterpart to Solidity's `uint120` operator.
     *
     * Requirements:
     *
     * - input must fit into 120 bits
     */
    function toUint120(uint256 value) internal pure returns (uint120) {
        if (value > type(uint120).max) {
            revert SafeCastOverflowedUintDowncast(120, value);
        }
        return uint120(value);
    }

    /**
     * @dev Returns the downcasted uint112 from uint256, reverting on
     * overflow (when the input is greater than largest uint112).
     *
     * Counterpart to Solidity's `uint112` operator.
     *
     * Requirements:
     *
     * - input must fit into 112 bits
     */
    function toUint112(uint256 value) internal pure returns (uint112) {
        if (value > type(uint112).max) {
            revert SafeCastOverflowedUintDowncast(112, value);
        }
        return uint112(value);
    }

    /**
     * @dev Returns the downcasted uint104 from uint256, reverting on
     * overflow (when the input is greater than largest uint104).
     *
     * Counterpart to Solidity's `uint104` operator.
     *
     * Requirements:
     *
     * - input must fit into 104 bits
     */
    function toUint104(uint256 value) internal pure returns (uint104) {
        if (value > type(uint104).max) {
            revert SafeCastOverflowedUintDowncast(104, value);
        }
        return uint104(value);
    }

    /**
     * @dev Returns the downcasted uint96 from uint256, reverting on
     * overflow (when the input is greater than largest uint96).
     *
     * Counterpart to Solidity's `uint96` operator.
     *
     * Requirements:
     *
     * - input must fit into 96 bits
     */
    function toUint96(uint256 value) internal pure returns (uint96) {
        if (value > type(uint96).max) {
            revert SafeCastOverflowedUintDowncast(96, value);
        }
        return uint96(value);
    }

    /**
     * @dev Returns the downcasted uint88 from uint256, reverting on
     * overflow (when the input is greater than largest uint88).
     *
     * Counterpart to Solidity's `uint88` operator.
     *
     * Requirements:
     *
     * - input must fit into 88 bits
     */
    function toUint88(uint256 value) internal pure returns (uint88) {
        if (value > type(uint88).max) {
            revert SafeCastOverflowedUintDowncast(88, value);
        }
        return uint88(value);
    }

    /**
     * @dev Returns the downcasted uint80 from uint256, reverting on
     * overflow (when the input is greater than largest uint80).
     *
     * Counterpart to Solidity's `uint80` operator.
     *
     * Requirements:
     *
     * - input must fit into 80 bits
     */
    function toUint80(uint256 value) internal pure returns (uint80) {
        if (value > type(uint80).max) {
            revert SafeCastOverflowedUintDowncast(80, value);
        }
        return uint80(value);
    }

    /**
     * @dev Returns the downcasted uint72 from uint256, reverting on
     * overflow (when the input is greater than largest uint72).
     *
     * Counterpart to Solidity's `uint72` operator.
     *
     * Requirements:
     *
     * - input must fit into 72 bits
     */
    function toUint72(uint256 value) internal pure returns (uint72) {
        if (value > type(uint72).max) {
            revert SafeCastOverflowedUintDowncast(72, value);
        }
        return uint72(value);
    }

    /**
     * @dev Returns the downcasted uint64 from uint256, reverting on
     * overflow (when the input is greater than largest uint64).
     *
     * Counterpart to Solidity's `uint64` operator.
     *
     * Requirements:
     *
     * - input must fit into 64 bits
     */
    function toUint64(uint256 value) internal pure returns (uint64) {
        if (value > type(uint64).max) {
            revert SafeCastOverflowedUintDowncast(64, value);
        }
        return uint64(value);
    }

    /**
     * @dev Returns the downcasted uint56 from uint256, reverting on
     * overflow (when the input is greater than largest uint56).
     *
     * Counterpart to Solidity's `uint56` operator.
     *
     * Requirements:
     *
     * - input must fit into 56 bits
     */
    function toUint56(uint256 value) internal pure returns (uint56) {
        if (value > type(uint56).max) {
            revert SafeCastOverflowedUintDowncast(56, value);
        }
        return uint56(value);
    }

    /**
     * @dev Returns the downcasted uint48 from uint256, reverting on
     * overflow (when the input is greater than largest uint48).
     *
     * Counterpart to Solidity's `uint48` operator.
     *
     * Requirements:
     *
     * - input must fit into 48 bits
     */
    function toUint48(uint256 value) internal pure returns (uint48) {
        if (value > type(uint48).max) {
            revert SafeCastOverflowedUintDowncast(48, value);
        }
        return uint48(value);
    }

    /**
     * @dev Returns the downcasted uint40 from uint256, reverting on
     * overflow (when the input is greater than largest uint40).
     *
     * Counterpart to Solidity's `uint40` operator.
     *
     * Requirements:
     *
     * - input must fit into 40 bits
     */
    function toUint40(uint256 value) internal pure returns (uint40) {
        if (value > type(uint40).max) {
            revert SafeCastOverflowedUintDowncast(40, value);
        }
        return uint40(value);
    }

    /**
     * @dev Returns the downcasted uint32 from uint256, reverting on
     * overflow (when the input is greater than largest uint32).
     *
     * Counterpart to Solidity's `uint32` operator.
     *
     * Requirements:
     *
     * - input must fit into 32 bits
     */
    function toUint32(uint256 value) internal pure returns (uint32) {
        if (value > type(uint32).max) {
            revert SafeCastOverflowedUintDowncast(32, value);
        }
        return uint32(value);
    }

    /**
     * @dev Returns the downcasted uint24 from uint256, reverting on
     * overflow (when the input is greater than largest uint24).
     *
     * Counterpart to Solidity's `uint24` operator.
     *
     * Requirements:
     *
     * - input must fit into 24 bits
     */
    function toUint24(uint256 value) internal pure returns (uint24) {
        if (value > type(uint24).max) {
            revert SafeCastOverflowedUintDowncast(24, value);
        }
        return uint24(value);
    }

    /**
     * @dev Returns the downcasted uint16 from uint256, reverting on
     * overflow (when the input is greater than largest uint16).
     *
     * Counterpart to Solidity's `uint16` operator.
     *
     * Requirements:
     *
     * - input must fit into 16 bits
     */
    function toUint16(uint256 value) internal pure returns (uint16) {
        if (value > type(uint16).max) {
            revert SafeCastOverflowedUintDowncast(16, value);
        }
        return uint16(value);
    }

    /**
     * @dev Returns the downcasted uint8 from uint256, reverting on
     * overflow (when the input is greater than largest uint8).
     *
     * Counterpart to Solidity's `uint8` operator.
     *
     * Requirements:
     *
     * - input must fit into 8 bits
     */
    function toUint8(uint256 value) internal pure returns (uint8) {
        if (value > type(uint8).max) {
            revert SafeCastOverflowedUintDowncast(8, value);
        }
        return uint8(value);
    }

    /**
     * @dev Converts a signed int256 into an unsigned uint256.
     *
     * Requirements:
     *
     * - input must be greater than or equal to 0.
     */
    function toUint256(int256 value) internal pure returns (uint256) {
        if (value < 0) {
            revert SafeCastOverflowedIntToUint(value);
        }
        return uint256(value);
    }

    /**
     * @dev Returns the downcasted int248 from int256, reverting on
     * overflow (when the input is less than smallest int248 or
     * greater than largest int248).
     *
     * Counterpart to Solidity's `int248` operator.
     *
     * Requirements:
     *
     * - input must fit into 248 bits
     */
    function toInt248(int256 value) internal pure returns (int248 downcasted) {
        downcasted = int248(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(248, value);
        }
    }

    /**
     * @dev Returns the downcasted int240 from int256, reverting on
     * overflow (when the input is less than smallest int240 or
     * greater than largest int240).
     *
     * Counterpart to Solidity's `int240` operator.
     *
     * Requirements:
     *
     * - input must fit into 240 bits
     */
    function toInt240(int256 value) internal pure returns (int240 downcasted) {
        downcasted = int240(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(240, value);
        }
    }

    /**
     * @dev Returns the downcasted int232 from int256, reverting on
     * overflow (when the input is less than smallest int232 or
     * greater than largest int232).
     *
     * Counterpart to Solidity's `int232` operator.
     *
     * Requirements:
     *
     * - input must fit into 232 bits
     */
    function toInt232(int256 value) internal pure returns (int232 downcasted) {
        downcasted = int232(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(232, value);
        }
    }

    /**
     * @dev Returns the downcasted int224 from int256, reverting on
     * overflow (when the input is less than smallest int224 or
     * greater than largest int224).
     *
     * Counterpart to Solidity's `int224` operator.
     *
     * Requirements:
     *
     * - input must fit into 224 bits
     */
    function toInt224(int256 value) internal pure returns (int224 downcasted) {
        downcasted = int224(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(224, value);
        }
    }

    /**
     * @dev Returns the downcasted int216 from int256, reverting on
     * overflow (when the input is less than smallest int216 or
     * greater than largest int216).
     *
     * Counterpart to Solidity's `int216` operator.
     *
     * Requirements:
     *
     * - input must fit into 216 bits
     */
    function toInt216(int256 value) internal pure returns (int216 downcasted) {
        downcasted = int216(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(216, value);
        }
    }

    /**
     * @dev Returns the downcasted int208 from int256, reverting on
     * overflow (when the input is less than smallest int208 or
     * greater than largest int208).
     *
     * Counterpart to Solidity's `int208` operator.
     *
     * Requirements:
     *
     * - input must fit into 208 bits
     */
    function toInt208(int256 value) internal pure returns (int208 downcasted) {
        downcasted = int208(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(208, value);
        }
    }

    /**
     * @dev Returns the downcasted int200 from int256, reverting on
     * overflow (when the input is less than smallest int200 or
     * greater than largest int200).
     *
     * Counterpart to Solidity's `int200` operator.
     *
     * Requirements:
     *
     * - input must fit into 200 bits
     */
    function toInt200(int256 value) internal pure returns (int200 downcasted) {
        downcasted = int200(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(200, value);
        }
    }

    /**
     * @dev Returns the downcasted int192 from int256, reverting on
     * overflow (when the input is less than smallest int192 or
     * greater than largest int192).
     *
     * Counterpart to Solidity's `int192` operator.
     *
     * Requirements:
     *
     * - input must fit into 192 bits
     */
    function toInt192(int256 value) internal pure returns (int192 downcasted) {
        downcasted = int192(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(192, value);
        }
    }

    /**
     * @dev Returns the downcasted int184 from int256, reverting on
     * overflow (when the input is less than smallest int184 or
     * greater than largest int184).
     *
     * Counterpart to Solidity's `int184` operator.
     *
     * Requirements:
     *
     * - input must fit into 184 bits
     */
    function toInt184(int256 value) internal pure returns (int184 downcasted) {
        downcasted = int184(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(184, value);
        }
    }

    /**
     * @dev Returns the downcasted int176 from int256, reverting on
     * overflow (when the input is less than smallest int176 or
     * greater than largest int176).
     *
     * Counterpart to Solidity's `int176` operator.
     *
     * Requirements:
     *
     * - input must fit into 176 bits
     */
    function toInt176(int256 value) internal pure returns (int176 downcasted) {
        downcasted = int176(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(176, value);
        }
    }

    /**
     * @dev Returns the downcasted int168 from int256, reverting on
     * overflow (when the input is less than smallest int168 or
     * greater than largest int168).
     *
     * Counterpart to Solidity's `int168` operator.
     *
     * Requirements:
     *
     * - input must fit into 168 bits
     */
    function toInt168(int256 value) internal pure returns (int168 downcasted) {
        downcasted = int168(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(168, value);
        }
    }

    /**
     * @dev Returns the downcasted int160 from int256, reverting on
     * overflow (when the input is less than smallest int160 or
     * greater than largest int160).
     *
     * Counterpart to Solidity's `int160` operator.
     *
     * Requirements:
     *
     * - input must fit into 160 bits
     */
    function toInt160(int256 value) internal pure returns (int160 downcasted) {
        downcasted = int160(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(160, value);
        }
    }

    /**
     * @dev Returns the downcasted int152 from int256, reverting on
     * overflow (when the input is less than smallest int152 or
     * greater than largest int152).
     *
     * Counterpart to Solidity's `int152` operator.
     *
     * Requirements:
     *
     * - input must fit into 152 bits
     */
    function toInt152(int256 value) internal pure returns (int152 downcasted) {
        downcasted = int152(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(152, value);
        }
    }

    /**
     * @dev Returns the downcasted int144 from int256, reverting on
     * overflow (when the input is less than smallest int144 or
     * greater than largest int144).
     *
     * Counterpart to Solidity's `int144` operator.
     *
     * Requirements:
     *
     * - input must fit into 144 bits
     */
    function toInt144(int256 value) internal pure returns (int144 downcasted) {
        downcasted = int144(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(144, value);
        }
    }

    /**
     * @dev Returns the downcasted int136 from int256, reverting on
     * overflow (when the input is less than smallest int136 or
     * greater than largest int136).
     *
     * Counterpart to Solidity's `int136` operator.
     *
     * Requirements:
     *
     * - input must fit into 136 bits
     */
    function toInt136(int256 value) internal pure returns (int136 downcasted) {
        downcasted = int136(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(136, value);
        }
    }

    /**
     * @dev Returns the downcasted int128 from int256, reverting on
     * overflow (when the input is less than smallest int128 or
     * greater than largest int128).
     *
     * Counterpart to Solidity's `int128` operator.
     *
     * Requirements:
     *
     * - input must fit into 128 bits
     */
    function toInt128(int256 value) internal pure returns (int128 downcasted) {
        downcasted = int128(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(128, value);
        }
    }

    /**
     * @dev Returns the downcasted int120 from int256, reverting on
     * overflow (when the input is less than smallest int120 or
     * greater than largest int120).
     *
     * Counterpart to Solidity's `int120` operator.
     *
     * Requirements:
     *
     * - input must fit into 120 bits
     */
    function toInt120(int256 value) internal pure returns (int120 downcasted) {
        downcasted = int120(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(120, value);
        }
    }

    /**
     * @dev Returns the downcasted int112 from int256, reverting on
     * overflow (when the input is less than smallest int112 or
     * greater than largest int112).
     *
     * Counterpart to Solidity's `int112` operator.
     *
     * Requirements:
     *
     * - input must fit into 112 bits
     */
    function toInt112(int256 value) internal pure returns (int112 downcasted) {
        downcasted = int112(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(112, value);
        }
    }

    /**
     * @dev Returns the downcasted int104 from int256, reverting on
     * overflow (when the input is less than smallest int104 or
     * greater than largest int104).
     *
     * Counterpart to Solidity's `int104` operator.
     *
     * Requirements:
     *
     * - input must fit into 104 bits
     */
    function toInt104(int256 value) internal pure returns (int104 downcasted) {
        downcasted = int104(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(104, value);
        }
    }

    /**
     * @dev Returns the downcasted int96 from int256, reverting on
     * overflow (when the input is less than smallest int96 or
     * greater than largest int96).
     *
     * Counterpart to Solidity's `int96` operator.
     *
     * Requirements:
     *
     * - input must fit into 96 bits
     */
    function toInt96(int256 value) internal pure returns (int96 downcasted) {
        downcasted = int96(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(96, value);
        }
    }

    /**
     * @dev Returns the downcasted int88 from int256, reverting on
     * overflow (when the input is less than smallest int88 or
     * greater than largest int88).
     *
     * Counterpart to Solidity's `int88` operator.
     *
     * Requirements:
     *
     * - input must fit into 88 bits
     */
    function toInt88(int256 value) internal pure returns (int88 downcasted) {
        downcasted = int88(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(88, value);
        }
    }

    /**
     * @dev Returns the downcasted int80 from int256, reverting on
     * overflow (when the input is less than smallest int80 or
     * greater than largest int80).
     *
     * Counterpart to Solidity's `int80` operator.
     *
     * Requirements:
     *
     * - input must fit into 80 bits
     */
    function toInt80(int256 value) internal pure returns (int80 downcasted) {
        downcasted = int80(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(80, value);
        }
    }

    /**
     * @dev Returns the downcasted int72 from int256, reverting on
     * overflow (when the input is less than smallest int72 or
     * greater than largest int72).
     *
     * Counterpart to Solidity's `int72` operator.
     *
     * Requirements:
     *
     * - input must fit into 72 bits
     */
    function toInt72(int256 value) internal pure returns (int72 downcasted) {
        downcasted = int72(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(72, value);
        }
    }

    /**
     * @dev Returns the downcasted int64 from int256, reverting on
     * overflow (when the input is less than smallest int64 or
     * greater than largest int64).
     *
     * Counterpart to Solidity's `int64` operator.
     *
     * Requirements:
     *
     * - input must fit into 64 bits
     */
    function toInt64(int256 value) internal pure returns (int64 downcasted) {
        downcasted = int64(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(64, value);
        }
    }

    /**
     * @dev Returns the downcasted int56 from int256, reverting on
     * overflow (when the input is less than smallest int56 or
     * greater than largest int56).
     *
     * Counterpart to Solidity's `int56` operator.
     *
     * Requirements:
     *
     * - input must fit into 56 bits
     */
    function toInt56(int256 value) internal pure returns (int56 downcasted) {
        downcasted = int56(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(56, value);
        }
    }

    /**
     * @dev Returns the downcasted int48 from int256, reverting on
     * overflow (when the input is less than smallest int48 or
     * greater than largest int48).
     *
     * Counterpart to Solidity's `int48` operator.
     *
     * Requirements:
     *
     * - input must fit into 48 bits
     */
    function toInt48(int256 value) internal pure returns (int48 downcasted) {
        downcasted = int48(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(48, value);
        }
    }

    /**
     * @dev Returns the downcasted int40 from int256, reverting on
     * overflow (when the input is less than smallest int40 or
     * greater than largest int40).
     *
     * Counterpart to Solidity's `int40` operator.
     *
     * Requirements:
     *
     * - input must fit into 40 bits
     */
    function toInt40(int256 value) internal pure returns (int40 downcasted) {
        downcasted = int40(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(40, value);
        }
    }

    /**
     * @dev Returns the downcasted int32 from int256, reverting on
     * overflow (when the input is less than smallest int32 or
     * greater than largest int32).
     *
     * Counterpart to Solidity's `int32` operator.
     *
     * Requirements:
     *
     * - input must fit into 32 bits
     */
    function toInt32(int256 value) internal pure returns (int32 downcasted) {
        downcasted = int32(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(32, value);
        }
    }

    /**
     * @dev Returns the downcasted int24 from int256, reverting on
     * overflow (when the input is less than smallest int24 or
     * greater than largest int24).
     *
     * Counterpart to Solidity's `int24` operator.
     *
     * Requirements:
     *
     * - input must fit into 24 bits
     */
    function toInt24(int256 value) internal pure returns (int24 downcasted) {
        downcasted = int24(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(24, value);
        }
    }

    /**
     * @dev Returns the downcasted int16 from int256, reverting on
     * overflow (when the input is less than smallest int16 or
     * greater than largest int16).
     *
     * Counterpart to Solidity's `int16` operator.
     *
     * Requirements:
     *
     * - input must fit into 16 bits
     */
    function toInt16(int256 value) internal pure returns (int16 downcasted) {
        downcasted = int16(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(16, value);
        }
    }

    /**
     * @dev Returns the downcasted int8 from int256, reverting on
     * overflow (when the input is less than smallest int8 or
     * greater than largest int8).
     *
     * Counterpart to Solidity's `int8` operator.
     *
     * Requirements:
     *
     * - input must fit into 8 bits
     */
    function toInt8(int256 value) internal pure returns (int8 downcasted) {
        downcasted = int8(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(8, value);
        }
    }

    /**
     * @dev Converts an unsigned uint256 into a signed int256.
     *
     * Requirements:
     *
     * - input must be less than or equal to maxInt256.
     */
    function toInt256(uint256 value) internal pure returns (int256) {
        // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
        if (value > uint256(type(int256).max)) {
            revert SafeCastOverflowedUintToInt(value);
        }
        return int256(value);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
import {Multicall} from "@openzeppelin/contracts/utils/Multicall.sol";

/// @title WithdrawGate
/// @author ScopeLift
/// @notice A contract to enforce a withdrawal delay for users exiting the LST.
contract WithdrawGate is Ownable, Multicall, EIP712 {
  using SafeERC20 for IERC20;

  /// @notice Thrown when an invalid LST address is provided.
  error WithdrawGate__InvalidLSTAddress();

  /// @notice Thrown when an invalid delay is set.
  error WithdrawGate__InvalidDelay();

  /// @notice Thrown when the caller is not the LST.
  error WithdrawGate__CallerNotLST();

  /// @notice Thrown when the withdrawal is not found.
  error WithdrawGate__WithdrawalNotFound();

  /// @notice Thrown when the withdrawal is not yet eligible.
  error WithdrawGate__WithdrawalNotEligible();

  /// @notice Thrown when the withdrawal has already been completed.
  error WithdrawGate__WithdrawalAlreadyCompleted();

  /// @notice Thrown when the caller is not the designated receiver.
  error WithdrawGate__CallerNotReceiver();

  /// @notice Thrown when the signature is invalid.
  error WithdrawGate__InvalidSignature();

  /// @notice Thrown when the deadline has expired.
  error WithdrawGate__ExpiredDeadline();

  /// @notice The address of the LST contract.
  address public immutable LST;

  /// @notice The address of the token that can be withdrawn, assumed to revert on failed transfers.
  address public immutable WITHDRAWAL_TOKEN;

  /// @notice The maximum allowed delay for withdrawals.
  uint256 public constant DELAY_MAX = 30 days;

  /// @notice The current delay period for withdrawals.
  uint256 public delay;

  /// @notice The EIP-712 typehash for the CompleteWithdrawal struct.
  bytes32 public constant COMPLETE_WITHDRAWAL_TYPEHASH =
    keccak256("CompleteWithdrawal(uint256 identifier,uint256 deadline)");

  /// @notice A struct to store withdrawal information.
  struct Withdrawal {
    address receiver;
    uint96 amount;
    uint256 eligibleTimestamp;
  }

  /// @notice Mapping from withdrawal identifier to Withdrawal struct.
  mapping(uint256 withdrawId => Withdrawal withdrawal) public withdrawals;

  /// @notice Counter for generating unique withdrawal identifiers.
  uint256 internal nextWithdrawalId;

  /// @notice Emitted when the delay period is set.
  event DelaySet(uint256 oldDelay, uint256 newDelay);

  /// @notice Emitted when a withdrawal is initiated.
  event WithdrawalInitiated(uint256 amount, address receiver, uint256 eligibleTimestamp, uint256 identifier);

  /// @notice Emitted when a withdrawal is completed.
  event WithdrawalCompleted(uint256 identifier, address receiver, uint256 amount);

  /// @notice Initializes the WithdrawGate contract.
  /// @param _owner The address that will own this contract.
  /// @param _lst The address of the LST contract.
  /// @param _initialDelay The initial withdrawal delay period.
  constructor(address _owner, address _lst, address _withdrawalToken, uint256 _initialDelay)
    Ownable(_owner)
    EIP712("WithdrawGate", "1")
  {
    if (_lst == address(0)) {
      revert WithdrawGate__InvalidLSTAddress();
    }
    if (_initialDelay > DELAY_MAX) {
      revert WithdrawGate__InvalidDelay();
    }

    LST = _lst;
    WITHDRAWAL_TOKEN = _withdrawalToken;
    _setDelay(_initialDelay);
    nextWithdrawalId = 1;
  }

  /// @notice Sets a new delay period for withdrawals.
  /// @param _newDelay The new delay period to set.
  /// @dev Only the contract owner can call this function.
  /// @dev Reverts if the new delay exceeds DELAY_MAX.
  function setDelay(uint256 _newDelay) external virtual {
    _checkOwner();
    _setDelay(_newDelay);
  }

  /// @notice Internal function to set the delay period.
  /// @param _newDelay The new delay period to set.
  /// @dev Reverts if the new delay exceeds DELAY_MAX.
  function _setDelay(uint256 _newDelay) internal virtual {
    if (_newDelay > DELAY_MAX) {
      revert WithdrawGate__InvalidDelay();
    }

    uint256 _oldDelay = delay;

    emit DelaySet(_oldDelay, _newDelay);
    delay = _newDelay;
  }

  /// @notice Initiates a withdrawal for a user.
  /// @param _amount The amount of tokens to withdraw.
  /// @param _receiver The address that will receive the tokens.
  /// @return _identifier The unique identifier for this withdrawal.
  /// @dev Can only be called by the LST contract.
  /// @dev Assumes the WITHDRAW_TOKENs have already been transferred to this contract.
  function initiateWithdrawal(uint96 _amount, address _receiver) external virtual returns (uint256 _identifier) {
    if (msg.sender != LST) {
      revert WithdrawGate__CallerNotLST();
    }
    if (_receiver == address(0)) {
      revert WithdrawGate__CallerNotReceiver();
    }

    _identifier = nextWithdrawalId++;
    uint256 _eligibleTimestamp = block.timestamp + delay;

    withdrawals[_identifier] = Withdrawal({receiver: _receiver, amount: _amount, eligibleTimestamp: _eligibleTimestamp});

    emit WithdrawalInitiated(_amount, _receiver, _eligibleTimestamp, _identifier);
  }

  /// @notice Completes a previously initiated withdrawal.
  /// @param _identifier The unique identifier of the withdrawal to complete.
  function completeWithdrawal(uint256 _identifier) external virtual {
    if (nextWithdrawalId <= _identifier) {
      revert WithdrawGate__WithdrawalNotFound();
    }

    Withdrawal memory _withdrawal = withdrawals[_identifier];

    if (msg.sender != _withdrawal.receiver) {
      revert WithdrawGate__CallerNotReceiver();
    }

    _completeWithdrawal(_identifier, _withdrawal);
  }

  /// @notice Completes a previously initiated withdrawal on behalf of the receiver.
  /// @param _deadline The deadline by which the withdrawal must be completed.
  /// @param _identifier The unique identifier of the withdrawal to complete.
  /// @param _signature The EIP-712 or EIP-1271 signature authorizing the withdrawal.
  function completeWithdrawalOnBehalf(uint256 _identifier, uint256 _deadline, bytes memory _signature) external virtual {
    if (nextWithdrawalId <= _identifier) {
      revert WithdrawGate__WithdrawalNotFound();
    }

    Withdrawal memory _withdrawal = withdrawals[_identifier];
    if (block.timestamp > _deadline) {
      revert WithdrawGate__ExpiredDeadline();
    }

    bytes32 _structHash = keccak256(abi.encode(COMPLETE_WITHDRAWAL_TYPEHASH, _identifier, _deadline));
    bool _isValid =
      SignatureChecker.isValidSignatureNow(_withdrawal.receiver, _hashTypedDataV4(_structHash), _signature);
    if (!_isValid) {
      revert WithdrawGate__InvalidSignature();
    }

    _completeWithdrawal(_identifier, _withdrawal);
  }

  /// @notice Internal function to complete a withdrawal.
  /// @param _identifier The unique identifier of the withdrawal to complete.
  /// @param _withdrawal The memory reference to the Withdrawal struct.
  function _completeWithdrawal(uint256 _identifier, Withdrawal memory _withdrawal) internal virtual {
    if (block.timestamp < _withdrawal.eligibleTimestamp || _withdrawal.eligibleTimestamp == 0) {
      revert WithdrawGate__WithdrawalNotEligible();
    }

    // Clear the withdrawal by zeroing the eligibleTimestamp
    withdrawals[_identifier].eligibleTimestamp = 0;

    // This transfer assumes WITHDRAWAL_TOKEN will revert if the transfer fails.
    IERC20(WITHDRAWAL_TOKEN).safeTransfer(_withdrawal.receiver, _withdrawal.amount);

    emit WithdrawalCompleted(_identifier, _withdrawal.receiver, _withdrawal.amount);
  }

  /// @notice Gets the next withdrawal identifier.
  /// @return The next withdrawal identifier.
  function getNextWithdrawalId() external view virtual returns (uint256) {
    return nextWithdrawalId;
  }
}

File 19 of 33 : DelegationSurrogate.sol
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.23;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/// @title DelegationSurrogate
/// @author [ScopeLift](https://scopelift.co)
/// @notice A dead-simple contract whose only purpose is to hold ERC20 tokens which can always be
/// moved by the Surrogate's deployer.
abstract contract DelegationSurrogate {
  /// @param _token The token that will be held by this surrogate.
  constructor(IERC20 _token) {
    _token.approve(msg.sender, type(uint256).max);
  }
}

// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.23;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/// @title INotifiableRewardReceiver
/// @author [ScopeLift](https://scopelift.co)
/// @notice The communication interface between contracts that distribute rewards and the
/// Staker contract. In particular, said contracts only need to know the staker
/// implements the specified methods in order to forward payouts to the staker contract. The
/// Staker contract receives the rewards and abstracts the distribution mechanics.
interface INotifiableRewardReceiver {
  /// @notice ERC20 token in which rewards are denominated and distributed.
  function REWARD_TOKEN() external view returns (IERC20);

  /// @notice Method called to notify a reward receiver it has received a reward.
  /// @param _amount The amount of reward.
  function notifyRewardAmount(uint256 _amount) external;
}

// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.23;

/// @title IEarningPowerCalculator
/// @author [ScopeLift](https://scopelift.co)
/// @notice Interface to which Earning Power Calculators must conform in order to provide earning
/// power updates to an instance of Staker. Well behaving earning power calculators should:
///
/// 1. Be deterministic, i.e. produce the same output for the same input at a given time.
/// 2. Return values that are in the same order of magnitude as reasonable stake token amounts.
///    Avoid returning values that are dramatically detached from the staked amount.
/// 3. Avoid too much "churn" on earning power values, in particular, avoid returning "true" for
///    the `getNewEarningPower` method's `_isQualifiedForBump` too frequently, as such an earning
///    calculator would result in repeated bumps on a user's deposit, requiring excessive
///    monitoring on their behalf to avoid eating into their rewards.
interface IEarningPowerCalculator {
  /// @notice Returns the current earning power for a given staker, delegatee and staking amount.
  /// @param _amountStaked The amount of tokens staked.
  /// @param _staker The address of the staker.
  /// @param _delegatee The address of their chosen delegatee.
  /// @return _earningPower The calculated earning power.
  function getEarningPower(uint256 _amountStaked, address _staker, address _delegatee)
    external
    view
    returns (uint256 _earningPower);

  /// @notice Returns the current earning power for a given staker, delegatee, staking amount, and
  /// old earning power, along with a flag denoting whether the change in earning power warrants
  /// "bumping." Bumping means paying a third party a bit of the rewards to update the deposit's
  /// earning power on the depositor's behalf.
  /// @param _amountStaked The amount of tokens staked.
  /// @param _staker The address of the staker.
  /// @param _delegatee The address of their chosen delegatee.
  /// @param _oldEarningPower The earning power currently assigned to the deposit for which new
  /// earning power is being calculated.
  /// @return _newEarningPower The calculated earning power.
  /// @return _isQualifiedForBump A flag indicating whether or not this new earning power qualifies
  /// the deposit for having its earning power bumped.
  /// @dev Earning Power calculators should only "qualify" a bump when the difference warrants a
  /// forced update by a third party. This could be, for example, to reduce a deposit's earning
  /// power because their delegatee has become inactive. Even in these cases, a calculator should
  /// avoid qualifying for a bump too frequently. A calculator implementer may, for example, want
  /// to implement a grace period or a threshold difference before qualifying a deposit for a bump.
  function getNewEarningPower(
    uint256 _amountStaked,
    address _staker,
    address _delegatee,
    uint256 _oldEarningPower
  ) external view returns (uint256 _newEarningPower, bool _isQualifiedForBump);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)

pragma solidity ^0.8.20;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev The ETH balance of the account is not enough to perform the operation.
     */
    error AddressInsufficientBalance(address account);

    /**
     * @dev There's no code at `target` (it is not a contract).
     */
    error AddressEmptyCode(address target);

    /**
     * @dev A call to an address target failed. The target may have reverted.
     */
    error FailedInnerCall();

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        if (address(this).balance < amount) {
            revert AddressInsufficientBalance(address(this));
        }

        (bool success, ) = recipient.call{value: amount}("");
        if (!success) {
            revert FailedInnerCall();
        }
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason or custom error, it is bubbled
     * up by this function (like regular Solidity function calls). However, if
     * the call reverted with no returned reason, this function reverts with a
     * {FailedInnerCall} error.
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        if (address(this).balance < value) {
            revert AddressInsufficientBalance(address(this));
        }
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
     * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
     * unsuccessful call.
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata
    ) internal view returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            // only check if target is a contract if the call was successful and the return data is empty
            // otherwise we already know that it was a contract
            if (returndata.length == 0 && target.code.length == 0) {
                revert AddressEmptyCode(target);
            }
            return returndata;
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
     * revert reason or with a default {FailedInnerCall} error.
     */
    function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            return returndata;
        }
    }

    /**
     * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
     */
    function _revert(bytes memory returndata) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert FailedInnerCall();
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/MessageHashUtils.sol)

pragma solidity ^0.8.20;

import {Strings} from "../Strings.sol";

/**
 * @dev Signature message hash utilities for producing digests to be consumed by {ECDSA} recovery or signing.
 *
 * The library provides methods for generating a hash of a message that conforms to the
 * https://eips.ethereum.org/EIPS/eip-191[EIP 191] and https://eips.ethereum.org/EIPS/eip-712[EIP 712]
 * specifications.
 */
library MessageHashUtils {
    /**
     * @dev Returns the keccak256 digest of an EIP-191 signed data with version
     * `0x45` (`personal_sign` messages).
     *
     * The digest is calculated by prefixing a bytes32 `messageHash` with
     * `"\x19Ethereum Signed Message:\n32"` and hashing the result. It corresponds with the
     * hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method.
     *
     * NOTE: The `messageHash` parameter is intended to be the result of hashing a raw message with
     * keccak256, although any bytes32 value can be safely used because the final digest will
     * be re-hashed.
     *
     * See {ECDSA-recover}.
     */
    function toEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32 digest) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, "\x19Ethereum Signed Message:\n32") // 32 is the bytes-length of messageHash
            mstore(0x1c, messageHash) // 0x1c (28) is the length of the prefix
            digest := keccak256(0x00, 0x3c) // 0x3c is the length of the prefix (0x1c) + messageHash (0x20)
        }
    }

    /**
     * @dev Returns the keccak256 digest of an EIP-191 signed data with version
     * `0x45` (`personal_sign` messages).
     *
     * The digest is calculated by prefixing an arbitrary `message` with
     * `"\x19Ethereum Signed Message:\n" + len(message)` and hashing the result. It corresponds with the
     * hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method.
     *
     * See {ECDSA-recover}.
     */
    function toEthSignedMessageHash(bytes memory message) internal pure returns (bytes32) {
        return
            keccak256(bytes.concat("\x19Ethereum Signed Message:\n", bytes(Strings.toString(message.length)), message));
    }

    /**
     * @dev Returns the keccak256 digest of an EIP-191 signed data with version
     * `0x00` (data with intended validator).
     *
     * The digest is calculated by prefixing an arbitrary `data` with `"\x19\x00"` and the intended
     * `validator` address. Then hashing the result.
     *
     * See {ECDSA-recover}.
     */
    function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked(hex"19_00", validator, data));
    }

    /**
     * @dev Returns the keccak256 digest of an EIP-712 typed data (EIP-191 version `0x01`).
     *
     * The digest is calculated from a `domainSeparator` and a `structHash`, by prefixing them with
     * `\x19\x01` and hashing the result. It corresponds to the hash signed by the
     * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] JSON-RPC method as part of EIP-712.
     *
     * See {ECDSA-recover}.
     */
    function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 digest) {
        /// @solidity memory-safe-assembly
        assembly {
            let ptr := mload(0x40)
            mstore(ptr, hex"19_01")
            mstore(add(ptr, 0x02), domainSeparator)
            mstore(add(ptr, 0x22), structHash)
            digest := keccak256(ptr, 0x42)
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/ShortStrings.sol)

pragma solidity ^0.8.20;

import {StorageSlot} from "./StorageSlot.sol";

// | string  | 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA   |
// | length  | 0x                                                              BB |
type ShortString is bytes32;

/**
 * @dev This library provides functions to convert short memory strings
 * into a `ShortString` type that can be used as an immutable variable.
 *
 * Strings of arbitrary length can be optimized using this library if
 * they are short enough (up to 31 bytes) by packing them with their
 * length (1 byte) in a single EVM word (32 bytes). Additionally, a
 * fallback mechanism can be used for every other case.
 *
 * Usage example:
 *
 * ```solidity
 * contract Named {
 *     using ShortStrings for *;
 *
 *     ShortString private immutable _name;
 *     string private _nameFallback;
 *
 *     constructor(string memory contractName) {
 *         _name = contractName.toShortStringWithFallback(_nameFallback);
 *     }
 *
 *     function name() external view returns (string memory) {
 *         return _name.toStringWithFallback(_nameFallback);
 *     }
 * }
 * ```
 */
library ShortStrings {
    // Used as an identifier for strings longer than 31 bytes.
    bytes32 private constant FALLBACK_SENTINEL = 0x00000000000000000000000000000000000000000000000000000000000000FF;

    error StringTooLong(string str);
    error InvalidShortString();

    /**
     * @dev Encode a string of at most 31 chars into a `ShortString`.
     *
     * This will trigger a `StringTooLong` error is the input string is too long.
     */
    function toShortString(string memory str) internal pure returns (ShortString) {
        bytes memory bstr = bytes(str);
        if (bstr.length > 31) {
            revert StringTooLong(str);
        }
        return ShortString.wrap(bytes32(uint256(bytes32(bstr)) | bstr.length));
    }

    /**
     * @dev Decode a `ShortString` back to a "normal" string.
     */
    function toString(ShortString sstr) internal pure returns (string memory) {
        uint256 len = byteLength(sstr);
        // using `new string(len)` would work locally but is not memory safe.
        string memory str = new string(32);
        /// @solidity memory-safe-assembly
        assembly {
            mstore(str, len)
            mstore(add(str, 0x20), sstr)
        }
        return str;
    }

    /**
     * @dev Return the length of a `ShortString`.
     */
    function byteLength(ShortString sstr) internal pure returns (uint256) {
        uint256 result = uint256(ShortString.unwrap(sstr)) & 0xFF;
        if (result > 31) {
            revert InvalidShortString();
        }
        return result;
    }

    /**
     * @dev Encode a string into a `ShortString`, or write it to storage if it is too long.
     */
    function toShortStringWithFallback(string memory value, string storage store) internal returns (ShortString) {
        if (bytes(value).length < 32) {
            return toShortString(value);
        } else {
            StorageSlot.getStringSlot(store).value = value;
            return ShortString.wrap(FALLBACK_SENTINEL);
        }
    }

    /**
     * @dev Decode a string that was encoded to `ShortString` or written to storage using {setWithFallback}.
     */
    function toStringWithFallback(ShortString value, string storage store) internal pure returns (string memory) {
        if (ShortString.unwrap(value) != FALLBACK_SENTINEL) {
            return toString(value);
        } else {
            return store;
        }
    }

    /**
     * @dev Return the length of a string that was encoded to `ShortString` or written to storage using
     * {setWithFallback}.
     *
     * WARNING: This will return the "byte length" of the string. This may not reflect the actual length in terms of
     * actual characters as the UTF-8 encoding of a single character can span over multiple bytes.
     */
    function byteLengthWithFallback(ShortString value, string storage store) internal view returns (uint256) {
        if (ShortString.unwrap(value) != FALLBACK_SENTINEL) {
            return byteLength(value);
        } else {
            return bytes(store).length;
        }
    }
}

File 26 of 33 : IERC5267.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC5267.sol)

pragma solidity ^0.8.20;

interface IERC5267 {
    /**
     * @dev MAY be emitted to signal that the domain could have changed.
     */
    event EIP712DomainChanged();

    /**
     * @dev returns the fields and values that describe the domain separator used by this contract for EIP-712
     * signature.
     */
    function eip712Domain()
        external
        view
        returns (
            bytes1 fields,
            string memory name,
            string memory version,
            uint256 chainId,
            address verifyingContract,
            bytes32 salt,
            uint256[] memory extensions
        );
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/ECDSA.sol)

pragma solidity ^0.8.20;

/**
 * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
 *
 * These functions can be used to verify that a message was signed by the holder
 * of the private keys of a given address.
 */
library ECDSA {
    enum RecoverError {
        NoError,
        InvalidSignature,
        InvalidSignatureLength,
        InvalidSignatureS
    }

    /**
     * @dev The signature derives the `address(0)`.
     */
    error ECDSAInvalidSignature();

    /**
     * @dev The signature has an invalid length.
     */
    error ECDSAInvalidSignatureLength(uint256 length);

    /**
     * @dev The signature has an S value that is in the upper half order.
     */
    error ECDSAInvalidSignatureS(bytes32 s);

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not
     * return address(0) without also returning an error description. Errors are documented using an enum (error type)
     * and a bytes32 providing additional information about the error.
     *
     * If no error is returned, then the address can be used for verification purposes.
     *
     * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
     *
     * Documentation for signature generation:
     * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
     * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
     */
    function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError, bytes32) {
        if (signature.length == 65) {
            bytes32 r;
            bytes32 s;
            uint8 v;
            // ecrecover takes the signature parameters, and the only way to get them
            // currently is to use assembly.
            /// @solidity memory-safe-assembly
            assembly {
                r := mload(add(signature, 0x20))
                s := mload(add(signature, 0x40))
                v := byte(0, mload(add(signature, 0x60)))
            }
            return tryRecover(hash, v, r, s);
        } else {
            return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length));
        }
    }

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature`. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
     */
    function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
        (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature);
        _throwError(error, errorArg);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
     *
     * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
     */
    function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError, bytes32) {
        unchecked {
            bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
            // We do not check for an overflow here since the shift operation results in 0 or 1.
            uint8 v = uint8((uint256(vs) >> 255) + 27);
            return tryRecover(hash, v, r, s);
        }
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
     */
    function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
        (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs);
        _throwError(error, errorArg);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
     * `r` and `s` signature fields separately.
     */
    function tryRecover(
        bytes32 hash,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal pure returns (address, RecoverError, bytes32) {
        // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
        // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
        // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
        // signatures from current libraries generate a unique signature with an s-value in the lower half order.
        //
        // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
        // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
        // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
        // these malleable signatures as well.
        if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
            return (address(0), RecoverError.InvalidSignatureS, s);
        }

        // If the signature is valid (and not malleable), return the signer address
        address signer = ecrecover(hash, v, r, s);
        if (signer == address(0)) {
            return (address(0), RecoverError.InvalidSignature, bytes32(0));
        }

        return (signer, RecoverError.NoError, bytes32(0));
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `v`,
     * `r` and `s` signature fields separately.
     */
    function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
        (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s);
        _throwError(error, errorArg);
        return recovered;
    }

    /**
     * @dev Optionally reverts with the corresponding custom error according to the `error` argument provided.
     */
    function _throwError(RecoverError error, bytes32 errorArg) private pure {
        if (error == RecoverError.NoError) {
            return; // no error: do nothing
        } else if (error == RecoverError.InvalidSignature) {
            revert ECDSAInvalidSignature();
        } else if (error == RecoverError.InvalidSignatureLength) {
            revert ECDSAInvalidSignatureLength(uint256(errorArg));
        } else if (error == RecoverError.InvalidSignatureS) {
            revert ECDSAInvalidSignatureS(errorArg);
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1271.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC1271 standard signature validation method for
 * contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271].
 */
interface IERC1271 {
    /**
     * @dev Should return whether the signature provided is valid for the provided data
     * @param hash      Hash of the data to be signed
     * @param signature Signature byte array associated with _data
     */
    function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Strings.sol)

pragma solidity ^0.8.20;

import {Math} from "./math/Math.sol";
import {SignedMath} from "./math/SignedMath.sol";

/**
 * @dev String operations.
 */
library Strings {
    bytes16 private constant HEX_DIGITS = "0123456789abcdef";
    uint8 private constant ADDRESS_LENGTH = 20;

    /**
     * @dev The `value` string doesn't fit in the specified `length`.
     */
    error StringsInsufficientHexLength(uint256 value, uint256 length);

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        unchecked {
            uint256 length = Math.log10(value) + 1;
            string memory buffer = new string(length);
            uint256 ptr;
            /// @solidity memory-safe-assembly
            assembly {
                ptr := add(buffer, add(32, length))
            }
            while (true) {
                ptr--;
                /// @solidity memory-safe-assembly
                assembly {
                    mstore8(ptr, byte(mod(value, 10), HEX_DIGITS))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    /**
     * @dev Converts a `int256` to its ASCII `string` decimal representation.
     */
    function toStringSigned(int256 value) internal pure returns (string memory) {
        return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value)));
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        unchecked {
            return toHexString(value, Math.log256(value) + 1);
        }
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
        uint256 localValue = value;
        bytes memory buffer = new bytes(2 * length + 2);
        buffer[0] = "0";
        buffer[1] = "x";
        for (uint256 i = 2 * length + 1; i > 1; --i) {
            buffer[i] = HEX_DIGITS[localValue & 0xf];
            localValue >>= 4;
        }
        if (localValue != 0) {
            revert StringsInsufficientHexLength(value, length);
        }
        return string(buffer);
    }

    /**
     * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal
     * representation.
     */
    function toHexString(address addr) internal pure returns (string memory) {
        return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH);
    }

    /**
     * @dev Returns true if the two strings are equal.
     */
    function equal(string memory a, string memory b) internal pure returns (bool) {
        return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/StorageSlot.sol)
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.

pragma solidity ^0.8.20;

/**
 * @dev Library for reading and writing primitive types to specific storage slots.
 *
 * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
 * This library helps with reading and writing to such slots without the need for inline assembly.
 *
 * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
 *
 * Example usage to set ERC1967 implementation slot:
 * ```solidity
 * contract ERC1967 {
 *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
 *
 *     function _getImplementation() internal view returns (address) {
 *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
 *     }
 *
 *     function _setImplementation(address newImplementation) internal {
 *         require(newImplementation.code.length > 0);
 *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
 *     }
 * }
 * ```
 */
library StorageSlot {
    struct AddressSlot {
        address value;
    }

    struct BooleanSlot {
        bool value;
    }

    struct Bytes32Slot {
        bytes32 value;
    }

    struct Uint256Slot {
        uint256 value;
    }

    struct StringSlot {
        string value;
    }

    struct BytesSlot {
        bytes value;
    }

    /**
     * @dev Returns an `AddressSlot` with member `value` located at `slot`.
     */
    function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `BooleanSlot` with member `value` located at `slot`.
     */
    function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
     */
    function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `Uint256Slot` with member `value` located at `slot`.
     */
    function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `StringSlot` with member `value` located at `slot`.
     */
    function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `StringSlot` representation of the string storage pointer `store`.
     */
    function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := store.slot
        }
    }

    /**
     * @dev Returns an `BytesSlot` with member `value` located at `slot`.
     */
    function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
     */
    function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := store.slot
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol)

pragma solidity ^0.8.20;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    /**
     * @dev Muldiv operation overflow.
     */
    error MathOverflowedMulDiv();

    enum Rounding {
        Floor, // Toward negative infinity
        Ceil, // Toward positive infinity
        Trunc, // Toward zero
        Expand // Away from zero
    }

    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            uint256 c = a + b;
            if (c < a) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, with an overflow flag.
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b > a) return (false, 0);
            return (true, a - b);
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
            // benefit is lost if 'b' is also tested.
            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
            if (a == 0) return (true, 0);
            uint256 c = a * b;
            if (c / a != b) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a / b);
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a % b);
        }
    }

    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds towards infinity instead
     * of rounding towards zero.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        if (b == 0) {
            // Guarantee the same behavior as in a regular Solidity division.
            return a / b;
        }

        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a == 0 ? 0 : (a - 1) / b + 1;
    }

    /**
     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
     * denominator == 0.
     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
     * Uniswap Labs also under MIT license.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2^256 + prod0.
            uint256 prod0 = x * y; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                // The surrounding unchecked block does not change this fact.
                // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                return prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            if (denominator <= prod1) {
                revert MathOverflowedMulDiv();
            }

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0].
            uint256 remainder;
            assembly {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator and compute largest power of two divisor of denominator.
            // Always >= 1. See https://cs.stackexchange.com/q/138556/92363.

            uint256 twos = denominator & (0 - denominator);
            assembly {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [prod1 prod0] by twos.
                prod0 := div(prod0, twos)

                // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from prod1 into prod0.
            prod0 |= prod1 * twos;

            // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
            // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv = 1 mod 2^4.
            uint256 inverse = (3 * denominator) ^ 2;

            // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
            // works in modular arithmetic, doubling the correct bits in each step.
            inverse *= 2 - denominator * inverse; // inverse mod 2^8
            inverse *= 2 - denominator * inverse; // inverse mod 2^16
            inverse *= 2 - denominator * inverse; // inverse mod 2^32
            inverse *= 2 - denominator * inverse; // inverse mod 2^64
            inverse *= 2 - denominator * inverse; // inverse mod 2^128
            inverse *= 2 - denominator * inverse; // inverse mod 2^256

            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
            // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
            // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inverse;
            return result;
        }
    }

    /**
     * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
        uint256 result = mulDiv(x, y, denominator);
        if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) {
            result += 1;
        }
        return result;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
     * towards zero.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        //
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
        //
        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
        // into the expected uint128 result.
        unchecked {
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            return min(result, a / result);
        }
    }

    /**
     * @notice Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = sqrt(a);
            return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10 ** 64) {
                value /= 10 ** 64;
                result += 64;
            }
            if (value >= 10 ** 32) {
                value /= 10 ** 32;
                result += 32;
            }
            if (value >= 10 ** 16) {
                value /= 10 ** 16;
                result += 16;
            }
            if (value >= 10 ** 8) {
                value /= 10 ** 8;
                result += 8;
            }
            if (value >= 10 ** 4) {
                value /= 10 ** 4;
                result += 4;
            }
            if (value >= 10 ** 2) {
                value /= 10 ** 2;
                result += 2;
            }
            if (value >= 10 ** 1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);
        }
    }

    /**
     * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
     */
    function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
        return uint8(rounding) % 2 == 1;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SignedMath.sol)

pragma solidity ^0.8.20;

/**
 * @dev Standard signed math utilities missing in the Solidity language.
 */
library SignedMath {
    /**
     * @dev Returns the largest of two signed numbers.
     */
    function max(int256 a, int256 b) internal pure returns (int256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two signed numbers.
     */
    function min(int256 a, int256 b) internal pure returns (int256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two signed numbers without overflow.
     * The result is rounded towards zero.
     */
    function average(int256 a, int256 b) internal pure returns (int256) {
        // Formula from the book "Hacker's Delight"
        int256 x = (a & b) + ((a ^ b) >> 1);
        return x + (int256(uint256(x) >> 255) & (a ^ b));
    }

    /**
     * @dev Returns the absolute unsigned value of a signed value.
     */
    function abs(int256 n) internal pure returns (uint256) {
        unchecked {
            // must be unchecked in order to support `n = type(int256).min`
            return uint256(n >= 0 ? n : -n);
        }
    }
}

Settings
{
  "remappings": [
    "@openzeppelin/contracts/=lib/stGOV/lib/staker/lib/openzeppelin-contracts/contracts/",
    "ds-test/=lib/stGOV/lib/staker/lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/",
    "erc4626-tests/=lib/stGOV/lib/staker/lib/openzeppelin-contracts/lib/erc4626-tests/",
    "forge-std/=lib/forge-std/src/",
    "openzeppelin-contracts/=lib/stGOV/lib/staker/lib/openzeppelin-contracts/",
    "stGOV-test/=lib/stGOV/test/",
    "stGOV/=lib/stGOV/src/",
    "staker-test/=lib/stGOV/lib/staker/test/",
    "staker/=lib/stGOV/lib/staker/src/"
  ],
  "optimizer": {
    "enabled": true,
    "runs": 5083
  },
  "metadata": {
    "useLiteralContent": false,
    "bytecodeHash": "ipfs",
    "appendCBOR": true
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "evmVersion": "cancun",
  "viaIR": true,
  "libraries": {}
}

Contract Security Audit

Contract ABI

API
[{"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"string","name":"_symbol","type":"string"},{"internalType":"string","name":"_version","type":"string"},{"internalType":"contract GovLst","name":"_lst","type":"address"},{"internalType":"contract IERC20","name":"_stakeToken","type":"address"},{"internalType":"uint256","name":"_shareScaleFactor","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"AddressInsufficientBalance","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[],"name":"FixedGovLst__InsufficientBalance","type":"error"},{"inputs":[],"name":"FixedGovLst__InvalidSignature","type":"error"},{"inputs":[],"name":"FixedGovLst__SignatureExpired","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"currentNonce","type":"uint256"}],"name":"InvalidAccountNonce","type":"error"},{"inputs":[],"name":"InvalidShortString","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"inputs":[{"internalType":"string","name":"str","type":"string"}],"name":"StringTooLong","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"holder","type":"address"},{"indexed":false,"internalType":"Staker.DepositIdentifier","name":"oldDepositId","type":"uint256"},{"indexed":false,"internalType":"Staker.DepositIdentifier","name":"newDepositId","type":"uint256"}],"name":"DepositUpdated","type":"event"},{"anonymous":false,"inputs":[],"name":"EIP712DomainChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Fixed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Rescued","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Staked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Unfixed","type":"event"},{"inputs":[],"name":"CONVERT_TO_FIXED_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONVERT_TO_REBASING_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LST","outputs":[{"internalType":"contract GovLst","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PERMIT_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RESCUE_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SHARE_SCALE_FACTOR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_TOKEN","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UNSTAKE_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UPDATE_DEPOSIT_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"holder","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_spender","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_holder","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_lstTokens","type":"uint256"}],"name":"convertToFixed","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"uint256","name":"_nonce","type":"uint256"},{"internalType":"uint256","name":"_deadline","type":"uint256"},{"internalType":"bytes","name":"_signature","type":"bytes"}],"name":"convertToFixedOnBehalf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_fixedTokens","type":"uint256"}],"name":"convertToRebasing","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"uint256","name":"_nonce","type":"uint256"},{"internalType":"uint256","name":"_deadline","type":"uint256"},{"internalType":"bytes","name":"_signature","type":"bytes"}],"name":"convertToRebasingOnBehalf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"defaultDelegatee","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_delegatee","type":"address"}],"name":"delegate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_holder","type":"address"}],"name":"delegateeForHolder","outputs":[{"internalType":"address","name":"_delegatee","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_holder","type":"address"}],"name":"delegates","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_delegatee","type":"address"}],"name":"depositForDelegatee","outputs":[{"internalType":"Staker.DepositIdentifier","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"eip712Domain","outputs":[{"internalType":"bytes1","name":"fields","type":"bytes1"},{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"version","type":"string"},{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"address","name":"verifyingContract","type":"address"},{"internalType":"bytes32","name":"salt","type":"bytes32"},{"internalType":"uint256[]","name":"extensions","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_delegatee","type":"address"}],"name":"fetchOrInitializeDepositForDelegatee","outputs":[{"internalType":"Staker.DepositIdentifier","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"results","type":"bytes[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"address","name":"_spender","type":"address"},{"internalType":"uint256","name":"_value","type":"uint256"},{"internalType":"uint256","name":"_deadline","type":"uint256"},{"internalType":"uint8","name":"_v","type":"uint8"},{"internalType":"bytes32","name":"_r","type":"bytes32"},{"internalType":"bytes32","name":"_s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"uint256","name":"_deadline","type":"uint256"},{"internalType":"uint8","name":"_v","type":"uint8"},{"internalType":"bytes32","name":"_r","type":"bytes32"},{"internalType":"bytes32","name":"_s","type":"bytes32"}],"name":"permitAndStake","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rescue","outputs":[{"internalType":"uint256","name":"_fixedTokens","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"uint256","name":"_nonce","type":"uint256"},{"internalType":"uint256","name":"_deadline","type":"uint256"},{"internalType":"bytes","name":"_signature","type":"bytes"}],"name":"rescueOnBehalf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakeTokens","type":"uint256"}],"name":"stake","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"uint256","name":"_nonce","type":"uint256"},{"internalType":"uint256","name":"_deadline","type":"uint256"},{"internalType":"bytes","name":"_signature","type":"bytes"}],"name":"stakeOnBehalf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_fixedTokens","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_fixedTokens","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_fixedTokens","type":"uint256"}],"name":"unstake","outputs":[{"internalType":"uint256","name":"_stakeTokens","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"uint256","name":"_nonce","type":"uint256"},{"internalType":"uint256","name":"_deadline","type":"uint256"},{"internalType":"bytes","name":"_signature","type":"bytes"}],"name":"unstakeOnBehalf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"Staker.DepositIdentifier","name":"_newDepositId","type":"uint256"}],"name":"updateDeposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"Staker.DepositIdentifier","name":"_newDepositId","type":"uint256"},{"internalType":"uint256","name":"_nonce","type":"uint256"},{"internalType":"uint256","name":"_deadline","type":"uint256"},{"internalType":"bytes","name":"_signature","type":"bytes"}],"name":"updateDepositOnBehalf","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"version","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"}]

6101c0806040523461050a57613354803803809161001d828561050e565b833981019060c08183031261050a5780516001600160401b03811161050a5782610048918301610531565b60208201516001600160401b03811161050a5783610067918401610531565b604083015190936001600160401b03821161050a57610087918401610531565b606083015190916001600160a01b038216820361050a576080840151936001600160a01b038516850361050a5760a00151926100c282610586565b610120526100cf81610708565b6101405281516020830120908160e0526020815191012080610100524660a0526040519060208201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a0815261014060c08261050e565b5190206080523060c0528051906001600160401b03821161040d5760035490600182811c92168015610500575b60208310146103ef5781601f849311610492575b50602090601f831160011461042c575f92610421575b50508160011b915f199060031b1c1916176003555b83516001600160401b03811161040d57600454600181811c91168015610403575b60208210146103ef57601f811161038c575b50602094601f8211600114610329579481929394955f9261031e575b50508160011b915f199060031b1c1916176004555b610160526101a05261018052604051612b139081610841823960805181612249015260a05181612306015260c0518161221a015260e05181612298015261010051816122be01526101205181610d57015261014051818181610d81015261101d015261016051818181610ae001528181610f4701528181610fe0015281816113b2015281816114dd015281816118da01528181611ba601528181611c8701528181611d8a015281816120300152818161237301526126680152610180518181816112a901528181611415015261240a01526101a05181818161021401528181610ec20152818161145301528181611acd01528181611e2e01528181611faa0152818161214401526125080152f35b015190505f806101fb565b601f1982169560045f52805f20915f5b8881106103745750836001959697981061035c575b505050811b01600455610210565b01515f1960f88460031b161c191690555f808061034e565b91926020600181928685015181550194019201610339565b60045f527f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b601f830160051c810191602084106103e5575b601f0160051c01905b8181106103da57506101df565b5f81556001016103cd565b90915081906103c4565b634e487b7160e01b5f52602260045260245ffd5b90607f16906101cd565b634e487b7160e01b5f52604160045260245ffd5b015190505f80610197565b60035f9081528281209350601f198516905b81811061047a5750908460019594939210610462575b505050811b016003556101ac565b01515f1960f88460031b161c191690555f8080610454565b9293602060018192878601518155019501930161043e565b60035f529091507fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b601f840160051c810191602085106104f6575b90601f859493920160051c01905b8181106104e85750610181565b5f81558493506001016104db565b90915081906104cd565b91607f169161016d565b5f80fd5b601f909101601f19168101906001600160401b0382119082101761040d57604052565b81601f8201121561050a578051906001600160401b03821161040d5760405192610565601f8401601f19166020018561050e565b8284526020838301011161050a57815f9260208093018386015e8301015290565b908151602081105f14610600575090601f8151116105c05760208151910151602082106105b1571790565b5f198260200360031b1b161790565b604460209160405192839163305a27a960e01b83528160048401528051918291826024860152018484015e5f828201840152601f01601f19168101030190fd5b6001600160401b03811161040d575f54600181811c911680156106fe575b60208210146103ef57601f81116106cc575b50602092601f821160011461066d57928192935f92610662575b50508160011b915f199060031b1c1916175f5560ff90565b015190505f8061064a565b601f198216935f8052805f20915f5b8681106106b4575083600195961061069c575b505050811b015f5560ff90565b01515f1960f88460031b161c191690555f808061068f565b9192602060018192868501518155019401920161067c565b5f8052601f60205f20910160051c810190601f830160051c015b8181106106f35750610630565b5f81556001016106e6565b90607f169061061e565b908151602081105f14610733575090601f8151116105c05760208151910151602082106105b1571790565b6001600160401b03811161040d57600154600181811c91168015610836575b60208210146103ef57601f8111610803575b50602092601f82116001146107a257928192935f92610797575b50508160011b915f199060031b1c19161760015560ff90565b015190505f8061077e565b601f1982169360015f52805f20915f5b8681106107eb57508360019596106107d3575b505050811b0160015560ff90565b01515f1960f88460031b161c191690555f80806107c5565b919260206001819286850151815501940192016107b2565b60015f52601f60205f20910160051c810190601f830160051c015b81811061082b5750610764565b5f815560010161081e565b90607f169061075256fe60806040526004361015610011575f80fd5b5f5f3560e01c80626b2b591461170457806306fdde031461163a5780630700cc4214611600578063095ea7b3146115885780630afe3fed1461154e5780630c4cb55c14611497578063119e9d641461147857806318160ddd146114395780631c39b672146113f65780631ce9f923146113545780631ddec39b1461127d5780631fbe19791461126157806323b872dd146111a757806327cb8f521461110e5780632e17de78146110ee57806330adf81f146110b3578063313ce567146110975780633259c9141461105c5780633644e5151461104157806354fd4d501461100457806355d5c37b14610fc0578063587cde1e14610c645780635c19a95c14610ee757806370a0823114610e8d5780637ecebe0014610e5557806384b0196e14610d3d57806389ee09f014610d02578063921630c314610c69578063942ff14014610c6457806395d89b4114610b7d578063a694fc3a14610b5d578063a79f5b1014610a7f578063a88898e714610a44578063a9059cbb14610a12578063ac9650d8146107fe578063b601bbc3146107c3578063bc34ee22146106ca578063c782767b146106aa578063cbf0f205146105e6578063d505accf14610420578063dd62ed3e146103d2578063df1eed0d14610339578063ee836c351461023a5763f5706759146101fd575f80fd5b3461023757806003193601126102375760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b80fd5b50346102375761024936611826565b91906102588286979597612733565b804211610311576102d092916102ca916040519060208201927f801afda14e54c9b981ce6b7101d75bf8df9b294ef01d71741120c1dd942cb6cb84526001600160a01b0389166040840152896060840152608083015260a082015260a081526102c260c082611774565b5190206126df565b8461279a565b156102e95760206102e18484612616565b604051908152f35b807ff35976ef0000000000000000000000000000000000000000000000000000000060049252fd5b6004847f6cab85c7000000000000000000000000000000000000000000000000000000008152fd5b50346102375761034836611826565b91906103578286979597612733565b804211610311576103c192916102ca916040519060208201927fad9c22b83dd1cf60bfdcf34513024790267f8ac5bf377e3ce558e853a641228b84526001600160a01b0389166040840152896060840152608083015260a082015260a081526102c260c082611774565b156102e95760206102e18484612136565b5034610237576040600319360112610237576001600160a01b0360406103f6611748565b928261040061175e565b9416815260076020522091165f52602052602060405f2054604051908152f35b50346102375760e06003193601126102375761043a611748565b61044261175e565b604435606435926084359060ff821682036105e2578442116105ba576020916104ee8261048a89946001600160a01b03165f52600260205260405f2080549060018201905590565b97604051906001600160a01b0380888401947f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9865216998a604085015216998a606084015288608084015260a083015260c082015260c081526102c260e082611774565b6040805191825260ff92909216602082015260a4359181019190915260c435606082015281805260809060015afa156105af576001600160a01b03845116801580156105a5575b61057d577f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259160209186526007825260408620855f5282528060405f2055604051908152a380f35b6004857ff35976ef000000000000000000000000000000000000000000000000000000008152fd5b5082811415610535565b6040513d85823e3d90fd5b6004867f6cab85c7000000000000000000000000000000000000000000000000000000008152fd5b8580fd5b503461023757608060031936011261023757610600611748565b9060443560243560643567ffffffffffffffff81116106a6576106279036906004016117e0565b916106328286612733565b8042116103115761069692916102ca916040519060208201927f1c24a0d6d5e5b7973590781b8f5836f26e13ce0b3dc6852eefc1114048c0fe0e84526001600160a01b038916604084015260608301526080820152608081526102c260a082611774565b156102e95760206102e183611d80565b8380fd5b50346102375760206003193601126102375760206102e160043533612616565b50346102375760a0600319360112610237576106e4611748565b6024359060643560443560843567ffffffffffffffff81116105e25761070e9036906004016117e0565b916107198285612733565b8042116105ba576107899291610783916040519060208201927f37d81b40a9b05b0bc4bdb720ed5e8ef5f66be2ba9af9fbaef13adbba9181f25e84526001600160a01b0388166040840152886060840152608083015260a082015260a081526102c260c082611774565b8361279a565b1561079b579061079891611c38565b80f35b6004837ff35976ef000000000000000000000000000000000000000000000000000000008152fd5b503461023757806003193601126102375760206040517f1c24a0d6d5e5b7973590781b8f5836f26e13ce0b3dc6852eefc1114048c0fe0e8152f35b50346102375760206003193601126102375760043567ffffffffffffffff8111610a0e5736602382011215610a0e57806004013567ffffffffffffffff8111610a0a573660248260051b84010111610a0a579060206040516108608282611774565b84815281810191601f19810136843761087885611a2c565b936108866040519586611774565b858552601f1961089587611a2c565b01875b8181106109fb57505086907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbd81360301915b8781101561097e5760248160051b830101358381121561097a5782019060248201359167ffffffffffffffff8311610976576044018a8336038213610237578061095a92896109466001978b8e6040519483869484860198893784019083820190898252519283915e010185815203601f198101835282611774565b5190305af46109536125e7565b9030612983565b610964828a611a7e565b5261096f8189611a7e565b50016108ca565b8a80fd5b8980fd5b83898860405191838301848452825180915260408401948060408360051b870101940192955b8287106109b15785850386f35b9091929382806109eb837fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc08a600196030186528851611723565b96019201960195929190926109a4565b60608782018501528301610898565b8280fd5b5080fd5b503461023757604060031936011261023757610a39610a2f611748565b6024359033611f8b565b602060405160018152f35b503461023757806003193601126102375760206040517f50939ff8118ce6ab8bd952f323a3982b869c7b50787cbc6bbc9e57d13afa9ca18152f35b503461023757602060031936011261023757610a99611748565b906001600160a01b03604051927fa79f5b10000000000000000000000000000000000000000000000000000000008452166004830152602082602481846001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165af1908115610b515790610b1a575b602090604051908152f35b506020813d602011610b49575b81610b3460209383611774565b81010312610b455760209051610b0f565b5f80fd5b3d9150610b27565b604051903d90823e3d90fd5b50346102375760206003193601126102375760206102e160043533612369565b5034610237578060031936011261023757604051908060045490610ba08261193a565b8085529160018116908115610c3d5750600114610be0575b610bdc84610bc881860382611774565b604051918291602083526020830190611723565b0390f35b600481527f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b939250905b808210610c2357509091508101602001610bc882610bb8565b919260018160209254838588010152019101909291610c0a565b60ff191660208087019190915292151560051b85019092019250610bc89150839050610bb8565b611874565b503461023757610c7836611826565b9190610c878286979597612733565b80421161031157610cf192916102ca916040519060208201927fa461f2f1e741cadd3094f8eabe3ba7e04633c3f9a954bf1bec554224fe5ed3c584526001600160a01b0389166040840152896060840152608083015260a082015260a081526102c260c082611774565b156102e95760206102e18484612369565b503461023757806003193601126102375760206040517fa461f2f1e741cadd3094f8eabe3ba7e04633c3f9a954bf1bec554224fe5ed3c58152f35b5034610237578060031936011261023757610df990610d7b7f000000000000000000000000000000000000000000000000000000000000000061294d565b90610da57f00000000000000000000000000000000000000000000000000000000000000006128d4565b906020610e0760405193610db98386611774565b8385525f3681376040519687967f0f00000000000000000000000000000000000000000000000000000000000000885260e08589015260e0880190611723565b908682036040880152611723565b904660608601523060808601528260a086015284820360c08601528080855193848152019401925b828110610e3e57505050500390f35b835185528695509381019392810192600101610e2f565b50346102375760206003193601126102375760406020916001600160a01b03610e7c611748565b168152600283522054604051908152f35b5034610237576020600319360112610237576102e160406020926001600160a01b03610eb7611748565b1681526005845220547f000000000000000000000000000000000000000000000000000000000000000090611d3c565b503461023757602060031936011261023757610f01611748565b6001600160a01b03604051917fa79f5b10000000000000000000000000000000000000000000000000000000008352166004820152602081602481856001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165af18015610fb5578290610f81575b610798915033611c38565b506020813d602011610fad575b81610f9b60209383611774565b81010312610b45576107989051610f76565b3d9150610f8e565b6040513d84823e3d90fd5b503461023757806003193601126102375760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b5034610237578060031936011261023757610bdc610bc87f00000000000000000000000000000000000000000000000000000000000000006128d4565b503461023757806003193601126102375760206102e1612210565b503461023757806003193601126102375760206040517f37d81b40a9b05b0bc4bdb720ed5e8ef5f66be2ba9af9fbaef13adbba9181f25e8152f35b5034610237578060031936011261023757602060405160128152f35b503461023757806003193601126102375760206040517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98152f35b50346102375760206003193601126102375760206102e160043533612136565b50346102375761111d36611826565b919061112c8286979597612733565b8042116103115761119692916102ca916040519060208201927f50939ff8118ce6ab8bd952f323a3982b869c7b50787cbc6bbc9e57d13afa9ca184526001600160a01b0389166040840152896060840152608083015260a082015260a081526102c260c082611774565b156102e95760206102e18484611abf565b503461023757606060031936011261023757610a39906111c5611748565b6111cd61175e565b9060443592836001600160a01b038316918281526007602052604081206001600160a01b0333165f5260205260405f20547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff810361122f575b50505050611f8b565b60409261123b91611a44565b9281526007602052206001600160a01b0333165f5260205260405f20555f838180611226565b503461023757806003193601126102375760206102e133611d80565b34610b455760a0600319360112610b455760043560443560ff8116809103610b45576001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691823b15610b45575f8060209460e46102e19560405194859384927fd505accf0000000000000000000000000000000000000000000000000000000084523360048501523060248501528860448501526024356064850152608484015260643560a484015260843560c48401525af1611344575b5033612369565b5f61134e91611774565b5f61133d565b34610b45576020600319360112610b455761136d611748565b6001600160a01b03604051917f1ce9f9230000000000000000000000000000000000000000000000000000000083521660048201526020816024816001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165afa80156113eb575f90610b1a57602090604051908152f35b6040513d5f823e3d90fd5b34610b45575f600319360112610b455760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b34610b45575f600319360112610b455760206102e16006547f000000000000000000000000000000000000000000000000000000000000000090611d3c565b34610b45576020600319360112610b455761149560043533611c38565b005b34610b45575f600319360112610b45576040517f0c4cb55c0000000000000000000000000000000000000000000000000000000081526020816004816001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165afa80156113eb576020915f91611521575b506001600160a01b0360405191168152f35b6115419150823d8411611547575b6115398183611774565b810190611a0d565b8261150f565b503d61152f565b34610b45575f600319360112610b455760206040517f801afda14e54c9b981ce6b7101d75bf8df9b294ef01d71741120c1dd942cb6cb8152f35b34610b45576040600319360112610b45576115a1611748565b6001600160a01b0360243591335f52600760205260405f208282165f526020528260405f205560405192835216907f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560203392a3602060405160018152f35b34610b45575f600319360112610b455760206040517fad9c22b83dd1cf60bfdcf34513024790267f8ac5bf377e3ce558e853a641228b8152f35b34610b45575f600319360112610b45576040515f60035461165a8161193a565b80845290600181169081156116e05750600114611682575b610bdc83610bc881850382611774565b60035f9081527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b939250905b8082106116c657509091508101602001610bc8611672565b9192600181602092548385880101520191019092916116ae565b60ff191660208086019190915291151560051b84019091019150610bc89050611672565b34610b45576020600319360112610b455760206102e160043533611abf565b90601f19601f602080948051918291828752018686015e5f8582860101520116010190565b600435906001600160a01b0382168203610b4557565b602435906001600160a01b0382168203610b4557565b90601f601f19910116810190811067ffffffffffffffff82111761179757604052565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b67ffffffffffffffff811161179757601f01601f191660200190565b81601f82011215610b45578035906117f7826117c4565b926118056040519485611774565b82845260208383010111610b4557815f926020809301838601378301015290565b60a0600319820112610b45576004356001600160a01b0381168103610b4557916024359160443591606435916084359067ffffffffffffffff8211610b4557611871916004016117e0565b90565b34610b45576020600319360112610b4557611895611890611748565b61232c565b6001600160a01b03604051917f942ff1400000000000000000000000000000000000000000000000000000000083521660048201526020816024816001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165afa80156113eb576020915f9161191d57506001600160a01b0360405191168152f35b6119349150823d8411611547576115398183611774565b5f61150f565b90600182811c92168015611981575b602083101461195457565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b91607f1691611949565b5f929181549161199a8361193a565b80835292600181169081156119ef57506001146119b657505050565b5f9081526020812093945091925b8383106119d5575060209250010190565b6001816020929493945483858701015201910191906119c4565b9050602094955060ff1991509291921683830152151560051b010190565b90816020910312610b4557516001600160a01b0381168103610b455790565b67ffffffffffffffff81116117975760051b60200190565b91908203918211611a5157565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b8051821015611a925760209160051b010190565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b6020611b999281925f611af27f000000000000000000000000000000000000000000000000000000000000000084612720565b927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef6001600160a01b0384169687928385526005825260408520611b37888254611a44565b9055611b4587600654611a44565b600655604051908152a360405194859283927fbd43185700000000000000000000000000000000000000000000000000000000845260048401602090939291936001600160a01b0360408201951681520152565b03815f6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165af19182156113eb575f92611c04575b507fae47706d00d5577c8b7cdd43658512d28ad81d491c94212c779f164243bc40946020604051848152a290565b9091506020813d602011611c30575b81611c2060209383611774565b81010312610b455751905f611bd6565b3d9150611c13565b906001600160a01b03604051927f8b28785500000000000000000000000000000000000000000000000000000000845216918260048201528160248201526020816044815f6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165af19081156113eb575f91611cea575b506040907fc41d9f7e00791bcb7043cf5cdc5f415ac694ea8fd9267e3595a90a683eb789a29282519182526020820152a2565b90506020813d602011611d34575b81611d0560209383611774565b81010312610b4557517fc41d9f7e00791bcb7043cf5cdc5f415ac694ea8fd9267e3595a90a683eb789a2611cb7565b3d9150611cf8565b8115611d46570490565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b91908201809211611a5157565b6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001690611db48161232c565b906001600160a01b03604051927ff5eb42dc000000000000000000000000000000000000000000000000000000008452166004830152602082602481865afa9182156113eb575f92611f50575b506001600160a01b03611e23911691825f52600560205260405f205490611a44565b916020611e5b611e547f00000000000000000000000000000000000000000000000000000000000000008096611d3c565b9485612720565b835f526005825260405f20611e71828254611d73565b9055611e7f81600654611d73565b600655835f7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051898152a36024604051809481937f8508e46600000000000000000000000000000000000000000000000000000000835260048301525afa9081156113eb575f91611f1d575b5060207f8aec0ce3dadffacf4b7a963e0fed1ff2e6151b4c95d4a65acafa9d129963040291604051908152a290565b90506020813d602011611f48575b81611f3860209383611774565b81010312610b4557516020611eee565b3d9150611f2b565b9091506020813d602011611f83575b81611f6c60209383611774565b81010312610b455751906001600160a01b03611e01565b3d9150611f5f565b916001600160a01b03831691825f52600560205260405f205482611fd07f00000000000000000000000000000000000000000000000000000000000000008093611d3c565b1061210e5781611fe260409285612720565b82517f708ca24f0000000000000000000000000000000000000000000000000000000081526001600160a01b0397881660048201529187166024830152604482015294859060649082905f907f0000000000000000000000000000000000000000000000000000000000000000165af19384156113eb575f905f956120cc575b50916001600160a01b036020927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef94865f52600585526120a760405f20918254611a44565b90551694855f52600583526120c160405f20918254611d73565b9055604051908152a3565b9450506040843d604011612106575b816120e860409383611774565b81010312610b45578351602090940151936001600160a01b03612062565b3d91506120db565b7f4024ed9b000000000000000000000000000000000000000000000000000000005f5260045ffd5b6020611b999281925f6121697f000000000000000000000000000000000000000000000000000000000000000084612720565b927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef6001600160a01b03841696879283855260058252604085206121ae888254611a44565b90556121bc87600654611a44565b600655604051908152a360405194859283927f5a54f08f00000000000000000000000000000000000000000000000000000000845260048401602090939291936001600160a01b0360408201951681520152565b6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016301480612303575b1561226b577f000000000000000000000000000000000000000000000000000000000000000090565b60405160208101907f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f82527f000000000000000000000000000000000000000000000000000000000000000060408201527f000000000000000000000000000000000000000000000000000000000000000060608201524660808201523060a082015260a081526122fd60c082611774565b51902090565b507f00000000000000000000000000000000000000000000000000000000000000004614612242565b6001600160a01b0390604051826d1000010100000110011011010010816020840194160116825260208152612362604082611774565b5190201690565b6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016906040516124405f80602084017f23b872dd000000000000000000000000000000000000000000000000000000008152612400856123f28a8a8a602485016001600160a01b036040929594938160608401971683521660208201520152565b03601f198101875286611774565b6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001694519082865af16124396125e7565b9083612983565b80519081151591826125c3575b505061259857506040517fb2a06e5e0000000000000000000000000000000000000000000000000000000081526001600160a01b03821660048201526024810184905291602090839060449082905f905af19182156113eb575f92612563575b50602061252d6001600160a01b037f8fc64276dc392250fbc0589c1605ceb4da5b7ba1d2648703a256d8f3675ae09b931693845f526005835260405f206124f5828254611d73565b905561250381600654611d73565b6006557f000000000000000000000000000000000000000000000000000000000000000090611d3c565b93835f7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051898152a3604051908152a290565b9091506020813d602011612590575b8161257f60209383611774565b81010312610b4557519060206124ad565b3d9150612572565b7f5274afe7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b8192509060209181010312610b455760200151801590811503610b45575f8061244d565b3d15612611573d906125f8826117c4565b916126066040519384611774565b82523d5f602084013e565b606090565b6040517fa0f720380000000000000000000000000000000000000000000000000000000081526001600160a01b03821660048201526024810183905290602082806044810103815f6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165af19182156113eb575f926125635750602061252d6001600160a01b037f8fc64276dc392250fbc0589c1605ceb4da5b7ba1d2648703a256d8f3675ae09b931693845f526005835260405f206124f5828254611d73565b6042906126ea612210565b90604051917f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201522090565b81810292918115918404141715611a5157565b612757816001600160a01b03165f52600260205260405f2080549060018201905590565b809203612762575050565b6001600160a01b03907f752d88c0000000000000000000000000000000000000000000000000000000005f521660045260245260445ffd5b906127a58382612a0f565b5060048195929510156128a757159384612891575b5083156127c8575b50505090565b5f93509061281961282785949360405192839160208301957f1626ba7e0000000000000000000000000000000000000000000000000000000087526024840152604060448401526064830190611723565b03601f198101835282611774565b51915afa6128336125e7565b81612883575b81612848575b505f80806127c2565b9050602081805181010312610b4557602001517f1626ba7e00000000000000000000000000000000000000000000000000000000145f61283f565b905060208151101590612839565b6001600160a01b0384811691161493505f6127ba565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b60ff81146129335760ff811690601f821161290b57604051916128f8604084611774565b6020808452838101919036833783525290565b7fb3512b0c000000000000000000000000000000000000000000000000000000005f5260045ffd5b506040516118718161294681600161198b565b0382611774565b60ff81146129715760ff811690601f821161290b57604051916128f8604084611774565b5060405161187181612946815f61198b565b906129c0575080511561299857805190602001fd5b7f1425ea42000000000000000000000000000000000000000000000000000000005f5260045ffd5b81511580612a06575b6129d1575090565b6001600160a01b03907f9996b315000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b50803b156129c9565b8151919060418303612a3f57612a389250602082015190606060408401519301515f1a90612a49565b9192909190565b50505f9160029190565b90917f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411612ad25790612aa26020945f9493604051948594859094939260ff6060936080840197845216602083015260408201520152565b838052039060015afa156113eb575f516001600160a01b03811615612ac857905f905f90565b505f906001905f90565b5050505f916003919056fea2646970667358221220cb6fd926bb02ab7364d5ba10388fa7e899d3e6b8553448c1b6a2d34b5855ea9664736f6c634300081c003300000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001400000000000000000000000001932e815254c53b3ecd81cecf252a5ac7f0e8bea0000000000000000000000000b010000b7624eb9b3dfbc279673c76e9d29d5f700000000000000000000000000000000000000000000000000000002540be400000000000000000000000000000000000000000000000000000000000000000b5374616b6564204f626f6c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000673744f424f4c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013100000000000000000000000000000000000000000000000000000000000000

Deployed Bytecode

0x60806040526004361015610011575f80fd5b5f5f3560e01c80626b2b591461170457806306fdde031461163a5780630700cc4214611600578063095ea7b3146115885780630afe3fed1461154e5780630c4cb55c14611497578063119e9d641461147857806318160ddd146114395780631c39b672146113f65780631ce9f923146113545780631ddec39b1461127d5780631fbe19791461126157806323b872dd146111a757806327cb8f521461110e5780632e17de78146110ee57806330adf81f146110b3578063313ce567146110975780633259c9141461105c5780633644e5151461104157806354fd4d501461100457806355d5c37b14610fc0578063587cde1e14610c645780635c19a95c14610ee757806370a0823114610e8d5780637ecebe0014610e5557806384b0196e14610d3d57806389ee09f014610d02578063921630c314610c69578063942ff14014610c6457806395d89b4114610b7d578063a694fc3a14610b5d578063a79f5b1014610a7f578063a88898e714610a44578063a9059cbb14610a12578063ac9650d8146107fe578063b601bbc3146107c3578063bc34ee22146106ca578063c782767b146106aa578063cbf0f205146105e6578063d505accf14610420578063dd62ed3e146103d2578063df1eed0d14610339578063ee836c351461023a5763f5706759146101fd575f80fd5b3461023757806003193601126102375760206040517f00000000000000000000000000000000000000000000000000000002540be4008152f35b80fd5b50346102375761024936611826565b91906102588286979597612733565b804211610311576102d092916102ca916040519060208201927f801afda14e54c9b981ce6b7101d75bf8df9b294ef01d71741120c1dd942cb6cb84526001600160a01b0389166040840152896060840152608083015260a082015260a081526102c260c082611774565b5190206126df565b8461279a565b156102e95760206102e18484612616565b604051908152f35b807ff35976ef0000000000000000000000000000000000000000000000000000000060049252fd5b6004847f6cab85c7000000000000000000000000000000000000000000000000000000008152fd5b50346102375761034836611826565b91906103578286979597612733565b804211610311576103c192916102ca916040519060208201927fad9c22b83dd1cf60bfdcf34513024790267f8ac5bf377e3ce558e853a641228b84526001600160a01b0389166040840152896060840152608083015260a082015260a081526102c260c082611774565b156102e95760206102e18484612136565b5034610237576040600319360112610237576001600160a01b0360406103f6611748565b928261040061175e565b9416815260076020522091165f52602052602060405f2054604051908152f35b50346102375760e06003193601126102375761043a611748565b61044261175e565b604435606435926084359060ff821682036105e2578442116105ba576020916104ee8261048a89946001600160a01b03165f52600260205260405f2080549060018201905590565b97604051906001600160a01b0380888401947f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9865216998a604085015216998a606084015288608084015260a083015260c082015260c081526102c260e082611774565b6040805191825260ff92909216602082015260a4359181019190915260c435606082015281805260809060015afa156105af576001600160a01b03845116801580156105a5575b61057d577f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259160209186526007825260408620855f5282528060405f2055604051908152a380f35b6004857ff35976ef000000000000000000000000000000000000000000000000000000008152fd5b5082811415610535565b6040513d85823e3d90fd5b6004867f6cab85c7000000000000000000000000000000000000000000000000000000008152fd5b8580fd5b503461023757608060031936011261023757610600611748565b9060443560243560643567ffffffffffffffff81116106a6576106279036906004016117e0565b916106328286612733565b8042116103115761069692916102ca916040519060208201927f1c24a0d6d5e5b7973590781b8f5836f26e13ce0b3dc6852eefc1114048c0fe0e84526001600160a01b038916604084015260608301526080820152608081526102c260a082611774565b156102e95760206102e183611d80565b8380fd5b50346102375760206003193601126102375760206102e160043533612616565b50346102375760a0600319360112610237576106e4611748565b6024359060643560443560843567ffffffffffffffff81116105e25761070e9036906004016117e0565b916107198285612733565b8042116105ba576107899291610783916040519060208201927f37d81b40a9b05b0bc4bdb720ed5e8ef5f66be2ba9af9fbaef13adbba9181f25e84526001600160a01b0388166040840152886060840152608083015260a082015260a081526102c260c082611774565b8361279a565b1561079b579061079891611c38565b80f35b6004837ff35976ef000000000000000000000000000000000000000000000000000000008152fd5b503461023757806003193601126102375760206040517f1c24a0d6d5e5b7973590781b8f5836f26e13ce0b3dc6852eefc1114048c0fe0e8152f35b50346102375760206003193601126102375760043567ffffffffffffffff8111610a0e5736602382011215610a0e57806004013567ffffffffffffffff8111610a0a573660248260051b84010111610a0a579060206040516108608282611774565b84815281810191601f19810136843761087885611a2c565b936108866040519586611774565b858552601f1961089587611a2c565b01875b8181106109fb57505086907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbd81360301915b8781101561097e5760248160051b830101358381121561097a5782019060248201359167ffffffffffffffff8311610976576044018a8336038213610237578061095a92896109466001978b8e6040519483869484860198893784019083820190898252519283915e010185815203601f198101835282611774565b5190305af46109536125e7565b9030612983565b610964828a611a7e565b5261096f8189611a7e565b50016108ca565b8a80fd5b8980fd5b83898860405191838301848452825180915260408401948060408360051b870101940192955b8287106109b15785850386f35b9091929382806109eb837fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc08a600196030186528851611723565b96019201960195929190926109a4565b60608782018501528301610898565b8280fd5b5080fd5b503461023757604060031936011261023757610a39610a2f611748565b6024359033611f8b565b602060405160018152f35b503461023757806003193601126102375760206040517f50939ff8118ce6ab8bd952f323a3982b869c7b50787cbc6bbc9e57d13afa9ca18152f35b503461023757602060031936011261023757610a99611748565b906001600160a01b03604051927fa79f5b10000000000000000000000000000000000000000000000000000000008452166004830152602082602481846001600160a01b037f0000000000000000000000001932e815254c53b3ecd81cecf252a5ac7f0e8bea165af1908115610b515790610b1a575b602090604051908152f35b506020813d602011610b49575b81610b3460209383611774565b81010312610b455760209051610b0f565b5f80fd5b3d9150610b27565b604051903d90823e3d90fd5b50346102375760206003193601126102375760206102e160043533612369565b5034610237578060031936011261023757604051908060045490610ba08261193a565b8085529160018116908115610c3d5750600114610be0575b610bdc84610bc881860382611774565b604051918291602083526020830190611723565b0390f35b600481527f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b939250905b808210610c2357509091508101602001610bc882610bb8565b919260018160209254838588010152019101909291610c0a565b60ff191660208087019190915292151560051b85019092019250610bc89150839050610bb8565b611874565b503461023757610c7836611826565b9190610c878286979597612733565b80421161031157610cf192916102ca916040519060208201927fa461f2f1e741cadd3094f8eabe3ba7e04633c3f9a954bf1bec554224fe5ed3c584526001600160a01b0389166040840152896060840152608083015260a082015260a081526102c260c082611774565b156102e95760206102e18484612369565b503461023757806003193601126102375760206040517fa461f2f1e741cadd3094f8eabe3ba7e04633c3f9a954bf1bec554224fe5ed3c58152f35b5034610237578060031936011261023757610df990610d7b7f5374616b6564204f626f6c00000000000000000000000000000000000000000b61294d565b90610da57f31000000000000000000000000000000000000000000000000000000000000016128d4565b906020610e0760405193610db98386611774565b8385525f3681376040519687967f0f00000000000000000000000000000000000000000000000000000000000000885260e08589015260e0880190611723565b908682036040880152611723565b904660608601523060808601528260a086015284820360c08601528080855193848152019401925b828110610e3e57505050500390f35b835185528695509381019392810192600101610e2f565b50346102375760206003193601126102375760406020916001600160a01b03610e7c611748565b168152600283522054604051908152f35b5034610237576020600319360112610237576102e160406020926001600160a01b03610eb7611748565b1681526005845220547f00000000000000000000000000000000000000000000000000000002540be40090611d3c565b503461023757602060031936011261023757610f01611748565b6001600160a01b03604051917fa79f5b10000000000000000000000000000000000000000000000000000000008352166004820152602081602481856001600160a01b037f0000000000000000000000001932e815254c53b3ecd81cecf252a5ac7f0e8bea165af18015610fb5578290610f81575b610798915033611c38565b506020813d602011610fad575b81610f9b60209383611774565b81010312610b45576107989051610f76565b3d9150610f8e565b6040513d84823e3d90fd5b503461023757806003193601126102375760206040516001600160a01b037f0000000000000000000000001932e815254c53b3ecd81cecf252a5ac7f0e8bea168152f35b5034610237578060031936011261023757610bdc610bc87f31000000000000000000000000000000000000000000000000000000000000016128d4565b503461023757806003193601126102375760206102e1612210565b503461023757806003193601126102375760206040517f37d81b40a9b05b0bc4bdb720ed5e8ef5f66be2ba9af9fbaef13adbba9181f25e8152f35b5034610237578060031936011261023757602060405160128152f35b503461023757806003193601126102375760206040517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98152f35b50346102375760206003193601126102375760206102e160043533612136565b50346102375761111d36611826565b919061112c8286979597612733565b8042116103115761119692916102ca916040519060208201927f50939ff8118ce6ab8bd952f323a3982b869c7b50787cbc6bbc9e57d13afa9ca184526001600160a01b0389166040840152896060840152608083015260a082015260a081526102c260c082611774565b156102e95760206102e18484611abf565b503461023757606060031936011261023757610a39906111c5611748565b6111cd61175e565b9060443592836001600160a01b038316918281526007602052604081206001600160a01b0333165f5260205260405f20547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff810361122f575b50505050611f8b565b60409261123b91611a44565b9281526007602052206001600160a01b0333165f5260205260405f20555f838180611226565b503461023757806003193601126102375760206102e133611d80565b34610b455760a0600319360112610b455760043560443560ff8116809103610b45576001600160a01b037f0000000000000000000000000b010000b7624eb9b3dfbc279673c76e9d29d5f71691823b15610b45575f8060209460e46102e19560405194859384927fd505accf0000000000000000000000000000000000000000000000000000000084523360048501523060248501528860448501526024356064850152608484015260643560a484015260843560c48401525af1611344575b5033612369565b5f61134e91611774565b5f61133d565b34610b45576020600319360112610b455761136d611748565b6001600160a01b03604051917f1ce9f9230000000000000000000000000000000000000000000000000000000083521660048201526020816024816001600160a01b037f0000000000000000000000001932e815254c53b3ecd81cecf252a5ac7f0e8bea165afa80156113eb575f90610b1a57602090604051908152f35b6040513d5f823e3d90fd5b34610b45575f600319360112610b455760206040516001600160a01b037f0000000000000000000000000b010000b7624eb9b3dfbc279673c76e9d29d5f7168152f35b34610b45575f600319360112610b455760206102e16006547f00000000000000000000000000000000000000000000000000000002540be40090611d3c565b34610b45576020600319360112610b455761149560043533611c38565b005b34610b45575f600319360112610b45576040517f0c4cb55c0000000000000000000000000000000000000000000000000000000081526020816004816001600160a01b037f0000000000000000000000001932e815254c53b3ecd81cecf252a5ac7f0e8bea165afa80156113eb576020915f91611521575b506001600160a01b0360405191168152f35b6115419150823d8411611547575b6115398183611774565b810190611a0d565b8261150f565b503d61152f565b34610b45575f600319360112610b455760206040517f801afda14e54c9b981ce6b7101d75bf8df9b294ef01d71741120c1dd942cb6cb8152f35b34610b45576040600319360112610b45576115a1611748565b6001600160a01b0360243591335f52600760205260405f208282165f526020528260405f205560405192835216907f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560203392a3602060405160018152f35b34610b45575f600319360112610b455760206040517fad9c22b83dd1cf60bfdcf34513024790267f8ac5bf377e3ce558e853a641228b8152f35b34610b45575f600319360112610b45576040515f60035461165a8161193a565b80845290600181169081156116e05750600114611682575b610bdc83610bc881850382611774565b60035f9081527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b939250905b8082106116c657509091508101602001610bc8611672565b9192600181602092548385880101520191019092916116ae565b60ff191660208086019190915291151560051b84019091019150610bc89050611672565b34610b45576020600319360112610b455760206102e160043533611abf565b90601f19601f602080948051918291828752018686015e5f8582860101520116010190565b600435906001600160a01b0382168203610b4557565b602435906001600160a01b0382168203610b4557565b90601f601f19910116810190811067ffffffffffffffff82111761179757604052565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b67ffffffffffffffff811161179757601f01601f191660200190565b81601f82011215610b45578035906117f7826117c4565b926118056040519485611774565b82845260208383010111610b4557815f926020809301838601378301015290565b60a0600319820112610b45576004356001600160a01b0381168103610b4557916024359160443591606435916084359067ffffffffffffffff8211610b4557611871916004016117e0565b90565b34610b45576020600319360112610b4557611895611890611748565b61232c565b6001600160a01b03604051917f942ff1400000000000000000000000000000000000000000000000000000000083521660048201526020816024816001600160a01b037f0000000000000000000000001932e815254c53b3ecd81cecf252a5ac7f0e8bea165afa80156113eb576020915f9161191d57506001600160a01b0360405191168152f35b6119349150823d8411611547576115398183611774565b5f61150f565b90600182811c92168015611981575b602083101461195457565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b91607f1691611949565b5f929181549161199a8361193a565b80835292600181169081156119ef57506001146119b657505050565b5f9081526020812093945091925b8383106119d5575060209250010190565b6001816020929493945483858701015201910191906119c4565b9050602094955060ff1991509291921683830152151560051b010190565b90816020910312610b4557516001600160a01b0381168103610b455790565b67ffffffffffffffff81116117975760051b60200190565b91908203918211611a5157565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b8051821015611a925760209160051b010190565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b6020611b999281925f611af27f00000000000000000000000000000000000000000000000000000002540be40084612720565b927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef6001600160a01b0384169687928385526005825260408520611b37888254611a44565b9055611b4587600654611a44565b600655604051908152a360405194859283927fbd43185700000000000000000000000000000000000000000000000000000000845260048401602090939291936001600160a01b0360408201951681520152565b03815f6001600160a01b037f0000000000000000000000001932e815254c53b3ecd81cecf252a5ac7f0e8bea165af19182156113eb575f92611c04575b507fae47706d00d5577c8b7cdd43658512d28ad81d491c94212c779f164243bc40946020604051848152a290565b9091506020813d602011611c30575b81611c2060209383611774565b81010312610b455751905f611bd6565b3d9150611c13565b906001600160a01b03604051927f8b28785500000000000000000000000000000000000000000000000000000000845216918260048201528160248201526020816044815f6001600160a01b037f0000000000000000000000001932e815254c53b3ecd81cecf252a5ac7f0e8bea165af19081156113eb575f91611cea575b506040907fc41d9f7e00791bcb7043cf5cdc5f415ac694ea8fd9267e3595a90a683eb789a29282519182526020820152a2565b90506020813d602011611d34575b81611d0560209383611774565b81010312610b4557517fc41d9f7e00791bcb7043cf5cdc5f415ac694ea8fd9267e3595a90a683eb789a2611cb7565b3d9150611cf8565b8115611d46570490565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b91908201809211611a5157565b6001600160a01b037f0000000000000000000000001932e815254c53b3ecd81cecf252a5ac7f0e8bea1690611db48161232c565b906001600160a01b03604051927ff5eb42dc000000000000000000000000000000000000000000000000000000008452166004830152602082602481865afa9182156113eb575f92611f50575b506001600160a01b03611e23911691825f52600560205260405f205490611a44565b916020611e5b611e547f00000000000000000000000000000000000000000000000000000002540be4008096611d3c565b9485612720565b835f526005825260405f20611e71828254611d73565b9055611e7f81600654611d73565b600655835f7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051898152a36024604051809481937f8508e46600000000000000000000000000000000000000000000000000000000835260048301525afa9081156113eb575f91611f1d575b5060207f8aec0ce3dadffacf4b7a963e0fed1ff2e6151b4c95d4a65acafa9d129963040291604051908152a290565b90506020813d602011611f48575b81611f3860209383611774565b81010312610b4557516020611eee565b3d9150611f2b565b9091506020813d602011611f83575b81611f6c60209383611774565b81010312610b455751906001600160a01b03611e01565b3d9150611f5f565b916001600160a01b03831691825f52600560205260405f205482611fd07f00000000000000000000000000000000000000000000000000000002540be4008093611d3c565b1061210e5781611fe260409285612720565b82517f708ca24f0000000000000000000000000000000000000000000000000000000081526001600160a01b0397881660048201529187166024830152604482015294859060649082905f907f0000000000000000000000001932e815254c53b3ecd81cecf252a5ac7f0e8bea165af19384156113eb575f905f956120cc575b50916001600160a01b036020927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef94865f52600585526120a760405f20918254611a44565b90551694855f52600583526120c160405f20918254611d73565b9055604051908152a3565b9450506040843d604011612106575b816120e860409383611774565b81010312610b45578351602090940151936001600160a01b03612062565b3d91506120db565b7f4024ed9b000000000000000000000000000000000000000000000000000000005f5260045ffd5b6020611b999281925f6121697f00000000000000000000000000000000000000000000000000000002540be40084612720565b927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef6001600160a01b03841696879283855260058252604085206121ae888254611a44565b90556121bc87600654611a44565b600655604051908152a360405194859283927f5a54f08f00000000000000000000000000000000000000000000000000000000845260048401602090939291936001600160a01b0360408201951681520152565b6001600160a01b037f0000000000000000000000006590cbbccbe6b83ef3774ef1904d86a7b02c2fcc16301480612303575b1561226b577f113bbb5f860b439ca4b717adc8623f4607d9d2ffb14d3ad75090c31c66d1f03f90565b60405160208101907f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f82527f269053129bf0740ec5491ffb0a1bc10f4379b75eae079b08d6dd497725fd2acf60408201527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260a081526122fd60c082611774565b51902090565b507f00000000000000000000000000000000000000000000000000000000000000014614612242565b6001600160a01b0390604051826d1000010100000110011011010010816020840194160116825260208152612362604082611774565b5190201690565b6001600160a01b037f0000000000000000000000001932e815254c53b3ecd81cecf252a5ac7f0e8bea16906040516124405f80602084017f23b872dd000000000000000000000000000000000000000000000000000000008152612400856123f28a8a8a602485016001600160a01b036040929594938160608401971683521660208201520152565b03601f198101875286611774565b6001600160a01b037f0000000000000000000000000b010000b7624eb9b3dfbc279673c76e9d29d5f71694519082865af16124396125e7565b9083612983565b80519081151591826125c3575b505061259857506040517fb2a06e5e0000000000000000000000000000000000000000000000000000000081526001600160a01b03821660048201526024810184905291602090839060449082905f905af19182156113eb575f92612563575b50602061252d6001600160a01b037f8fc64276dc392250fbc0589c1605ceb4da5b7ba1d2648703a256d8f3675ae09b931693845f526005835260405f206124f5828254611d73565b905561250381600654611d73565b6006557f00000000000000000000000000000000000000000000000000000002540be40090611d3c565b93835f7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051898152a3604051908152a290565b9091506020813d602011612590575b8161257f60209383611774565b81010312610b4557519060206124ad565b3d9150612572565b7f5274afe7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b8192509060209181010312610b455760200151801590811503610b45575f8061244d565b3d15612611573d906125f8826117c4565b916126066040519384611774565b82523d5f602084013e565b606090565b6040517fa0f720380000000000000000000000000000000000000000000000000000000081526001600160a01b03821660048201526024810183905290602082806044810103815f6001600160a01b037f0000000000000000000000001932e815254c53b3ecd81cecf252a5ac7f0e8bea165af19182156113eb575f926125635750602061252d6001600160a01b037f8fc64276dc392250fbc0589c1605ceb4da5b7ba1d2648703a256d8f3675ae09b931693845f526005835260405f206124f5828254611d73565b6042906126ea612210565b90604051917f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201522090565b81810292918115918404141715611a5157565b612757816001600160a01b03165f52600260205260405f2080549060018201905590565b809203612762575050565b6001600160a01b03907f752d88c0000000000000000000000000000000000000000000000000000000005f521660045260245260445ffd5b906127a58382612a0f565b5060048195929510156128a757159384612891575b5083156127c8575b50505090565b5f93509061281961282785949360405192839160208301957f1626ba7e0000000000000000000000000000000000000000000000000000000087526024840152604060448401526064830190611723565b03601f198101835282611774565b51915afa6128336125e7565b81612883575b81612848575b505f80806127c2565b9050602081805181010312610b4557602001517f1626ba7e00000000000000000000000000000000000000000000000000000000145f61283f565b905060208151101590612839565b6001600160a01b0384811691161493505f6127ba565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b60ff81146129335760ff811690601f821161290b57604051916128f8604084611774565b6020808452838101919036833783525290565b7fb3512b0c000000000000000000000000000000000000000000000000000000005f5260045ffd5b506040516118718161294681600161198b565b0382611774565b60ff81146129715760ff811690601f821161290b57604051916128f8604084611774565b5060405161187181612946815f61198b565b906129c0575080511561299857805190602001fd5b7f1425ea42000000000000000000000000000000000000000000000000000000005f5260045ffd5b81511580612a06575b6129d1575090565b6001600160a01b03907f9996b315000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b50803b156129c9565b8151919060418303612a3f57612a389250602082015190606060408401519301515f1a90612a49565b9192909190565b50505f9160029190565b90917f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411612ad25790612aa26020945f9493604051948594859094939260ff6060936080840197845216602083015260408201520152565b838052039060015afa156113eb575f516001600160a01b03811615612ac857905f905f90565b505f906001905f90565b5050505f916003919056fea2646970667358221220cb6fd926bb02ab7364d5ba10388fa7e899d3e6b8553448c1b6a2d34b5855ea9664736f6c634300081c0033

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

00000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001400000000000000000000000001932e815254c53b3ecd81cecf252a5ac7f0e8bea0000000000000000000000000b010000b7624eb9b3dfbc279673c76e9d29d5f700000000000000000000000000000000000000000000000000000002540be400000000000000000000000000000000000000000000000000000000000000000b5374616b6564204f626f6c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000673744f424f4c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013100000000000000000000000000000000000000000000000000000000000000

-----Decoded View---------------
Arg [0] : _name (string): Staked Obol
Arg [1] : _symbol (string): stOBOL
Arg [2] : _version (string): 1
Arg [3] : _lst (address): 0x1932e815254c53B3Ecd81CECf252A5AC7f0e8BeA
Arg [4] : _stakeToken (address): 0x0B010000b7624eb9B3DfBC279673C76E9D29D5F7
Arg [5] : _shareScaleFactor (uint256): 10000000000

-----Encoded View---------------
12 Constructor Arguments found :
Arg [0] : 00000000000000000000000000000000000000000000000000000000000000c0
Arg [1] : 0000000000000000000000000000000000000000000000000000000000000100
Arg [2] : 0000000000000000000000000000000000000000000000000000000000000140
Arg [3] : 0000000000000000000000001932e815254c53b3ecd81cecf252a5ac7f0e8bea
Arg [4] : 0000000000000000000000000b010000b7624eb9b3dfbc279673c76e9d29d5f7
Arg [5] : 00000000000000000000000000000000000000000000000000000002540be400
Arg [6] : 000000000000000000000000000000000000000000000000000000000000000b
Arg [7] : 5374616b6564204f626f6c000000000000000000000000000000000000000000
Arg [8] : 0000000000000000000000000000000000000000000000000000000000000006
Arg [9] : 73744f424f4c0000000000000000000000000000000000000000000000000000
Arg [10] : 0000000000000000000000000000000000000000000000000000000000000001
Arg [11] : 3100000000000000000000000000000000000000000000000000000000000000


Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export  ]
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.