ETH Price: $2,288.13 (+0.53%)
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

Token Holdings

More Info

Private Name Tags

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Stake243728252026-02-03 0:42:479 hrs ago1770079367IN
0x52b8969F...2B65c165C
0 ETH0.000009110.09503733
Get Reward243724182026-02-02 23:20:5911 hrs ago1770074459IN
0x52b8969F...2B65c165C
0 ETH0.000008770.10107672
Stake243715942026-02-02 20:35:1113 hrs ago1770064511IN
0x52b8969F...2B65c165C
0 ETH0.000016090.14987491
Stake243642862026-02-01 20:05:1138 hrs ago1769976311IN
0x52b8969F...2B65c165C
0 ETH0.000015290.159488
Get Reward243642522026-02-01 19:58:2338 hrs ago1769975903IN
0x52b8969F...2B65c165C
0 ETH0.000021510.24769864
Stake243625252026-02-01 14:10:5944 hrs ago1769955059IN
0x52b8969F...2B65c165C
0 ETH0.000033060.32842269
Get Reward243625122026-02-01 14:08:2344 hrs ago1769954903IN
0x52b8969F...2B65c165C
0 ETH0.000021840.31312451
Stake243616702026-02-01 11:18:5947 hrs ago1769944739IN
0x52b8969F...2B65c165C
0 ETH0.000087520.91270775
Get Reward243616622026-02-01 11:17:2347 hrs ago1769944643IN
0x52b8969F...2B65c165C
0 ETH0.000083890.9658409
Complete Withdra...243548202026-01-31 12:20:592 days ago1769862059IN
0x52b8969F...2B65c165C
0 ETH0.000004470.10414848
Get Reward243434172026-01-29 22:11:594 days ago1769724719IN
0x52b8969F...2B65c165C
0 ETH0.000007210.08300738
Stake243402192026-01-29 11:30:114 days ago1769686211IN
0x52b8969F...2B65c165C
0 ETH0.000008260.08618729
Get Reward243402132026-01-29 11:28:594 days ago1769686139IN
0x52b8969F...2B65c165C
0 ETH0.000007520.08664014
Stake243284482026-01-27 20:05:596 days ago1769544359IN
0x52b8969F...2B65c165C
0 ETH0.000005120.05347739
Get Reward243284432026-01-27 20:04:596 days ago1769544299IN
0x52b8969F...2B65c165C
0 ETH0.000005090.05865196
Stake243208192026-01-26 18:33:237 days ago1769452403IN
0x52b8969F...2B65c165C
0 ETH0.000011470.1456114
Stake243195382026-01-26 14:15:477 days ago1769436947IN
0x52b8969F...2B65c165C
0 ETH0.000027430.28608208
Get Reward243195332026-01-26 14:14:477 days ago1769436887IN
0x52b8969F...2B65c165C
0 ETH0.000020790.23944499
Get Reward243137672026-01-25 18:57:118 days ago1769367431IN
0x52b8969F...2B65c165C
0 ETH0.000014250.159709
Stake243133322026-01-25 17:30:118 days ago1769362211IN
0x52b8969F...2B65c165C
0 ETH0.000006910.06872172
Get Reward243133182026-01-25 17:27:238 days ago1769362043IN
0x52b8969F...2B65c165C
0 ETH0.000004650.06669914
Get Reward243123192026-01-25 14:07:118 days ago1769350031IN
0x52b8969F...2B65c165C
0 ETH0.000038520.53357389
Request Withdraw...243123172026-01-25 14:06:478 days ago1769350007IN
0x52b8969F...2B65c165C
0 ETH0.000066550.53602144
Request Withdraw...243122912026-01-25 14:01:358 days ago1769349695IN
0x52b8969F...2B65c165C
0 ETH0.000060670.53342724
Get Reward243122892026-01-25 14:01:118 days ago1769349671IN
0x52b8969F...2B65c165C
0 ETH0.00004030.53338588
View all transactions

View more zero value Internal Transactions in Advanced View mode

Advanced mode:
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:
SinglePoolStaking

Compiler Version
v0.8.20+commit.a1b79de6

Optimization Enabled:
Yes with 10000 runs

Other Settings:
shanghai EvmVersion
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

/*

████████╗ █████╗ ██╗     ██╗███████╗███╗   ███╗ █████╗ ███╗   ██╗
╚══██╔══╝██╔══██╗██║     ██║██╔════╝████╗ ████║██╔══██╗████╗  ██║
   ██║   ███████║██║     ██║███████╗██╔████╔██║███████║██╔██╗ ██║
   ██║   ██╔══██║██║     ██║╚════██║██║╚██╔╝██║██╔══██║██║╚██╗██║
   ██║   ██║  ██║███████╗██║███████║██║ ╚═╝ ██║██║  ██║██║ ╚████║
   ╚═╝   ╚═╝  ╚═╝╚══════╝╚═╝╚══════╝╚═╝     ╚═╝╚═╝  ╚═╝╚═╝  ╚═══╝

*/

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Ownable2Step, Ownable} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

/// @title SinglePoolStaking
/// @notice Single-pool proportional reward staking with an adjustable emission rate.
/// @dev
/// - Rewards are **prefunded** (non-mintable). Accrual is gated by `rewardReserves` to ensure the pool
///   never accounts more than the available reserves (prevents over-accrual/insolvency).
/// - Supports **same-token** staking (STAKE_TOKEN == REWARD_TOKEN) safely via reserve gating,
///   so reward payouts never consume staked principal.
/// - Uses a global `rewardPerTokenStored` (scaled by 1e18) with per-user snapshots to account rewards.
/// - All mutative paths that depend on time call `_updateGlobal()` first to snapshot history.
/// - This implementation assumes **standard ERC-20 semantics** for staking token transfers
///   (i.e., no fee-on-transfer on `stake()`/`withdraw()`). `fundRewards()` is robust via balance delta.
///
contract SinglePoolStaking is Ownable2Step, ReentrancyGuard {
    using SafeERC20 for IERC20;

    // ====== Immutable tokens ======

    /// @notice Token users deposit as principal (a.k.a. staked token). Standard ERC-20 (no fee/rebase).
    /// @dev Immutable at construction.
    IERC20 public immutable STAKE_TOKEN;

    /// @notice Token paid out as rewards. Standard ERC-20 (no fee/rebase).
    /// @dev Immutable at construction; can be same as `STAKE_TOKEN`.
    IERC20 public immutable REWARD_TOKEN;

    // ====== Emissions config ======

    /// @notice Rewards emitted per second.
    /// @dev Owner can adjust via governance-timelocked flow (see `proposeRewardRate` / `executeRewardRateChange`).
    uint256 public rewardRate;

    /// @notice Last timestamp when global rewards were accounted (i.e., when `_updateGlobal()` last ran).
    /// @dev Stored as uint64 to save gas; comparisons cast to uint256 where needed.
    uint64 public lastUpdateTime;

    /// @notice Accumulated rewards per staked token, scaled by 1e18.
    /// @dev Global index used to compute per-user deltas. Monotonic non-decreasing.
    uint256 public rewardPerTokenStored;

    /// @notice Prefunded reward reserves available for **future** accrual.
    /// @dev `_updateGlobal()` consumes from this bucket (moves to "owed but unpaid");
    ///      user claims **do not** touch `rewardReserves`.
    uint256 public rewardReserves;

    /// @notice Flag to enable emergency exit mode.
    /// @dev When enabled, users can withdraw their principal immediately, forfeiting any accrued rewards.
    ///      This is a governance-controlled feature that can be toggled by the owner.
    ///      When disabled, users must use the delayed withdrawal path to claim their principal.
    ///      This flag is set to `false` by default and can be toggled by the owner.
    bool public emergencyExitEnabled;

    // ====== Staking state ======

    /// @notice Total staked principal held by the contract.
    uint256 public totalStaked;

    /// @notice Per-user accounting data.
    /// @param balance Current staked principal.
    /// @param userRewardPerTokenPaid User snapshot of `rewardPerTokenStored` at last accounting.
    /// @param rewards Accrued but unclaimed rewards (accounted via snapshots).
    struct User {
        uint256 balance;
        uint256 userRewardPerTokenPaid;
        uint256 rewards;
    }

    /// @notice Mapping of user address to their staking accounting data.
    mapping(address => User) public users;

    // ====== Delayed withdrawal state ======

    /// @notice Lock duration between withdrawal request and claim (in seconds).
    /// @dev Configurable by owner. If set to 0, withdrawals can be completed immediately after requesting.
    uint64 public withdrawDelay;

    /// @notice User withdrawal request data.
    /// @param amount Requested amount that was removed from staking and no longer earns rewards.
    /// @param unlockTimestamp When the withdrawal can be completed.
    struct PendingWithdrawal {
        uint256 amount;
        uint64 unlockTimestamp;
    }

    /// @notice Mapping of user to their single active pending withdrawal (if any).
    mapping(address => PendingWithdrawal) public pendingWithdrawals;

    // ====== Events ======

    /// @notice Emitted when the contract is initialized with its parameters.
    /// @param _stakeToken The token users deposit as principal.
    /// @param _rewardToken The token used for rewards (may equal `_stakeToken`).
    /// @param _initialRewardRate Initial `rewardRate` in tokens per second.
    /// @param initialOwner Address to receive contract ownership.
    /// @param _maxRewardRate Governance max for `rewardRate` (tokens/sec).
    /// @param _rateChangeDelay Timelock delay for rate changes (in seconds).
    /// @param _initialWithdrawDelay Initial locked withdrawal delay (in seconds).
    /// @param _minStakeAmount Minimum amount required to stake.
    /// @dev Sets `lastUpdateTime` to `block.timestamp` and enforces `_initialRewardRate <= _maxRewardRate`.
    ///      Emits `RewardRateUpdated`, `WithdrawDelayUpdated`, and `MinStakeAmountUpdated` events.
    /// @dev This event is emitted when the contract is initialized with its parameters.
    ///      It provides a record of the initial configuration of the staking pool.
    ///      This is useful for transparency and auditing purposes, allowing users to
    ///      verify the initial setup of the staking contract.
    /// @dev This event is emitted when the contract is initialized with its parameters.
    event Initialized(
        address indexed _stakeToken,
        address indexed _rewardToken,
        uint256 _initialRewardRate,
        address indexed initialOwner,
        uint256 _maxRewardRate,
        uint256 _minRewardRate,
        uint64 _rateChangeDelay,
        uint64 _initialWithdrawDelay,
        uint256 _minStakeAmount
    );

    /// @notice Emitted when emergency exit mode is enabled/disabled.
    /// @dev When enabled, users can withdraw their principal immediately, forfeiting any accrued rewards.
    ///      When disabled, users must use the delayed withdrawal path to claim their principal.
    /// @param enabled True if emergency exits are enabled; false if disabled.
    event EmergencyExitEnabled(bool enabled);

    /// @notice Emitted when the minimum stake amount is updated.
    /// @param oldAmount The previous minimum stake amount.
    /// @param newAmount The new minimum stake amount.
    /// @dev This is a governance-controlled parameter that can be adjusted by the owner.
    ///      It sets the minimum amount required for users to stake or withdraw.
    ///      This is useful to prevent dust transactions and ensure meaningful participation.
    event MinStakeAmountUpdated(uint256 oldAmount, uint256 newAmount);

    /// @notice Emitted when `sender` stakes `amount` on behalf of `to`.
    /// @param sender The caller providing stake tokens.
    /// @param to The recipient whose balance increases.
    /// @param amount The amount staked.
    event Staked(address indexed sender, address indexed to, uint256 amount);

    /// @notice Emitted when `user` is paid `amount` of rewards to `to`.
    /// @param user The user whose rewards were claimed/reset.
    /// @param to Recipient of rewards.
    /// @param amount The reward amount paid.
    event RewardPaid(address indexed user, address indexed to, uint256 amount);

    /// @notice Emitted when emission rate is updated.
    /// @param oldRate Previous `rewardRate`.
    /// @param newRate New `rewardRate`.
    event RewardRateUpdated(uint256 oldRate, uint256 newRate);

    /// @notice Emitted when a reward rate change is proposed with a timelock.
    /// @param proposedRate The proposed reward rate (tokens/sec).
    /// @param executeAfter The earliest timestamp when execution is allowed.
    event RewardRateProposed(uint256 proposedRate, uint64 executeAfter);

    /// @notice Emitted when a pending reward rate change is canceled.
    /// @param canceledRate The previously proposed reward rate that was canceled.
    event RewardRateChangeCanceled(uint256 canceledRate);

    /// @notice Emitted when rewards are prefunded.
    /// @param from Funding source (owner).
    /// @param amount Net tokens received (uses balance delta, so may differ from input due to token quirks).
    /// @param newReserves Updated `rewardReserves` after funding.
    event RewardsFunded(address indexed from, uint256 amount, uint256 newReserves);

    /// @notice Emitted on emergency withdrawal (principal returned, rewards forfeited).
    /// @param user The user who exited via emergency withdrawal.
    /// @param to Recipient of principal (typically `user`).
    /// @param amount Principal returned.
    event EmergencyWithdraw(address indexed user, address indexed to, uint256 amount);

    /// @notice Emitted when unrelated ERC-20 tokens are rescued.
    /// @param token The rescued token address.
    /// @param to Recipient of rescued tokens.
    /// @param amount Amount rescued.
    event RescueTokens(address indexed token, address indexed to, uint256 amount);

    /// @notice Emitted when a delayed withdrawal is requested.
    /// @param user The address requesting withdrawal.
    /// @param amount Amount removed from staking and placed into the pending queue.
    /// @param unlockTimestamp Timestamp when withdrawal becomes claimable.
    event WithdrawalRequested(address indexed user, uint256 amount, uint64 unlockTimestamp);

    /// @notice Emitted when a pending withdrawal is completed and principal is transferred out.
    /// @param user The address completing withdrawal.
    /// @param amount The amount withdrawn.
    event WithdrawalCompleted(address indexed user, uint256 amount);

    /// @notice Emitted when a pending withdrawal is canceled and principal is re-staked.
    /// @param user The address canceling withdrawal.
    /// @param amount The amount returned to staking.
    event WithdrawalCanceled(address indexed user, uint256 amount);

    /// @notice Emitted when the withdrawal delay is updated.
    /// @param oldDelay Previous delay (seconds).
    /// @param newDelay New delay (seconds).
    event WithdrawDelayUpdated(uint64 oldDelay, uint64 newDelay);

    // ====== Errors ======

    /// @notice Thrown when emergency exits are disabled and a user tries to withdraw immediately.
    error EmergencyExitDisabled();

    /// @notice Thrown when a provided amount is zero where a positive value is required.
    error AmountZero();

    /// @notice Thrown when a requested delay exceeds the maximum allowed.
    /// @param requested The requested delay in seconds.
    /// @param max The maximum allowed delay in seconds.
    error DelayTooLong(uint64 requested, uint64 max);

    /// @notice Thrown when a provided amount is below the minimum required for staking/unstaking.
    /// @param provided The amount provided by the user.
    /// @param minRequired The minimum amount required to proceed.
    error AmountTooLow(uint256 provided, uint256 minRequired);

    /// @notice Thrown when a user attempts to withdraw/claim more than available.
    error InsufficientBalance();

    /// @notice Thrown when an operation targets an invalid token address or disallowed token.
    error InvalidToken();

    /// @notice Thrown when a proposed rate exceeds `MAX_REWARD_RATE`.
    /// @param requested The requested rate.
    /// @param max The maximum allowed rate.
    error RewardRateTooHigh(uint256 requested, uint256 max);

    /// @notice Thrown when a proposed reward rate is below the minimum allowed rate.
    /// @param requested The requested rate.
    /// @param min The minimum allowed rate.
    error RewardRateTooLow(uint256 requested, uint256 min);

    /// @notice Thrown when trying to execute a rate change before the timelock elapses.
    /// @param executeAfter The timestamp after which execution is allowed.
    error RateChangeDelayNotMet(uint64 executeAfter);

    /// @notice Thrown when no pending reward rate exists to execute or cancel.
    error NoPendingRate();

    /// @notice Thrown when a user already has an active pending withdrawal.
    error PendingWithdrawalExists();

    /// @notice Thrown when a user has no pending withdrawal to act upon.
    error NoPendingWithdrawal();

    /// @notice Thrown when attempting to complete a withdrawal before it's unlocked.
    /// @param unlockTimestamp The timestamp when completion becomes allowed.
    error WithdrawalNotUnlocked(uint64 unlockTimestamp);

    // ====== Governance params (constructor-initialized) ======

    /// @notice Maximum allowed emission rate (tokens/sec).
    uint256 public immutable MAX_REWARD_RATE;

    /// @notice Minimum allowed emission rate (tokens/sec). Set to 0 to allow pausing.
    uint256 public immutable MIN_REWARD_RATE;

    /// @notice Maximum withdraw delay (in seconds).
    /// @dev This is a governance-controlled parameter that can be adjusted by the owner.
    ///      It sets the maximum delay for withdrawals, ensuring that users cannot set excessively long delays
    uint32 public constant MAX_WITHDRAW_DELAY = 30 days;

    /// @notice Delay required between proposing and executing a reward rate change.
    uint64 public immutable RATE_CHANGE_DELAY;

    /// @notice Proposed reward rate pending execution after `rateChangeExecuteAfter`.
    uint256 public pendingRewardRate;

    /// @notice Earliest timestamp when the pending reward rate can be executed.
    uint64 public rateChangeExecuteAfter;

    /// @notice Minimum amount required to stake/unstake
    uint256 public minStakeAmount;

    // ====== Construction ======

    /// @notice Deploy the staking pool.
    /// @param _stakeToken ERC-20 token users deposit as principal.
    /// @param _rewardToken ERC-20 token used for rewards (may equal `_stakeToken`).
    /// @param _initialRewardRate Initial `rewardRate` in tokens per second.
    /// @param initialOwner Address to receive contract ownership.
    /// @param _maxRewardRate Governance max for `rewardRate` (tokens/sec).
    /// @param _minRewardRate Governance min for `rewardRate` (tokens/sec). Set to 0 to allow pausing.
    /// @param _rateChangeDelay Timelock delay for rate changes (in seconds).
    /// @param _initialWithdrawDelay Initial locked withdrawal delay (in seconds).
    /// @param _minStakeAmount Minimum amount required to stake or withdraw.
    /// @dev Sets `lastUpdateTime` to `block.timestamp` and enforces `_minRewardRate <= _initialRewardRate <= _maxRewardRate`.
    constructor(
        IERC20 _stakeToken,
        IERC20 _rewardToken,
        uint256 _initialRewardRate,
        address initialOwner,
        uint256 _maxRewardRate,
        uint256 _minRewardRate,
        uint64 _rateChangeDelay,
        uint64 _initialWithdrawDelay,
        uint256 _minStakeAmount
    ) Ownable(initialOwner) {
        if (address(_stakeToken) == address(0)) revert InvalidToken();
        if (address(_rewardToken) == address(0)) revert InvalidToken();
        if (_initialWithdrawDelay > MAX_WITHDRAW_DELAY) revert DelayTooLong(_initialWithdrawDelay, MAX_WITHDRAW_DELAY);
        if (_minRewardRate > _maxRewardRate) revert RewardRateTooLow(_minRewardRate, _maxRewardRate);
        if (_initialRewardRate < _minRewardRate) revert RewardRateTooLow(_initialRewardRate, _minRewardRate);
        if (_initialRewardRate > _maxRewardRate) revert RewardRateTooHigh(_initialRewardRate, _maxRewardRate);

        STAKE_TOKEN = _stakeToken;
        REWARD_TOKEN = _rewardToken;
        MAX_REWARD_RATE = _maxRewardRate;
        MIN_REWARD_RATE = _minRewardRate;
        RATE_CHANGE_DELAY = _rateChangeDelay;

        rewardRate = _initialRewardRate;
        withdrawDelay = _initialWithdrawDelay;
        minStakeAmount = _minStakeAmount;
        lastUpdateTime = uint64(block.timestamp);

        emit Initialized(
            address(_stakeToken),
            address(_rewardToken),
            _initialRewardRate,
            initialOwner,
            _maxRewardRate,
            _minRewardRate,
            _rateChangeDelay,
            _initialWithdrawDelay,
            _minStakeAmount
        );
    }

    // =========================
    //          Views
    // =========================

    /// @notice Current staked balance for `account`.
    /// @param account The user to query.
    /// @return amount The staked principal.
    function balanceOf(address account) external view returns (uint256 amount) {
        return users[account].balance;
    }

    /// @notice The timestamp used for reward calculations.
    /// @dev Returns `block.timestamp`. Split out as a function for clarity/extensibility.
    /// @return ts The timestamp at which rewards are applicable.
    function lastTimeRewardApplicable() public view returns (uint256 ts) {
        return block.timestamp;
    }

    /// @notice The current global rewards-per-token index (scaled by 1e18).
    /// @dev
    /// - If `totalStaked == 0`, returns the stored value (no accrual).
    /// - Applies reserve cap: at most `rewardReserves` may be accounted.
    /// @return rpt The current `rewardPerToken` value including any un-snapshotted elapsed window (view path).
    function rewardPerToken() public view returns (uint256 rpt) {
        if (totalStaked == 0) return rewardPerTokenStored;

        uint256 elapsed = lastTimeRewardApplicable() - uint256(lastUpdateTime);
        if (elapsed == 0) return rewardPerTokenStored;

        uint256 newly = elapsed * rewardRate;
        if (newly > rewardReserves) {
            newly = rewardReserves; // cap by reserves so we never over-accrue
        }

        // 1e18 scaling
        return rewardPerTokenStored + (newly * 1e18) / totalStaked;
    }

    /// @notice View the total rewards owed to `account` at the current timestamp.
    /// @dev Computed as: `u.rewards + u.balance * (rewardPerToken() - u.userRewardPerTokenPaid) / 1e18`.
    ///      This is a **view**; reserves are not consumed here (consumption happens on state updates).
    /// @param account The user to query.
    /// @return amount The accrued but unclaimed rewards.
    function earned(address account) public view returns (uint256 amount) {
        User memory u = users[account];
        uint256 rpt = rewardPerToken();
        return u.rewards + (u.balance * (rpt - u.userRewardPerTokenPaid)) / 1e18;
    }

    /// @notice View the total rewards reserves runway in seconds.
    /// @dev If `rewardRate == 0`, returns `type(uint256).max` (infinite runway).
    ///      Otherwise, computes `rewardReserves / rewardRate`.
    /// @return seconds The number of seconds the current reserves can sustain at the current rate.
    function rewardsRunwaySeconds() external view returns (uint256) {
        if (rewardRate == 0) return type(uint256).max;
        return rewardReserves / rewardRate;
    }

    // =========================
    //          Admin
    // =========================

    /// @notice Propose a new reward emission rate (tokens per second), subject to a delay.
    /// @dev Enforces `MIN_REWARD_RATE` and `MAX_REWARD_RATE`. Allows pausing with `0` if `MIN_REWARD_RATE` is 0. Emits `RewardRateProposed`.
    /// @param _newRate The proposed `rewardRate` value (tokens/sec).
    function proposeRewardRate(uint256 _newRate) external onlyOwner {
        if (_newRate < MIN_REWARD_RATE) revert RewardRateTooLow(_newRate, MIN_REWARD_RATE);
        if (_newRate > MAX_REWARD_RATE) revert RewardRateTooHigh(_newRate, MAX_REWARD_RATE);
        uint64 execAfter = uint64(block.timestamp) + RATE_CHANGE_DELAY;

        pendingRewardRate = _newRate;
        rateChangeExecuteAfter = execAfter;

        emit RewardRateProposed(_newRate, execAfter);
    }

    /// @notice Execute a previously proposed reward rate after the timelock elapses. Intentionally callable by anyone.
    /// @dev Snapshots global accounting first, then updates `rewardRate` and emits `RewardRateUpdated`.
    function executeRewardRateChange() external {
        uint64 execAfter = rateChangeExecuteAfter;
        if (execAfter == 0) revert NoPendingRate();
        if (block.timestamp < execAfter) revert RateChangeDelayNotMet(execAfter);

        _updateGlobal();

        uint256 old = rewardRate;
        uint256 next = pendingRewardRate;

        // clear pending first
        pendingRewardRate = 0;
        rateChangeExecuteAfter = 0;

        emit RewardRateUpdated(old, next);
        rewardRate = next;
    }

    /// @notice Cancel a pending reward rate change.
    /// @dev Clears pending state and emits `RewardRateChangeCanceled`.
    function cancelRewardRateChange() external onlyOwner {
        uint256 oldPending = pendingRewardRate;
        if (oldPending == 0) revert NoPendingRate();

        pendingRewardRate = 0;
        rateChangeExecuteAfter = 0;

        emit RewardRateChangeCanceled(oldPending);
    }

    /// @notice Prefund rewards into the pool.
    /// @dev
    /// - Uses **balance delta** to compute the net tokens actually received, which makes it robust
    ///   to some non-standard ERC-20 implementations (e.g., fee-on-transfer).
    /// - The **net** received amount is credited to `rewardReserves`.
    /// - Reentrancy is guarded.
    /// @param amount The nominal amount to transfer from the owner.
    function fundRewards(uint256 amount) external onlyOwner nonReentrant {
        if (amount == 0) revert AmountZero();

        uint256 beforeBal = REWARD_TOKEN.balanceOf(address(this));
        REWARD_TOKEN.safeTransferFrom(msg.sender, address(this), amount);
        uint256 received = REWARD_TOKEN.balanceOf(address(this)) - beforeBal;
        if (received == 0) revert AmountZero(); // defensive: nothing moved

        rewardReserves += received;
        emit RewardsFunded(msg.sender, received, rewardReserves);
    }

    /// @notice Rescue unrelated tokens accidentally sent to this contract.
    /// @dev Cannot rescue `STAKE_TOKEN` or `REWARD_TOKEN`.
    /// @param token The ERC-20 token to rescue.
    /// @param to Recipient for rescued tokens.
    /// @param amount The amount to rescue.
    function rescueTokens(IERC20 token, address to, uint256 amount) external onlyOwner {
        if (address(token) == address(STAKE_TOKEN)) revert InvalidToken();
        if (address(token) == address(REWARD_TOKEN)) revert InvalidToken();
        token.safeTransfer(to, amount);
        emit RescueTokens(address(token), to, amount);
    }

    /// @notice Set the withdrawal delay (in seconds) for delayed withdrawals.
    /// @dev Emits `WithdrawDelayUpdated`. Can be set to 0 to allow immediate completion after request.
    /// @param newDelay The new delay duration in seconds.
    function setWithdrawDelay(uint64 newDelay) external onlyOwner {
        if (newDelay > MAX_WITHDRAW_DELAY) revert DelayTooLong(newDelay, MAX_WITHDRAW_DELAY);
        uint64 old = withdrawDelay;
        withdrawDelay = newDelay;
        emit WithdrawDelayUpdated(old, newDelay);
    }

    /// @notice Enable/disable immediate emergency exits that forfeit rewards.
    /// @dev Default should be false in production; only enable during incidents.
    /// @param enabled True to enable emergency exits; false to disable.
    function setEmergencyExitEnabled(bool enabled) external onlyOwner {
        emergencyExitEnabled = enabled;
        emit EmergencyExitEnabled(enabled);
    }

    // @notice Set the minimum stake amount required for staking or withdrawing.
    /// @dev Emits `MinStakeAmountUpdated`. This is a governance-controlled parameter that can be adjusted by the owner.
    ///      It sets the minimum amount required for users to stake or withdraw.
    ///      This is useful to prevent dust transactions and ensure meaningful participation.
    /// @param newMinStakeAmount The new minimum stake amount in tokens.
    function setMinStakeAmount(uint256 newMinStakeAmount) external onlyOwner {
        uint256 oldMinStakeAmount = minStakeAmount;
        minStakeAmount = newMinStakeAmount;
        emit MinStakeAmountUpdated(oldMinStakeAmount, newMinStakeAmount);
    }

    // =========================
    //       User actions
    // =========================

    /// @notice Stake `amount` for yourself.
    /// @param amount Amount of `STAKE_TOKEN` to deposit.
    function stake(uint256 amount) external {
        stakeFor(amount, msg.sender);
    }

    /// @notice Stake `amount` on behalf of `to`.
    /// @dev
    /// - Updates global & user accounting first to snapshot prior rewards.
    /// - Transfers `amount` from `msg.sender` to the pool.
    /// - **Note:** If `STAKE_TOKEN` is fee-on-transfer, the recipient may receive less than `amount`,
    ///   and this function does **not** use balance delta; such tokens are not supported.
    /// @param amount Amount of `STAKE_TOKEN` to deposit.
    /// @param to Recipient whose stake balance increases.
    function stakeFor(uint256 amount, address to) public nonReentrant {
        if (amount == 0) revert AmountZero();
        if (amount < minStakeAmount) revert AmountTooLow(amount, minStakeAmount);

        _updateUser(to);

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

        users[to].balance += amount;
        totalStaked += amount;

        emit Staked(msg.sender, to, amount);
    }

    /// @notice Claim your rewards to your own address.
    /// @dev Updates accounting and pays out the owed rewards; resets `users[msg.sender].rewards` to 0.
    function getReward() external nonReentrant {
        _updateUser(msg.sender);

        uint256 reward = users[msg.sender].rewards;
        if (reward == 0) revert InsufficientBalance();
        users[msg.sender].rewards = 0;

        REWARD_TOKEN.safeTransfer(msg.sender, reward);
        emit RewardPaid(msg.sender, msg.sender, reward);
    }

    /// @notice Claim rewards to a custom address (e.g., auto-compounder).
    /// @dev Updates accounting and pays out the owed rewards to `to`; resets internal owed to 0.
    /// @param to Recipient of the rewards.
    function getRewardTo(address to) external nonReentrant {
        _updateUser(msg.sender);

        uint256 reward = users[msg.sender].rewards;
        if (reward == 0) revert InsufficientBalance();
        users[msg.sender].rewards = 0;

        REWARD_TOKEN.safeTransfer(to, reward);
        emit RewardPaid(msg.sender, to, reward);
    }

    /// @notice Initiate a delayed withdrawal request.
    /// @dev
    /// - Updates accounting, then **removes** `amount` from staking so it stops earning immediately.
    /// - Records a single pending withdrawal that becomes claimable after `withdrawDelay`.
    /// - Reverts if a pending withdrawal already exists for the caller.
    /// @param amount Amount of staked tokens to request withdrawal for.
    function requestWithdrawal(uint256 amount) external nonReentrant {
        if (amount == 0) revert AmountZero();

        _updateUser(msg.sender);

        User storage u = users[msg.sender];
        if (u.balance < amount) revert InsufficientBalance();

        PendingWithdrawal storage p = pendingWithdrawals[msg.sender];
        if (p.amount > 0) revert PendingWithdrawalExists();

        // Remove principal from staking (stops rewards immediately)
        u.balance -= amount;
        totalStaked -= amount;

        uint64 unlockAt = uint64(block.timestamp) + withdrawDelay;
        p.amount = amount;
        p.unlockTimestamp = unlockAt;

        emit WithdrawalRequested(msg.sender, amount, unlockAt);
    }

    /// @notice Complete a previously requested withdrawal after the delay.
    /// @dev
    /// - Does **not** modify rewards; rewards remain claimable separately at any time.
    /// - Clears pending state before transfer to prevent reentrancy issues.
    function completeWithdrawal() external nonReentrant {
        PendingWithdrawal storage p = pendingWithdrawals[msg.sender];
        uint256 amount = p.amount;
        if (amount == 0) revert NoPendingWithdrawal();
        uint64 unlockAt = p.unlockTimestamp;
        if (block.timestamp < unlockAt) revert WithdrawalNotUnlocked(unlockAt);

        // Clear pending state first (effects)
        p.amount = 0;
        p.unlockTimestamp = 0;

        STAKE_TOKEN.safeTransfer(msg.sender, amount);

        emit WithdrawalCompleted(msg.sender, amount);
    }

    /// @notice Cancel a pending withdrawal and re-stake the principal.
    /// @dev
    /// - Updates accounting first so the user **does not** backfill rewards for the pending period.
    /// - Adds the pending amount back to `users[msg.sender].balance` and `totalStaked`.
    function cancelWithdrawal() external nonReentrant {
        PendingWithdrawal storage p = pendingWithdrawals[msg.sender];
        uint256 amount = p.amount;
        if (amount == 0) revert NoPendingWithdrawal();

        _updateUser(msg.sender);

        // return principal to staking
        users[msg.sender].balance += amount;
        totalStaked += amount;

        // clear pending
        p.amount = 0;
        p.unlockTimestamp = 0;

        emit WithdrawalCanceled(msg.sender, amount);
    }

    /// @notice Withdraw staked principal immediately, **forfeiting** any accrued rewards.
    /// @dev
    /// - Calls `_updateGlobal()` (not `_updateUser`) to keep global math consistent.
    /// - Calculates forfeited rewards and returns them to rewardReserves to prevent reserve grief.
    /// - Zeros user principal & rewards and snaps `userRewardPerTokenPaid` to the latest global index.
    function emergencyWithdraw() external nonReentrant {
        if (!emergencyExitEnabled) revert EmergencyExitDisabled();

        User storage u = users[msg.sender];
        PendingWithdrawal storage p = pendingWithdrawals[msg.sender];

        uint256 amount = u.balance + p.amount; // Include pending
        if (amount == 0) revert InsufficientBalance();

        // Calculate forfeited rewards before updating global state
        uint256 forfeitedRewards = 0;
        if (u.balance > 0) {
            uint256 rpt = rewardPerToken();
            forfeitedRewards = u.rewards + (u.balance * (rpt - u.userRewardPerTokenPaid)) / 1e18;
        }

        _updateGlobal(); // keep global math consistent

        totalStaked -= u.balance; // Only deduct actual staked amount

        // Return forfeited rewards to reserves to prevent grief attack
        if (forfeitedRewards > 0) {
            rewardReserves += forfeitedRewards;
        }

        // Forfeit rewards and reset state
        u.balance = 0;
        u.rewards = 0;
        u.userRewardPerTokenPaid = rewardPerTokenStored;
        p.amount = 0;
        p.unlockTimestamp = 0;

        STAKE_TOKEN.safeTransfer(msg.sender, amount);
        emit EmergencyWithdraw(msg.sender, msg.sender, amount);
    }

    // =========================
    //        Internals
    // =========================

    /// @notice Update global reward accounting up to `block.timestamp`.
    /// @dev
    /// - No-op if `current == lastUpdateTime`.
    /// - If `totalStaked > 0`, increases `rewardPerTokenStored` by:
    ///       `deltaRPT = min(elapsed * rewardRate, rewardReserves) * 1e18 / totalStaked`
    ///   and **consumes** the same (uncapped) `newly` from `rewardReserves`.
    /// - Always updates `lastUpdateTime` to `current`.
    function _updateGlobal() internal {
        uint256 current = block.timestamp;
        uint256 last = uint256(lastUpdateTime);
        if (current == last) {
            return;
        }

        if (totalStaked > 0) {
            uint256 elapsed = current - last;
            uint256 newly = elapsed * rewardRate;

            // Cap accrual by reserves, so we never over-account rewards
            if (newly > rewardReserves) {
                newly = rewardReserves;
            }

            if (newly > 0) {
                uint256 rewardPerTokenIncrease = (newly * 1e18) / totalStaked;
                if (rewardPerTokenIncrease > 0) {
                    rewardPerTokenStored += rewardPerTokenIncrease;
                    rewardReserves -= newly; // move from reserves to "owed but unpaid"
                }
            }
        }

        lastUpdateTime = uint64(current);
    }

    /// @notice Update a user's accounting against the latest global snapshot.
    /// @dev
    /// - Calls `_updateGlobal()` first (ensuring global index is up to date).
    /// - If user has a positive balance, accrues:
    ///       `u.rewards += u.balance * (rewardPerTokenStored - u.userRewardPerTokenPaid) / 1e18`
    /// - Sets `u.userRewardPerTokenPaid = rewardPerTokenStored`.
    /// @param account The user to update.
    function _updateUser(address account) internal {
        _updateGlobal();

        User storage u = users[account];
        if (u.balance > 0) {
            uint256 delta = rewardPerTokenStored - u.userRewardPerTokenPaid;
            if (delta != 0) {
                u.rewards += (u.balance * delta) / 1e18;
            }
        }
        u.userRewardPerTokenPaid = rewardPerTokenStored;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-20 standard as defined in the ERC.
 */
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.2.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

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

/**
 * @title SafeERC20
 * @dev Wrappers around ERC-20 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 {
    /**
     * @dev An operation with an ERC-20 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.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    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.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    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.
     *
     * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
     * only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
     * set here.
     */
    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 Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            safeTransfer(token, to, value);
        } else if (!token.transferAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
     * has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferFromAndCallRelaxed(
        IERC1363 token,
        address from,
        address to,
        uint256 value,
        bytes memory data
    ) internal {
        if (to.code.length == 0) {
            safeTransferFrom(token, from, to, value);
        } else if (!token.transferFromAndCall(from, to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
     * Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
     * once without retrying, and relies on the returned value to be true.
     *
     * Reverts if the returned value is other than `true`.
     */
    function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            forceApprove(token, to, value);
        } else if (!token.approveAndCall(to, value, data)) {
            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 {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            // bubble errors
            if iszero(success) {
                let ptr := mload(0x40)
                returndatacopy(ptr, 0, returndatasize())
                revert(ptr, returndatasize())
            }
            returnSize := returndatasize()
            returnValue := mload(0)
        }

        if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
            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 silently catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        bool success;
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            returnSize := returndatasize()
            returnValue := mload(0)
        }
        return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (access/Ownable2Step.sol)

pragma solidity ^0.8.20;

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

/**
 * @dev Contract module which provides access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * This extension of the {Ownable} contract includes a two-step mechanism to transfer
 * ownership, where the new owner must call {acceptOwnership} in order to replace the
 * old one. This can help prevent common mistakes, such as transfers of ownership to
 * incorrect accounts, or to contracts that are unable to interact with the
 * permission system.
 *
 * The initial owner is specified at deployment time in the constructor for `Ownable`. This
 * can later be changed with {transferOwnership} and {acceptOwnership}.
 *
 * This module is used through inheritance. It will make available all functions
 * from parent (Ownable).
 */
abstract contract Ownable2Step is Ownable {
    address private _pendingOwner;

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

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

    /**
     * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
     * Can only be called by the current owner.
     *
     * Setting `newOwner` to the zero address is allowed; this can be used to cancel an initiated ownership transfer.
     */
    function transferOwnership(address newOwner) public virtual override onlyOwner {
        _pendingOwner = newOwner;
        emit OwnershipTransferStarted(owner(), newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual override {
        delete _pendingOwner;
        super._transferOwnership(newOwner);
    }

    /**
     * @dev The new owner accepts the ownership transfer.
     */
    function acceptOwnership() public virtual {
        address sender = _msgSender();
        if (pendingOwner() != sender) {
            revert OwnableUnauthorizedAccount(sender);
        }
        _transferOwnership(sender);
    }
}

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

pragma solidity ^0.8.20;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
 * consider using {ReentrancyGuardTransient} instead.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant NOT_ENTERED = 1;
    uint256 private constant ENTERED = 2;

    uint256 private _status;

    /**
     * @dev Unauthorized reentrant call.
     */
    error ReentrancyGuardReentrantCall();

    constructor() {
        _status = NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be NOT_ENTERED
        if (_status == ENTERED) {
            revert ReentrancyGuardReentrantCall();
        }

        // Any calls to nonReentrant after this point will fail
        _status = ENTERED;
    }

    function _nonReentrantAfter() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = NOT_ENTERED;
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        return _status == ENTERED;
    }
}

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

pragma solidity ^0.8.20;

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

/**
 * @title IERC1363
 * @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].
 *
 * Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract
 * after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.
 */
interface IERC1363 is IERC20, IERC165 {
    /*
     * Note: the ERC-165 identifier for this interface is 0xb0202a11.
     * 0xb0202a11 ===
     *   bytes4(keccak256('transferAndCall(address,uint256)')) ^
     *   bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
     *   bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
     *   bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^
     *   bytes4(keccak256('approveAndCall(address,uint256)')) ^
     *   bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
     */

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferAndCall(address to, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @param data Additional data with no specified format, sent in call to `to`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param from The address which you want to send tokens from.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferFromAndCall(address from, address to, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param from The address which you want to send tokens from.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @param data Additional data with no specified format, sent in call to `to`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
     * @param spender The address which will spend the funds.
     * @param value The amount of tokens to be spent.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function approveAndCall(address spender, uint256 value) external returns (bool);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
     * @param spender The address which will spend the funds.
     * @param value The amount of tokens to be spent.
     * @param data Additional data with no specified format, sent in call to `spender`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
}

// 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 8 of 11 : IERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol)

pragma solidity ^0.8.20;

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

File 9 of 11 : IERC165.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol)

pragma solidity ^0.8.20;

import {IERC165} from "../utils/introspection/IERC165.sol";

// 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.1.0) (utils/introspection/IERC165.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[ERC].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

Settings
{
  "remappings": [
    "@openzeppelin/=lib/openzeppelin-contracts/",
    "erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
    "forge-std/=lib/forge-std/src/",
    "halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/",
    "openzeppelin-contracts/=lib/openzeppelin-contracts/"
  ],
  "optimizer": {
    "enabled": true,
    "runs": 10000
  },
  "metadata": {
    "useLiteralContent": false,
    "bytecodeHash": "ipfs",
    "appendCBOR": true
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "evmVersion": "shanghai",
  "viaIR": false
}

Contract Security Audit

Contract ABI

API
[{"inputs":[{"internalType":"contract IERC20","name":"_stakeToken","type":"address"},{"internalType":"contract IERC20","name":"_rewardToken","type":"address"},{"internalType":"uint256","name":"_initialRewardRate","type":"uint256"},{"internalType":"address","name":"initialOwner","type":"address"},{"internalType":"uint256","name":"_maxRewardRate","type":"uint256"},{"internalType":"uint256","name":"_minRewardRate","type":"uint256"},{"internalType":"uint64","name":"_rateChangeDelay","type":"uint64"},{"internalType":"uint64","name":"_initialWithdrawDelay","type":"uint64"},{"internalType":"uint256","name":"_minStakeAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"provided","type":"uint256"},{"internalType":"uint256","name":"minRequired","type":"uint256"}],"name":"AmountTooLow","type":"error"},{"inputs":[],"name":"AmountZero","type":"error"},{"inputs":[{"internalType":"uint64","name":"requested","type":"uint64"},{"internalType":"uint64","name":"max","type":"uint64"}],"name":"DelayTooLong","type":"error"},{"inputs":[],"name":"EmergencyExitDisabled","type":"error"},{"inputs":[],"name":"InsufficientBalance","type":"error"},{"inputs":[],"name":"InvalidToken","type":"error"},{"inputs":[],"name":"NoPendingRate","type":"error"},{"inputs":[],"name":"NoPendingWithdrawal","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[],"name":"PendingWithdrawalExists","type":"error"},{"inputs":[{"internalType":"uint64","name":"executeAfter","type":"uint64"}],"name":"RateChangeDelayNotMet","type":"error"},{"inputs":[],"name":"ReentrancyGuardReentrantCall","type":"error"},{"inputs":[{"internalType":"uint256","name":"requested","type":"uint256"},{"internalType":"uint256","name":"max","type":"uint256"}],"name":"RewardRateTooHigh","type":"error"},{"inputs":[{"internalType":"uint256","name":"requested","type":"uint256"},{"internalType":"uint256","name":"min","type":"uint256"}],"name":"RewardRateTooLow","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"inputs":[{"internalType":"uint64","name":"unlockTimestamp","type":"uint64"}],"name":"WithdrawalNotUnlocked","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bool","name":"enabled","type":"bool"}],"name":"EmergencyExitEnabled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"EmergencyWithdraw","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_stakeToken","type":"address"},{"indexed":true,"internalType":"address","name":"_rewardToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"_initialRewardRate","type":"uint256"},{"indexed":true,"internalType":"address","name":"initialOwner","type":"address"},{"indexed":false,"internalType":"uint256","name":"_maxRewardRate","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_minRewardRate","type":"uint256"},{"indexed":false,"internalType":"uint64","name":"_rateChangeDelay","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"_initialWithdrawDelay","type":"uint64"},{"indexed":false,"internalType":"uint256","name":"_minStakeAmount","type":"uint256"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newAmount","type":"uint256"}],"name":"MinStakeAmountUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"RescueTokens","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"RewardPaid","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"canceledRate","type":"uint256"}],"name":"RewardRateChangeCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"proposedRate","type":"uint256"},{"indexed":false,"internalType":"uint64","name":"executeAfter","type":"uint64"}],"name":"RewardRateProposed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldRate","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newRate","type":"uint256"}],"name":"RewardRateUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newReserves","type":"uint256"}],"name":"RewardsFunded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Staked","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"oldDelay","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"newDelay","type":"uint64"}],"name":"WithdrawDelayUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"WithdrawalCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"WithdrawalCompleted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint64","name":"unlockTimestamp","type":"uint64"}],"name":"WithdrawalRequested","type":"event"},{"inputs":[],"name":"MAX_REWARD_RATE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_WITHDRAW_DELAY","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_REWARD_RATE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RATE_CHANGE_DELAY","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REWARD_TOKEN","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_TOKEN","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"cancelRewardRateChange","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"cancelWithdrawal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"completeWithdrawal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"earned","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"emergencyExitEnabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"emergencyWithdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"executeRewardRateChange","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"fundRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"getRewardTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"lastTimeRewardApplicable","outputs":[{"internalType":"uint256","name":"ts","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastUpdateTime","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minStakeAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingRewardRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"pendingWithdrawals","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint64","name":"unlockTimestamp","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newRate","type":"uint256"}],"name":"proposeRewardRate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rateChangeExecuteAfter","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"requestWithdrawal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"token","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"rescueTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rewardPerToken","outputs":[{"internalType":"uint256","name":"rpt","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardPerTokenStored","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardReserves","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardsRunwaySeconds","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"enabled","type":"bool"}],"name":"setEmergencyExitEnabled","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newMinStakeAmount","type":"uint256"}],"name":"setMinStakeAmount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint64","name":"newDelay","type":"uint64"}],"name":"setWithdrawDelay","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"stake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"to","type":"address"}],"name":"stakeFor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"totalStaked","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"users","outputs":[{"internalType":"uint256","name":"balance","type":"uint256"},{"internalType":"uint256","name":"userRewardPerTokenPaid","type":"uint256"},{"internalType":"uint256","name":"rewards","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"withdrawDelay","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"}]

61012060405234801562000011575f80fd5b506040516200262f3803806200262f833981016040819052620000349162000319565b856001600160a01b0381166200006457604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b6200006f816200027b565b5060016002556001600160a01b0389166200009d5760405163c1ab6dc160e01b815260040160405180910390fd5b6001600160a01b038816620000c55760405163c1ab6dc160e01b815260040160405180910390fd5b62278d006001600160401b03831611156200010857604051630f3d79f960e21b81526001600160401b038316600482015262278d0060248201526044016200005b565b848411156200013557604051635b573a6f60e11b815260048101859052602481018690526044016200005b565b838710156200016257604051635b573a6f60e11b815260048101889052602481018590526044016200005b565b848711156200018f576040516311a98bfd60e31b815260048101889052602481018690526044016200005b565b6001600160a01b03808a16608081905289821660a081905260c088905260e08790526001600160401b038087166101005260038b9055600a80548783166001600160401b031991821617909155600e869055600480544290931692909116919091179055604051928916929091907f2f86408737f09ccb191c1484b290ecc186ea894acf2f42ebb789096891e4b3379062000264908c908b908b908b908b908b90958652602086019490945260408501929092526001600160401b03908116606085015216608083015260a082015260c00190565b60405180910390a4505050505050505050620003b3565b600180546001600160a01b0319169055620002968162000299565b50565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b038116811462000296575f80fd5b80516001600160401b038116811462000314575f80fd5b919050565b5f805f805f805f805f6101208a8c03121562000333575f80fd5b89516200034081620002e8565b60208b01519099506200035381620002e8565b60408b015160608c015191995097506200036d81620002e8565b60808b015160a08c0151919750955093506200038c60c08b01620002fd565b92506200039c60e08b01620002fd565b91506101008a015190509295985092959850929598565b60805160a05160c05160e051610100516121d56200045a5f395f81816106b60152611b1301525f818161040f01528181611a090152611a5e01525f818161047301528181611a8c0152611ae101525f8181610519015281816109b501528181610ea901528181610f4301528181610fb201528181611380015261183201525f818161037001528181610bdc015281816112fb01528181611611015261175501526121d55ff3fe608060405234801561000f575f80fd5b50600436106102e2575f3560e01c80638da5cb5b11610187578063e03ff7cb116100dd578063f128632211610093578063f3f437031161006e578063f3f4370314610663578063fe7e6566146106b1578063ff142558146106d8575f80fd5b8063f12863221461063f578063f188768414610647578063f2fde38b14610650575f80fd5b8063e52ae7b3116100c3578063e52ae7b314610610578063eb4af04514610623578063f0de822814610636575f80fd5b8063e03ff7cb146105ea578063e30c3978146105f2575f80fd5b8063a87430ba1161013d578063cea9d26f11610118578063cea9d26f146105c6578063db2e21bc146105d9578063df136d65146105e1575f80fd5b8063a87430ba14610561578063c8f33c91146105aa578063cd3daf9d146105be575f80fd5b806399248ea71161016d57806399248ea7146105145780639ee679e81461053b578063a694fc3a1461054e575f80fd5b80638da5cb5b146104e457806393707a1914610501575f80fd5b80636b12686b1161023c5780637b0a47ee116101f257806380faa57d116101cd57806380faa57d146104c1578063817b1cd2146104c757806387a55692146104d0575f80fd5b80637b0a47ee1461049d5780637eb68f0f146104a65780637f73dffd146104b9575f80fd5b8063715018a611610222578063715018a614610466578063718dc3c31461046e57806379ba509714610495575f80fd5b80636b12686b1461040a57806370a0823114610431575f80fd5b80631fc3277d1161029c5780633d18b912116102775780633d18b912146103e7578063450d6a90146103ef57806351746bb2146103f7575f80fd5b80631fc3277d146103b757806322611280146103d657806333d4f626146103de575f80fd5b80630d36a9e9116102cc5780630d36a9e9146103395780630df25f1d146103565780631c39b6721461036b575f80fd5b80628cc262146102e65780630288a39c1461030c575b5f80fd5b6102f96102f4366004611fcd565b6106eb565b6040519081526020015b60405180910390f35b600a546103209067ffffffffffffffff1681565b60405167ffffffffffffffff9091168152602001610303565b6007546103469060ff1681565b6040519015158152602001610303565b610369610364366004611fef565b610783565b005b6103927f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610303565b6103c162278d0081565b60405163ffffffff9091168152602001610303565b610369610850565b6102f9600c5481565b610369610940565b610369610a33565b610369610405366004612016565b610b2e565b6102f97f000000000000000000000000000000000000000000000000000000000000000081565b6102f961043f366004611fcd565b73ffffffffffffffffffffffffffffffffffffffff165f9081526009602052604090205490565b610369610cb0565b6102f97f000000000000000000000000000000000000000000000000000000000000000081565b610369610cc1565b6102f960035481565b6103696104b4366004612044565b610d38565b610369610da6565b426102f9565b6102f960085481565b600d546103209067ffffffffffffffff1681565b5f5473ffffffffffffffffffffffffffffffffffffffff16610392565b61036961050f366004612063565b610e30565b6103927f000000000000000000000000000000000000000000000000000000000000000081565b610369610549366004612063565b6110c6565b61036961055c366004612063565b611259565b61058f61056f366004611fcd565b60096020525f908152604090208054600182015460029092015490919083565b60408051938452602084019290925290820152606001610303565b6004546103209067ffffffffffffffff1681565b6102f9611263565b6103696105d436600461207a565b6112f1565b610369611490565b6102f960055481565b61036961167d565b60015473ffffffffffffffffffffffffffffffffffffffff16610392565b61036961061e366004611fcd565b6117be565b610369610631366004612063565b6118c6565b6102f960065481565b6102f961190c565b6102f9600e5481565b61036961065e366004611fcd565b611950565b610693610671366004611fcd565b600b6020525f90815260409020805460019091015467ffffffffffffffff1682565b6040805192835267ffffffffffffffff909116602083015201610303565b6103207f000000000000000000000000000000000000000000000000000000000000000081565b6103696106e6366004612063565b6119ff565b73ffffffffffffffffffffffffffffffffffffffff81165f90815260096020908152604080832081516060810183528154815260018201549381019390935260020154908201528161073b611263565b9050670de0b6b3a764000082602001518261075691906120e5565b835161076291906120fe565b61076c9190612115565b826040015161077b919061214d565b949350505050565b61078b611b97565b62278d0067ffffffffffffffff821611156107ec576040517f3cf5e7e400000000000000000000000000000000000000000000000000000000815267ffffffffffffffff8216600482015262278d0060248201526044015b60405180910390fd5b600a805467ffffffffffffffff83811667ffffffffffffffff1983168117909355604080519190921680825260208201939093527f7bf377c6f26b11af5ca3523bbafe65b3cdd72a74e9a83c70b1a3c42d1014937891015b60405180910390a15050565b610858611be9565b335f908152600b60205260408120805490918190036108a3576040517f9121b84f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6108ac33611c2a565b335f90815260096020526040812080548392906108ca90849061214d565b925050819055508060085f8282546108e2919061214d565b90915550505f825560018201805467ffffffffffffffff1916905560405181815233907fc04d7db2ae23be2ba46893b43d7c4af2fc045905461f3df891e1d6c4d0683ef69060200160405180910390a2505061093e6001600255565b565b610948611be9565b61095133611c2a565b335f908152600960205260408120600201549081900361099d576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b335f818152600960205260408120600201556109f1907f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff169083611cbf565b604051818152339081907f540798df468d7b23d11f156fdb954cb19ad414d150722a7b6d55ba369dea792e9060200160405180910390a35061093e6001600255565b600d5467ffffffffffffffff165f819003610a7a576040517fbae04d1500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8067ffffffffffffffff16421015610aca576040517f9d58910b00000000000000000000000000000000000000000000000000000000815267ffffffffffffffff821660048201526024016107e3565b610ad2611d45565b600354600c80545f909155600d805467ffffffffffffffff1916905560408051838152602081018390527fc390a98ace15a7bb6bab611eedfdbb2685043b241a869420043cdfb23ccfee50910160405180910390a16003555050565b610b36611be9565b815f03610b6f576040517fcbca5aa200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600e54821015610bb957600e546040517f1cc6243f0000000000000000000000000000000000000000000000000000000081526107e3918491600401918252602082015260400190565b610bc281611c2a565b610c0473ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016333085611e1c565b73ffffffffffffffffffffffffffffffffffffffff81165f9081526009602052604081208054849290610c3890849061214d565b925050819055508160085f828254610c50919061214d565b909155505060405182815273ffffffffffffffffffffffffffffffffffffffff82169033907f5dac0c1b1112564a045ba943c9d50270893e8e826c49be8e7073adc713ab7bd79060200160405180910390a3610cac6001600255565b5050565b610cb8611b97565b61093e5f611e68565b600154339073ffffffffffffffffffffffffffffffffffffffff168114610d2c576040517f118cdaa700000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff821660048201526024016107e3565b610d3581611e68565b50565b610d40611b97565b600780547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00168215159081179091556040519081527fab5c2018f7e4bc1b76a4335b68c107fe55566a39f84abc9f3e6b0e6f23fe96cd906020015b60405180910390a150565b610dae611b97565b600c545f819003610deb576040517fbae04d1500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f600c55600d805467ffffffffffffffff191690556040518181527f0685795c7ec9e1e92ef2aaf7d85bdfbe11dd3d3ab29c2caae623beeaa91cba5490602001610d9b565b610e38611b97565b610e40611be9565b805f03610e79576040517fcbca5aa200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f907f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16906370a0823190602401602060405180830381865afa158015610f03573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f279190612160565b9050610f6b73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016333085611e1c565b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f90829073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016906370a0823190602401602060405180830381865afa158015610ff7573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061101b9190612160565b61102591906120e5565b9050805f03611060576040517fcbca5aa200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060065f828254611071919061214d565b909155505060065460405133917f17c4c73fe3b5d172ec864d377fb9e2323d3f90de65eed172cc20232b48ab2ae1916110b291858252602082015260400190565b60405180910390a25050610d356001600255565b6110ce611be9565b805f03611107576040517fcbca5aa200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61111033611c2a565b335f9081526009602052604090208054821115611159576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b335f908152600b602052604090208054156111a0576040517f75c4147300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b82825f015f8282546111b291906120e5565b925050819055508260085f8282546111ca91906120e5565b9091555050600a545f906111e89067ffffffffffffffff1642612177565b84835560018301805467ffffffffffffffff191667ffffffffffffffff831690811790915560408051878152602081019290925291925033917fbf6730b5b5d9fd45cdb42f69910317da629fd6e12b3ab41aa1708cd916385dfc910160405180910390a2505050610d356001600255565b610d358133610b2e565b5f6008545f03611274575060055490565b6004545f9061128d9067ffffffffffffffff16426120e5565b9050805f0361129e57505060055490565b5f600354826112ad91906120fe565b90506006548111156112be57506006545b6008546112d382670de0b6b3a76400006120fe565b6112dd9190612115565b6005546112ea919061214d565b9250505090565b6112f9611b97565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160361137e576040517fc1ab6dc100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603611403576040517fc1ab6dc100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61142473ffffffffffffffffffffffffffffffffffffffff84168383611cbf565b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f0de377c3f3362540c047602ad996510f81ec5d84f9ad151c7b6ebecce8ea6b7b8360405161148391815260200190565b60405180910390a3505050565b611498611be9565b60075460ff166114d4576040517fd6d0696c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b335f908152600960209081526040808320600b909252822080548254929391926114fe919061214d565b9050805f03611539576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b82545f901561158f575f61154b611263565b9050670de0b6b3a764000085600101548261156691906120e5565b865461157291906120fe565b61157c9190612115565b856002015461158b919061214d565b9150505b611597611d45565b8354600880545f906115aa9084906120e5565b909155505080156115cc578060065f8282546115c6919061214d565b90915550505b5f808555600285018190556005546001808701919091559084558301805467ffffffffffffffff1916905561163873ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000163384611cbf565b604051828152339081907ff24ef89f38eadc1bde50701ad6e4d6d11a2dc24f7cf834a486991f38833285049060200160405180910390a35050505061093e6001600255565b611685611be9565b335f908152600b60205260408120805490918190036116d0576040517f9121b84f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600182015467ffffffffffffffff1642811115611725576040517fb74c3b6100000000000000000000000000000000000000000000000000000000815267ffffffffffffffff821660048201526024016107e3565b5f835560018301805467ffffffffffffffff1916905561177c73ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000163384611cbf565b60405182815233907f1a39b9c5044b9f0ff56c5951e30c1ebe24911353aafcceb9250e83a24fe158c49060200160405180910390a250505061093e6001600255565b6117c6611be9565b6117cf33611c2a565b335f908152600960205260408120600201549081900361181b576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b335f9081526009602052604081206002015561186e7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff168383611cbf565b60405181815273ffffffffffffffffffffffffffffffffffffffff83169033907f540798df468d7b23d11f156fdb954cb19ad414d150722a7b6d55ba369dea792e9060200160405180910390a350610d356001600255565b6118ce611b97565b600e80549082905560408051828152602081018490527fca0542093af2ac14ccf6e52b6e1a131c7e2825fb3b51139bf1dd8186a1339e959101610844565b5f6003545f0361193b57507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90565b60035460065461194b9190612115565b905090565b611958611b97565b6001805473ffffffffffffffffffffffffffffffffffffffff83167fffffffffffffffffffffffff000000000000000000000000000000000000000090911681179091556119ba5f5473ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b611a07611b97565b7f0000000000000000000000000000000000000000000000000000000000000000811015611a8a576040517fb6ae74de000000000000000000000000000000000000000000000000000000008152600481018290527f000000000000000000000000000000000000000000000000000000000000000060248201526044016107e3565b7f0000000000000000000000000000000000000000000000000000000000000000811115611b0d576040517f8d4c5fe8000000000000000000000000000000000000000000000000000000008152600481018290527f000000000000000000000000000000000000000000000000000000000000000060248201526044016107e3565b5f611b387f000000000000000000000000000000000000000000000000000000000000000042612177565b600c839055600d805467ffffffffffffffff191667ffffffffffffffff83169081179091556040805185815260208101929092529192507f229dcef65c024a3de1eb55851f6e82a462593afa8ce9809cadda813adbd2b55d9101610844565b5f5473ffffffffffffffffffffffffffffffffffffffff16331461093e576040517f118cdaa70000000000000000000000000000000000000000000000000000000081523360048201526024016107e3565b6002805403611c24576040517f3ee5aeb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60028055565b611c32611d45565b73ffffffffffffffffffffffffffffffffffffffff81165f908152600960205260409020805415611cb3575f8160010154600554611c7091906120e5565b90508015611cb1578154670de0b6b3a764000090611c8f9083906120fe565b611c999190612115565b826002015f828254611cab919061214d565b90915550505b505b60055460019091015550565b60405173ffffffffffffffffffffffffffffffffffffffff838116602483015260448201839052611d4091859182169063a9059cbb906064015b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050611e99565b505050565b600454429067ffffffffffffffff16808203611d5f575050565b60085415611df7575f611d7282846120e5565b90505f60035482611d8391906120fe565b9050600654811115611d9457506006545b8015611df4576008545f90611db183670de0b6b3a76400006120fe565b611dbb9190612115565b90508015611df2578060055f828254611dd4919061214d565b925050819055508160065f828254611dec91906120e5565b90915550505b505b50505b506004805467ffffffffffffffff191667ffffffffffffffff92909216919091179055565b60405173ffffffffffffffffffffffffffffffffffffffff8481166024830152838116604483015260648201839052611e629186918216906323b872dd90608401611cf9565b50505050565b600180547fffffffffffffffffffffffff0000000000000000000000000000000000000000169055610d3581611f38565b5f8060205f8451602086015f885af180611eb8576040513d5f823e3d81fd5b50505f513d91508115611ecf578060011415611ee9565b73ffffffffffffffffffffffffffffffffffffffff84163b155b15611e62576040517f5274afe700000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff851660048201526024016107e3565b5f805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b73ffffffffffffffffffffffffffffffffffffffff81168114610d35575f80fd5b5f60208284031215611fdd575f80fd5b8135611fe881611fac565b9392505050565b5f60208284031215611fff575f80fd5b813567ffffffffffffffff81168114611fe8575f80fd5b5f8060408385031215612027575f80fd5b82359150602083013561203981611fac565b809150509250929050565b5f60208284031215612054575f80fd5b81358015158114611fe8575f80fd5b5f60208284031215612073575f80fd5b5035919050565b5f805f6060848603121561208c575f80fd5b833561209781611fac565b925060208401356120a781611fac565b929592945050506040919091013590565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b818103818111156120f8576120f86120b8565b92915050565b80820281158282048414176120f8576120f86120b8565b5f82612148577f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b500490565b808201808211156120f8576120f86120b8565b5f60208284031215612170575f80fd5b5051919050565b67ffffffffffffffff818116838216019080821115612198576121986120b8565b509291505056fea26469706673582212202388fe0eebfb866825d3b97c92b3e93b0768b305945931b0eba2505bd10a5c8c64736f6c6343000814003300000000000000000000000007c3e739c65f81ea79d19a88d27de4c9f15f8df000000000000000000000000007c3e739c65f81ea79d19a88d27de4c9f15f8df00000000000000000000000000000000000000000000000000013bd270e5f4000000000000000000000000000936c6ef8dadcac59eedacc3a51ede70c80f32e2c0000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000546000000000000000000000000000000000000000000000000000000000001275000000000000000000000000000000000000000000000000000de0b6b3a7640000

Deployed Bytecode

0x608060405234801561000f575f80fd5b50600436106102e2575f3560e01c80638da5cb5b11610187578063e03ff7cb116100dd578063f128632211610093578063f3f437031161006e578063f3f4370314610663578063fe7e6566146106b1578063ff142558146106d8575f80fd5b8063f12863221461063f578063f188768414610647578063f2fde38b14610650575f80fd5b8063e52ae7b3116100c3578063e52ae7b314610610578063eb4af04514610623578063f0de822814610636575f80fd5b8063e03ff7cb146105ea578063e30c3978146105f2575f80fd5b8063a87430ba1161013d578063cea9d26f11610118578063cea9d26f146105c6578063db2e21bc146105d9578063df136d65146105e1575f80fd5b8063a87430ba14610561578063c8f33c91146105aa578063cd3daf9d146105be575f80fd5b806399248ea71161016d57806399248ea7146105145780639ee679e81461053b578063a694fc3a1461054e575f80fd5b80638da5cb5b146104e457806393707a1914610501575f80fd5b80636b12686b1161023c5780637b0a47ee116101f257806380faa57d116101cd57806380faa57d146104c1578063817b1cd2146104c757806387a55692146104d0575f80fd5b80637b0a47ee1461049d5780637eb68f0f146104a65780637f73dffd146104b9575f80fd5b8063715018a611610222578063715018a614610466578063718dc3c31461046e57806379ba509714610495575f80fd5b80636b12686b1461040a57806370a0823114610431575f80fd5b80631fc3277d1161029c5780633d18b912116102775780633d18b912146103e7578063450d6a90146103ef57806351746bb2146103f7575f80fd5b80631fc3277d146103b757806322611280146103d657806333d4f626146103de575f80fd5b80630d36a9e9116102cc5780630d36a9e9146103395780630df25f1d146103565780631c39b6721461036b575f80fd5b80628cc262146102e65780630288a39c1461030c575b5f80fd5b6102f96102f4366004611fcd565b6106eb565b6040519081526020015b60405180910390f35b600a546103209067ffffffffffffffff1681565b60405167ffffffffffffffff9091168152602001610303565b6007546103469060ff1681565b6040519015158152602001610303565b610369610364366004611fef565b610783565b005b6103927f00000000000000000000000007c3e739c65f81ea79d19a88d27de4c9f15f8df081565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610303565b6103c162278d0081565b60405163ffffffff9091168152602001610303565b610369610850565b6102f9600c5481565b610369610940565b610369610a33565b610369610405366004612016565b610b2e565b6102f97f000000000000000000000000000000000000000000000000000000000000000081565b6102f961043f366004611fcd565b73ffffffffffffffffffffffffffffffffffffffff165f9081526009602052604090205490565b610369610cb0565b6102f97f0000000000000000000000000000000000000000000000000de0b6b3a764000081565b610369610cc1565b6102f960035481565b6103696104b4366004612044565b610d38565b610369610da6565b426102f9565b6102f960085481565b600d546103209067ffffffffffffffff1681565b5f5473ffffffffffffffffffffffffffffffffffffffff16610392565b61036961050f366004612063565b610e30565b6103927f00000000000000000000000007c3e739c65f81ea79d19a88d27de4c9f15f8df081565b610369610549366004612063565b6110c6565b61036961055c366004612063565b611259565b61058f61056f366004611fcd565b60096020525f908152604090208054600182015460029092015490919083565b60408051938452602084019290925290820152606001610303565b6004546103209067ffffffffffffffff1681565b6102f9611263565b6103696105d436600461207a565b6112f1565b610369611490565b6102f960055481565b61036961167d565b60015473ffffffffffffffffffffffffffffffffffffffff16610392565b61036961061e366004611fcd565b6117be565b610369610631366004612063565b6118c6565b6102f960065481565b6102f961190c565b6102f9600e5481565b61036961065e366004611fcd565b611950565b610693610671366004611fcd565b600b6020525f90815260409020805460019091015467ffffffffffffffff1682565b6040805192835267ffffffffffffffff909116602083015201610303565b6103207f000000000000000000000000000000000000000000000000000000000000546081565b6103696106e6366004612063565b6119ff565b73ffffffffffffffffffffffffffffffffffffffff81165f90815260096020908152604080832081516060810183528154815260018201549381019390935260020154908201528161073b611263565b9050670de0b6b3a764000082602001518261075691906120e5565b835161076291906120fe565b61076c9190612115565b826040015161077b919061214d565b949350505050565b61078b611b97565b62278d0067ffffffffffffffff821611156107ec576040517f3cf5e7e400000000000000000000000000000000000000000000000000000000815267ffffffffffffffff8216600482015262278d0060248201526044015b60405180910390fd5b600a805467ffffffffffffffff83811667ffffffffffffffff1983168117909355604080519190921680825260208201939093527f7bf377c6f26b11af5ca3523bbafe65b3cdd72a74e9a83c70b1a3c42d1014937891015b60405180910390a15050565b610858611be9565b335f908152600b60205260408120805490918190036108a3576040517f9121b84f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6108ac33611c2a565b335f90815260096020526040812080548392906108ca90849061214d565b925050819055508060085f8282546108e2919061214d565b90915550505f825560018201805467ffffffffffffffff1916905560405181815233907fc04d7db2ae23be2ba46893b43d7c4af2fc045905461f3df891e1d6c4d0683ef69060200160405180910390a2505061093e6001600255565b565b610948611be9565b61095133611c2a565b335f908152600960205260408120600201549081900361099d576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b335f818152600960205260408120600201556109f1907f00000000000000000000000007c3e739c65f81ea79d19a88d27de4c9f15f8df073ffffffffffffffffffffffffffffffffffffffff169083611cbf565b604051818152339081907f540798df468d7b23d11f156fdb954cb19ad414d150722a7b6d55ba369dea792e9060200160405180910390a35061093e6001600255565b600d5467ffffffffffffffff165f819003610a7a576040517fbae04d1500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8067ffffffffffffffff16421015610aca576040517f9d58910b00000000000000000000000000000000000000000000000000000000815267ffffffffffffffff821660048201526024016107e3565b610ad2611d45565b600354600c80545f909155600d805467ffffffffffffffff1916905560408051838152602081018390527fc390a98ace15a7bb6bab611eedfdbb2685043b241a869420043cdfb23ccfee50910160405180910390a16003555050565b610b36611be9565b815f03610b6f576040517fcbca5aa200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600e54821015610bb957600e546040517f1cc6243f0000000000000000000000000000000000000000000000000000000081526107e3918491600401918252602082015260400190565b610bc281611c2a565b610c0473ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000007c3e739c65f81ea79d19a88d27de4c9f15f8df016333085611e1c565b73ffffffffffffffffffffffffffffffffffffffff81165f9081526009602052604081208054849290610c3890849061214d565b925050819055508160085f828254610c50919061214d565b909155505060405182815273ffffffffffffffffffffffffffffffffffffffff82169033907f5dac0c1b1112564a045ba943c9d50270893e8e826c49be8e7073adc713ab7bd79060200160405180910390a3610cac6001600255565b5050565b610cb8611b97565b61093e5f611e68565b600154339073ffffffffffffffffffffffffffffffffffffffff168114610d2c576040517f118cdaa700000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff821660048201526024016107e3565b610d3581611e68565b50565b610d40611b97565b600780547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00168215159081179091556040519081527fab5c2018f7e4bc1b76a4335b68c107fe55566a39f84abc9f3e6b0e6f23fe96cd906020015b60405180910390a150565b610dae611b97565b600c545f819003610deb576040517fbae04d1500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f600c55600d805467ffffffffffffffff191690556040518181527f0685795c7ec9e1e92ef2aaf7d85bdfbe11dd3d3ab29c2caae623beeaa91cba5490602001610d9b565b610e38611b97565b610e40611be9565b805f03610e79576040517fcbca5aa200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f907f00000000000000000000000007c3e739c65f81ea79d19a88d27de4c9f15f8df073ffffffffffffffffffffffffffffffffffffffff16906370a0823190602401602060405180830381865afa158015610f03573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f279190612160565b9050610f6b73ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000007c3e739c65f81ea79d19a88d27de4c9f15f8df016333085611e1c565b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f90829073ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000007c3e739c65f81ea79d19a88d27de4c9f15f8df016906370a0823190602401602060405180830381865afa158015610ff7573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061101b9190612160565b61102591906120e5565b9050805f03611060576040517fcbca5aa200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060065f828254611071919061214d565b909155505060065460405133917f17c4c73fe3b5d172ec864d377fb9e2323d3f90de65eed172cc20232b48ab2ae1916110b291858252602082015260400190565b60405180910390a25050610d356001600255565b6110ce611be9565b805f03611107576040517fcbca5aa200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61111033611c2a565b335f9081526009602052604090208054821115611159576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b335f908152600b602052604090208054156111a0576040517f75c4147300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b82825f015f8282546111b291906120e5565b925050819055508260085f8282546111ca91906120e5565b9091555050600a545f906111e89067ffffffffffffffff1642612177565b84835560018301805467ffffffffffffffff191667ffffffffffffffff831690811790915560408051878152602081019290925291925033917fbf6730b5b5d9fd45cdb42f69910317da629fd6e12b3ab41aa1708cd916385dfc910160405180910390a2505050610d356001600255565b610d358133610b2e565b5f6008545f03611274575060055490565b6004545f9061128d9067ffffffffffffffff16426120e5565b9050805f0361129e57505060055490565b5f600354826112ad91906120fe565b90506006548111156112be57506006545b6008546112d382670de0b6b3a76400006120fe565b6112dd9190612115565b6005546112ea919061214d565b9250505090565b6112f9611b97565b7f00000000000000000000000007c3e739c65f81ea79d19a88d27de4c9f15f8df073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160361137e576040517fc1ab6dc100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f00000000000000000000000007c3e739c65f81ea79d19a88d27de4c9f15f8df073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603611403576040517fc1ab6dc100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61142473ffffffffffffffffffffffffffffffffffffffff84168383611cbf565b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f0de377c3f3362540c047602ad996510f81ec5d84f9ad151c7b6ebecce8ea6b7b8360405161148391815260200190565b60405180910390a3505050565b611498611be9565b60075460ff166114d4576040517fd6d0696c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b335f908152600960209081526040808320600b909252822080548254929391926114fe919061214d565b9050805f03611539576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b82545f901561158f575f61154b611263565b9050670de0b6b3a764000085600101548261156691906120e5565b865461157291906120fe565b61157c9190612115565b856002015461158b919061214d565b9150505b611597611d45565b8354600880545f906115aa9084906120e5565b909155505080156115cc578060065f8282546115c6919061214d565b90915550505b5f808555600285018190556005546001808701919091559084558301805467ffffffffffffffff1916905561163873ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000007c3e739c65f81ea79d19a88d27de4c9f15f8df0163384611cbf565b604051828152339081907ff24ef89f38eadc1bde50701ad6e4d6d11a2dc24f7cf834a486991f38833285049060200160405180910390a35050505061093e6001600255565b611685611be9565b335f908152600b60205260408120805490918190036116d0576040517f9121b84f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600182015467ffffffffffffffff1642811115611725576040517fb74c3b6100000000000000000000000000000000000000000000000000000000815267ffffffffffffffff821660048201526024016107e3565b5f835560018301805467ffffffffffffffff1916905561177c73ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000007c3e739c65f81ea79d19a88d27de4c9f15f8df0163384611cbf565b60405182815233907f1a39b9c5044b9f0ff56c5951e30c1ebe24911353aafcceb9250e83a24fe158c49060200160405180910390a250505061093e6001600255565b6117c6611be9565b6117cf33611c2a565b335f908152600960205260408120600201549081900361181b576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b335f9081526009602052604081206002015561186e7f00000000000000000000000007c3e739c65f81ea79d19a88d27de4c9f15f8df073ffffffffffffffffffffffffffffffffffffffff168383611cbf565b60405181815273ffffffffffffffffffffffffffffffffffffffff83169033907f540798df468d7b23d11f156fdb954cb19ad414d150722a7b6d55ba369dea792e9060200160405180910390a350610d356001600255565b6118ce611b97565b600e80549082905560408051828152602081018490527fca0542093af2ac14ccf6e52b6e1a131c7e2825fb3b51139bf1dd8186a1339e959101610844565b5f6003545f0361193b57507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90565b60035460065461194b9190612115565b905090565b611958611b97565b6001805473ffffffffffffffffffffffffffffffffffffffff83167fffffffffffffffffffffffff000000000000000000000000000000000000000090911681179091556119ba5f5473ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b611a07611b97565b7f0000000000000000000000000000000000000000000000000000000000000000811015611a8a576040517fb6ae74de000000000000000000000000000000000000000000000000000000008152600481018290527f000000000000000000000000000000000000000000000000000000000000000060248201526044016107e3565b7f0000000000000000000000000000000000000000000000000de0b6b3a7640000811115611b0d576040517f8d4c5fe8000000000000000000000000000000000000000000000000000000008152600481018290527f0000000000000000000000000000000000000000000000000de0b6b3a764000060248201526044016107e3565b5f611b387f000000000000000000000000000000000000000000000000000000000000546042612177565b600c839055600d805467ffffffffffffffff191667ffffffffffffffff83169081179091556040805185815260208101929092529192507f229dcef65c024a3de1eb55851f6e82a462593afa8ce9809cadda813adbd2b55d9101610844565b5f5473ffffffffffffffffffffffffffffffffffffffff16331461093e576040517f118cdaa70000000000000000000000000000000000000000000000000000000081523360048201526024016107e3565b6002805403611c24576040517f3ee5aeb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60028055565b611c32611d45565b73ffffffffffffffffffffffffffffffffffffffff81165f908152600960205260409020805415611cb3575f8160010154600554611c7091906120e5565b90508015611cb1578154670de0b6b3a764000090611c8f9083906120fe565b611c999190612115565b826002015f828254611cab919061214d565b90915550505b505b60055460019091015550565b60405173ffffffffffffffffffffffffffffffffffffffff838116602483015260448201839052611d4091859182169063a9059cbb906064015b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050611e99565b505050565b600454429067ffffffffffffffff16808203611d5f575050565b60085415611df7575f611d7282846120e5565b90505f60035482611d8391906120fe565b9050600654811115611d9457506006545b8015611df4576008545f90611db183670de0b6b3a76400006120fe565b611dbb9190612115565b90508015611df2578060055f828254611dd4919061214d565b925050819055508160065f828254611dec91906120e5565b90915550505b505b50505b506004805467ffffffffffffffff191667ffffffffffffffff92909216919091179055565b60405173ffffffffffffffffffffffffffffffffffffffff8481166024830152838116604483015260648201839052611e629186918216906323b872dd90608401611cf9565b50505050565b600180547fffffffffffffffffffffffff0000000000000000000000000000000000000000169055610d3581611f38565b5f8060205f8451602086015f885af180611eb8576040513d5f823e3d81fd5b50505f513d91508115611ecf578060011415611ee9565b73ffffffffffffffffffffffffffffffffffffffff84163b155b15611e62576040517f5274afe700000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff851660048201526024016107e3565b5f805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b73ffffffffffffffffffffffffffffffffffffffff81168114610d35575f80fd5b5f60208284031215611fdd575f80fd5b8135611fe881611fac565b9392505050565b5f60208284031215611fff575f80fd5b813567ffffffffffffffff81168114611fe8575f80fd5b5f8060408385031215612027575f80fd5b82359150602083013561203981611fac565b809150509250929050565b5f60208284031215612054575f80fd5b81358015158114611fe8575f80fd5b5f60208284031215612073575f80fd5b5035919050565b5f805f6060848603121561208c575f80fd5b833561209781611fac565b925060208401356120a781611fac565b929592945050506040919091013590565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b818103818111156120f8576120f86120b8565b92915050565b80820281158282048414176120f8576120f86120b8565b5f82612148577f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b500490565b808201808211156120f8576120f86120b8565b5f60208284031215612170575f80fd5b5051919050565b67ffffffffffffffff818116838216019080821115612198576121986120b8565b509291505056fea26469706673582212202388fe0eebfb866825d3b97c92b3e93b0768b305945931b0eba2505bd10a5c8c64736f6c63430008140033

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

00000000000000000000000007c3e739c65f81ea79d19a88d27de4c9f15f8df000000000000000000000000007c3e739c65f81ea79d19a88d27de4c9f15f8df00000000000000000000000000000000000000000000000000013bd270e5f4000000000000000000000000000936c6ef8dadcac59eedacc3a51ede70c80f32e2c0000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000546000000000000000000000000000000000000000000000000000000000001275000000000000000000000000000000000000000000000000000de0b6b3a7640000

-----Decoded View---------------
Arg [0] : _stakeToken (address): 0x07C3E739C65f81Ea79d19A88d27de4C9f15f8Df0
Arg [1] : _rewardToken (address): 0x07C3E739C65f81Ea79d19A88d27de4C9f15f8Df0
Arg [2] : _initialRewardRate (uint256): 5556000000000000
Arg [3] : initialOwner (address): 0x936c6ef8DadcaC59EEDACC3A51ede70C80F32e2C
Arg [4] : _maxRewardRate (uint256): 1000000000000000000
Arg [5] : _minRewardRate (uint256): 0
Arg [6] : _rateChangeDelay (uint64): 21600
Arg [7] : _initialWithdrawDelay (uint64): 1209600
Arg [8] : _minStakeAmount (uint256): 1000000000000000000

-----Encoded View---------------
9 Constructor Arguments found :
Arg [0] : 00000000000000000000000007c3e739c65f81ea79d19a88d27de4c9f15f8df0
Arg [1] : 00000000000000000000000007c3e739c65f81ea79d19a88d27de4c9f15f8df0
Arg [2] : 0000000000000000000000000000000000000000000000000013bd270e5f4000
Arg [3] : 000000000000000000000000936c6ef8dadcac59eedacc3a51ede70c80f32e2c
Arg [4] : 0000000000000000000000000000000000000000000000000de0b6b3a7640000
Arg [5] : 0000000000000000000000000000000000000000000000000000000000000000
Arg [6] : 0000000000000000000000000000000000000000000000000000000000005460
Arg [7] : 0000000000000000000000000000000000000000000000000000000000127500
Arg [8] : 0000000000000000000000000000000000000000000000000de0b6b3a7640000


Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ 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.