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

Transaction Decoder

Block:
21946433 at Feb-28-2025 05:47:35 PM +UTC
Transaction Fee:
0.000132079328738952 ETH $0.39
Gas Used:
83,748 Gas / 1.577104274 Gwei

Emitted Events:

Account State Difference:

  Address   Before After State Difference Code
0x377E168a...77EfaAc3E
(PROOF: Staking)
0x765EE437...82dF31fE5
0.070007285126107 Eth
Nonce: 798
0.069875205797368048 Eth
Nonce: 799
0.000132079328738952
(beaverbuild)
7.845075358490702319 Eth7.845117232490702319 Eth0.000041874
0x9B4a69dE...e5202944E

Execution Trace

ProofStaking.CALL( )
  • ProofToken.transfer( _recipient=0x765EE4370E2FFdfeAe6893c8658863e82dF31fE5, _amount=50000000000000 ) => ( True )
    File 1 of 2: ProofStaking
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)
    pragma solidity ^0.8.0;
    import "../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.
     *
     * By default, the owner account will be the one that deploys the contract. 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;
        event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
        /**
         * @dev Initializes the contract setting the deployer as the initial owner.
         */
        constructor() {
            _transferOwnership(_msgSender());
        }
        /**
         * @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 {
            require(owner() == _msgSender(), "Ownable: caller is not the owner");
        }
        /**
         * @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 {
            require(newOwner != address(0), "Ownable: new owner is the zero address");
            _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);
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol)
    pragma solidity ^0.8.0;
    /**
     * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
     * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
     *
     * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
     * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
     * need to send a transaction, and thus is not required to hold Ether at all.
     */
    interface IERC20Permit {
        /**
         * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
         * given ``owner``'s signed approval.
         *
         * IMPORTANT: The same issues {IERC20-approve} has related to transaction
         * ordering also apply here.
         *
         * Emits an {Approval} event.
         *
         * Requirements:
         *
         * - `spender` cannot be the zero address.
         * - `deadline` must be a timestamp in the future.
         * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
         * over the EIP712-formatted function arguments.
         * - the signature must use ``owner``'s current nonce (see {nonces}).
         *
         * For more information on the signature format, see the
         * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
         * section].
         */
        function permit(
            address owner,
            address spender,
            uint256 value,
            uint256 deadline,
            uint8 v,
            bytes32 r,
            bytes32 s
        ) external;
        /**
         * @dev Returns the current nonce for `owner`. This value must be
         * included whenever a signature is generated for {permit}.
         *
         * Every successful call to {permit} increases ``owner``'s nonce by one. This
         * prevents a signature from being used multiple times.
         */
        function nonces(address owner) external view returns (uint256);
        /**
         * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
         */
        // solhint-disable-next-line func-name-mixedcase
        function DOMAIN_SEPARATOR() external view returns (bytes32);
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
    pragma solidity ^0.8.0;
    /**
     * @dev Interface of the ERC20 standard as defined in the EIP.
     */
    interface IERC20 {
        /**
         * @dev Emitted when `value` tokens are moved from one account (`from`) to
         * another (`to`).
         *
         * Note that `value` may be zero.
         */
        event Transfer(address indexed from, address indexed to, uint256 value);
        /**
         * @dev Emitted when the allowance of a `spender` for an `owner` is set by
         * a call to {approve}. `value` is the new allowance.
         */
        event Approval(address indexed owner, address indexed spender, uint256 value);
        /**
         * @dev Returns the amount of tokens in existence.
         */
        function totalSupply() external view returns (uint256);
        /**
         * @dev Returns the amount of tokens owned by `account`.
         */
        function balanceOf(address account) external view returns (uint256);
        /**
         * @dev Moves `amount` 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 amount) 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 `amount` 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 amount) external returns (bool);
        /**
         * @dev Moves `amount` tokens from `from` to `to` using the
         * allowance mechanism. `amount` 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 amount) external returns (bool);
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)
    pragma solidity ^0.8.0;
    import "../IERC20.sol";
    import "../extensions/IERC20Permit.sol";
    import "../../../utils/Address.sol";
    /**
     * @title SafeERC20
     * @dev Wrappers around ERC20 operations that throw on failure (when the token
     * contract returns false). Tokens that return no value (and instead revert or
     * throw on failure) are also supported, non-reverting calls are assumed to be
     * successful.
     * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
     * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
     */
    library SafeERC20 {
        using Address for address;
        /**
         * @dev 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.encodeWithSelector(token.transfer.selector, 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.encodeWithSelector(token.transferFrom.selector, from, to, value));
        }
        /**
         * @dev Deprecated. This function has issues similar to the ones found in
         * {IERC20-approve}, and its usage is discouraged.
         *
         * Whenever possible, use {safeIncreaseAllowance} and
         * {safeDecreaseAllowance} instead.
         */
        function safeApprove(IERC20 token, address spender, uint256 value) internal {
            // safeApprove should only be called when setting an initial allowance,
            // or when resetting it to zero. To increase and decrease it, use
            // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
            require(
                (value == 0) || (token.allowance(address(this), spender) == 0),
                "SafeERC20: approve from non-zero to non-zero allowance"
            );
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
        }
        /**
         * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
         * non-reverting calls are assumed to be successful.
         */
        function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
            uint256 oldAllowance = token.allowance(address(this), spender);
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
        }
        /**
         * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
         * non-reverting calls are assumed to be successful.
         */
        function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
            unchecked {
                uint256 oldAllowance = token.allowance(address(this), spender);
                require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
                _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
            }
        }
        /**
         * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
         * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
         * to be set to zero before setting it to a non-zero value, such as USDT.
         */
        function forceApprove(IERC20 token, address spender, uint256 value) internal {
            bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);
            if (!_callOptionalReturnBool(token, approvalCall)) {
                _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
                _callOptionalReturn(token, approvalCall);
            }
        }
        /**
         * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
         * Revert on invalid signature.
         */
        function safePermit(
            IERC20Permit token,
            address owner,
            address spender,
            uint256 value,
            uint256 deadline,
            uint8 v,
            bytes32 r,
            bytes32 s
        ) internal {
            uint256 nonceBefore = token.nonces(owner);
            token.permit(owner, spender, value, deadline, v, r, s);
            uint256 nonceAfter = token.nonces(owner);
            require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
        }
        /**
         * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
         * on the return value: the return value is optional (but if data is returned, it must not be false).
         * @param token The token targeted by the call.
         * @param data The call data (encoded using abi.encode or one of its variants).
         */
        function _callOptionalReturn(IERC20 token, bytes memory data) private {
            // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
            // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
            // the target address contains contract code and also asserts for success in the low-level call.
            bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
            require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
        }
        /**
         * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
         * on the return value: the return value is optional (but if data is returned, it must not be false).
         * @param token The token targeted by the call.
         * @param data The call data (encoded using abi.encode or one of its variants).
         *
         * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
         */
        function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
            // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
            // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
            // and not revert is the subcall reverts.
            (bool success, bytes memory returndata) = address(token).call(data);
            return
                success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token));
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
    pragma solidity ^0.8.1;
    /**
     * @dev Collection of functions related to the address type
     */
    library Address {
        /**
         * @dev Returns true if `account` is a contract.
         *
         * [IMPORTANT]
         * ====
         * It is unsafe to assume that an address for which this function returns
         * false is an externally-owned account (EOA) and not a contract.
         *
         * Among others, `isContract` will return false for the following
         * types of addresses:
         *
         *  - an externally-owned account
         *  - a contract in construction
         *  - an address where a contract will be created
         *  - an address where a contract lived, but was destroyed
         *
         * Furthermore, `isContract` will also return true if the target contract within
         * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
         * which only has an effect at the end of a transaction.
         * ====
         *
         * [IMPORTANT]
         * ====
         * You shouldn't rely on `isContract` to protect against flash loan attacks!
         *
         * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
         * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
         * constructor.
         * ====
         */
        function isContract(address account) internal view returns (bool) {
            // This method relies on extcodesize/address.code.length, which returns 0
            // for contracts in construction, since the code is only stored at the end
            // of the constructor execution.
            return account.code.length > 0;
        }
        /**
         * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
         * `recipient`, forwarding all available gas and reverting on errors.
         *
         * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
         * of certain opcodes, possibly making contracts go over the 2300 gas limit
         * imposed by `transfer`, making them unable to receive funds via
         * `transfer`. {sendValue} removes this limitation.
         *
         * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
         *
         * IMPORTANT: because control is transferred to `recipient`, care must be
         * taken to not create reentrancy vulnerabilities. Consider using
         * {ReentrancyGuard} or the
         * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
         */
        function sendValue(address payable recipient, uint256 amount) internal {
            require(address(this).balance >= amount, "Address: insufficient balance");
            (bool success, ) = recipient.call{value: amount}("");
            require(success, "Address: unable to send value, recipient may have reverted");
        }
        /**
         * @dev Performs a Solidity function call using a low level `call`. A
         * plain `call` is an unsafe replacement for a function call: use this
         * function instead.
         *
         * If `target` reverts with a revert reason, it is bubbled up by this
         * function (like regular Solidity function calls).
         *
         * Returns the raw returned data. To convert to the expected return value,
         * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
         *
         * Requirements:
         *
         * - `target` must be a contract.
         * - calling `target` with `data` must not revert.
         *
         * _Available since v3.1._
         */
        function functionCall(address target, bytes memory data) internal returns (bytes memory) {
            return functionCallWithValue(target, data, 0, "Address: low-level call failed");
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
         * `errorMessage` as a fallback revert reason when `target` reverts.
         *
         * _Available since v3.1._
         */
        function functionCall(
            address target,
            bytes memory data,
            string memory errorMessage
        ) internal returns (bytes memory) {
            return functionCallWithValue(target, data, 0, errorMessage);
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but also transferring `value` wei to `target`.
         *
         * Requirements:
         *
         * - the calling contract must have an ETH balance of at least `value`.
         * - the called Solidity function must be `payable`.
         *
         * _Available since v3.1._
         */
        function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
            return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
        }
        /**
         * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
         * with `errorMessage` as a fallback revert reason when `target` reverts.
         *
         * _Available since v3.1._
         */
        function functionCallWithValue(
            address target,
            bytes memory data,
            uint256 value,
            string memory errorMessage
        ) internal returns (bytes memory) {
            require(address(this).balance >= value, "Address: insufficient balance for call");
            (bool success, bytes memory returndata) = target.call{value: value}(data);
            return verifyCallResultFromTarget(target, success, returndata, errorMessage);
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but performing a static call.
         *
         * _Available since v3.3._
         */
        function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
            return functionStaticCall(target, data, "Address: low-level static call failed");
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
         * but performing a static call.
         *
         * _Available since v3.3._
         */
        function functionStaticCall(
            address target,
            bytes memory data,
            string memory errorMessage
        ) internal view returns (bytes memory) {
            (bool success, bytes memory returndata) = target.staticcall(data);
            return verifyCallResultFromTarget(target, success, returndata, errorMessage);
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but performing a delegate call.
         *
         * _Available since v3.4._
         */
        function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
            return functionDelegateCall(target, data, "Address: low-level delegate call failed");
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
         * but performing a delegate call.
         *
         * _Available since v3.4._
         */
        function functionDelegateCall(
            address target,
            bytes memory data,
            string memory errorMessage
        ) internal returns (bytes memory) {
            (bool success, bytes memory returndata) = target.delegatecall(data);
            return verifyCallResultFromTarget(target, success, returndata, errorMessage);
        }
        /**
         * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
         * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
         *
         * _Available since v4.8._
         */
        function verifyCallResultFromTarget(
            address target,
            bool success,
            bytes memory returndata,
            string memory errorMessage
        ) internal view returns (bytes memory) {
            if (success) {
                if (returndata.length == 0) {
                    // only check isContract if the call was successful and the return data is empty
                    // otherwise we already know that it was a contract
                    require(isContract(target), "Address: call to non-contract");
                }
                return returndata;
            } else {
                _revert(returndata, errorMessage);
            }
        }
        /**
         * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
         * revert reason or using the provided one.
         *
         * _Available since v4.3._
         */
        function verifyCallResult(
            bool success,
            bytes memory returndata,
            string memory errorMessage
        ) internal pure returns (bytes memory) {
            if (success) {
                return returndata;
            } else {
                _revert(returndata, errorMessage);
            }
        }
        function _revert(bytes memory returndata, string memory errorMessage) private pure {
            // Look for revert reason and bubble it up if present
            if (returndata.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly
                /// @solidity memory-safe-assembly
                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
    pragma solidity ^0.8.0;
    /**
     * @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;
        }
    }
    // SPDX-License-Identifier: None
    pragma solidity ^0.8.19;
    interface IProofStaking {
        struct UserInfo {
            uint256 stakedAmount;
            uint256 rewardDebt;
            uint256 claimedAmount;
            uint256 claimableRewards;
            uint256 lastUnstakeTime;
            uint256 pendingUnstakeAmount;
            bool existingStaker;
        }
        /// @notice Set unstaking duration.
        /// @dev Only owner can call this function.
        function setUnstakingDuration(uint256 _duration) external;
        /// @notice Deposit accumulated ETH to staking pool again.
        /// @dev Only owner can call this function.
        function refreshPool() external;
        /// @notice Stake amount of $PROOF token.
        function stake(uint256 _amount) external;
        /// @notice Unstake amount of $PROOF token.
        /// @dev unstake amount should be less than staked amount.
        function unstake(uint256 _amount) external;
        /// @notice Restake unstaked token again.
        /// @dev All of the token that before unstake duration will be restaked.
        function restakeTokens() external;
        /// @notice Claimed unstaked token after unstaking period.
        function claimUnstakedToken() external;
        /// @notice Claim rewards.
        /// @dev Rewards are distributed as ETH.
        function claimRewards() external;
        /// @notice Get claimable rewards of the staker.
        function getClaimableRewards(
            address _staker
        ) external view returns (uint256);
        /// @notice Get all stakers address.
        function getStakers() external view returns (address[] memory);
        /// @notice Get claimable unstaked token.
        function getClaimableUnstakedToken(
            address _staker
        ) external view returns (uint256);
        /// @notice Get pending unstake amount by user.
        function getPendingUnstakeAmount(
            address _staker
        ) external view returns (uint256);
        function getTotalDepositAmount(address user) external view returns (uint256);
        function getDepositors() external view returns (address[] memory);
    }
    // SPDX-License-Identifier: None
    pragma solidity ^0.8.19;
    import "@openzeppelin/contracts/access/Ownable.sol";
    import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
    import "./interfaces/IProofStaking.sol";
    contract ProofStaking is Ownable, IProofStaking {
        using SafeERC20 for IERC20;
        
        mapping(address => UserInfo) public userInfo;
        mapping(address => uint256) public rewardDebt;
        mapping(address => uint256) public totalRewardDeposits;
        uint256 public totalStakedAmount;
        uint256 public unstakeDuration = 2 days;
        uint256 public unallocatedETH;
        uint256 public accRewardsPerShare;
        uint256 public ethDebt;
        uint256 public globalETHCollected;
        address[] userList;
        address[] depositorList;
        IERC20 public immutable proofToken;
        
        event Stake(address indexed user, uint256 amount);
        event Unstake(address indexed user, uint256 amount);
        event Withdraw(address indexed user, uint256 amount);
        event Claim(address indexed user, uint256 amount);
        constructor(address _proofToken) {
            proofToken = IERC20(_proofToken);
        }
        function stake(uint256 _amount) external override {
            UserInfo storage user = userInfo[msg.sender];
            uint256 pending;
            if (_amount == 0) { revert('cant stake 0'); }
            if (!user.existingStaker) {
                user.existingStaker = true;
                userList.push(msg.sender);
            }
            if (user.stakedAmount > 0) {
                pending = ((user.stakedAmount * accRewardsPerShare) / 1e24) - user.rewardDebt;
            }
            uint256 pendingUnstake = user.pendingUnstakeAmount;
            if (pendingUnstake >= _amount) {
                user.pendingUnstakeAmount -= _amount;
            } else {
                uint256 tokensNeeded = _amount - pendingUnstake;
                user.pendingUnstakeAmount = 0;
                proofToken.safeTransferFrom(msg.sender, address(this), tokensNeeded);
            }
            
            user.stakedAmount += _amount;
            user.rewardDebt = user.stakedAmount * accRewardsPerShare / 1e24;
            totalStakedAmount += _amount;
            if(pending > 0) {
                user.claimableRewards += pending;
            }
            
            emit Stake(msg.sender, _amount);
        }
        function unstake(uint256 _amount) external override {
            UserInfo storage user = userInfo[msg.sender];
            if (_amount == 0) { revert('cant unstake 0'); }
            if (user.stakedAmount < _amount) { revert('not enough staked'); }
            uint256 pending = ((user.stakedAmount * accRewardsPerShare) / 1e24) - user.rewardDebt;
            user.stakedAmount -= _amount;
            user.lastUnstakeTime = block.timestamp;
            user.pendingUnstakeAmount += _amount;
            user.rewardDebt = user.stakedAmount * accRewardsPerShare / 1e24;
            totalStakedAmount -= _amount;
            if(pending > 0) {
                user.claimableRewards += pending;
            }
            emit Unstake(msg.sender, _amount);
        }
        function restakeTokens() external override {
            UserInfo storage user = userInfo[msg.sender];
            uint256 pending;
            uint256 amountToRestake = user.pendingUnstakeAmount;
            if (amountToRestake == 0) { revert('No pending unstakes'); }
            if (user.stakedAmount > 0) {
                pending = ((user.stakedAmount * accRewardsPerShare) / 1e24) - user.rewardDebt;
            }
            user.pendingUnstakeAmount = 0;
            user.stakedAmount += amountToRestake;
            user.rewardDebt = user.stakedAmount * accRewardsPerShare / 1e24;
            totalStakedAmount += amountToRestake;
            if(pending > 0) {
                user.claimableRewards += pending;
            }
        }
        function claimUnstakedToken() external override {
            UserInfo storage user = userInfo[msg.sender];
            uint256 toWithdraw = user.pendingUnstakeAmount;
            if (toWithdraw == 0) { revert('No pending unstakes'); }
            if (block.timestamp < user.lastUnstakeTime + unstakeDuration) { revert('unstake not ready'); }
            user.pendingUnstakeAmount = 0;
            proofToken.safeTransfer(msg.sender, toWithdraw);
            emit Withdraw(msg.sender, toWithdraw);
        }
        function claimRewards() public override {
            UserInfo storage user = userInfo[msg.sender];
            user.claimableRewards += ((user.stakedAmount * accRewardsPerShare) / 1e24) - user.rewardDebt;
            uint256 claimable = user.claimableRewards;
            if (claimable == 0) { revert('no rewards to claim'); }  
            user.rewardDebt = user.stakedAmount * accRewardsPerShare / 1e24;
            uint256 amount = address(this).balance > claimable ? claimable : address(this).balance;
            user.claimableRewards -= amount;
            user.claimedAmount += amount; //for analytics only
            (bool sent, ) = msg.sender.call{value: amount}("");
            if (!sent) { revert('sending ETH failed'); }
            emit Claim(msg.sender, amount);
        }
        
        function setUnstakingDuration(uint256 newDuration) external override onlyOwner {
            unstakeDuration = newDuration;
        }
        function refreshPool() external override onlyOwner {
            uint256 amountToAdd = unallocatedETH;
            if (amountToAdd == 0) { revert('no unallocated ETH'); }
            if (totalStakedAmount == 0) { revert('"no holders'); }
            unallocatedETH = 0;
            accRewardsPerShare += (amountToAdd * 1e24) / totalStakedAmount; //precision
        }
        receive() external payable {
            uint256 amount = msg.value;
            if (amount == 0) { revert('no ETH'); }
            if (totalRewardDeposits[msg.sender] == 0) {
                depositorList.push(msg.sender);
            }
            totalRewardDeposits[msg.sender] += amount;
            globalETHCollected += amount;
            if (totalStakedAmount == 0) {
                unallocatedETH += amount;
            } else {
                if (ethDebt == 0) {
                    accRewardsPerShare += (amount * 1e24) / totalStakedAmount;
                } else if (ethDebt >= amount) {
                    ethDebt -= amount;
                } else { //amount > ethDebt and we have some ethDebt
                    accRewardsPerShare += ((amount - ethDebt) * 1e24) / totalStakedAmount;
                    ethDebt = 0;
                }
                
            }
        }
        function emergencyWithdrawUnclaimedRewards() external onlyOwner {
            uint256 amount = address(this).balance;
            ethDebt += amount;
            (bool sent, ) = msg.sender.call{value: amount}("");
            if (!sent) { revert('sending ETH failed'); }
        }
        //getters
        function getClaimableRewards(address _staker) external view returns (uint256) {
            UserInfo storage user = userInfo[_staker];
            return user.claimableRewards + ((user.stakedAmount * accRewardsPerShare) / 1e24) - user.rewardDebt;
        }
        function getStakers() external view returns (address[] memory) {
            return userList;
        }
        function getDepositors() external view returns (address[] memory) {
            return depositorList;
        }
        function getClaimableUnstakedToken(address _staker) external view returns (uint256) {
            UserInfo storage user = userInfo[_staker];
            if (block.timestamp >= user.lastUnstakeTime + unstakeDuration) {
                return user.pendingUnstakeAmount;
            } else {
                return 0;
            }
        }
        function getPendingUnstakeAmount(address _staker) external view returns (uint256) {
            return userInfo[_staker].pendingUnstakeAmount;
        }
        function getTotalDepositAmount(address user) external view returns (uint256) {
            return totalRewardDeposits[user];
        }
    }

    File 2 of 2: ProofToken
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)
    pragma solidity ^0.8.0;
    import "../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.
     *
     * By default, the owner account will be the one that deploys the contract. 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;
        event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
        /**
         * @dev Initializes the contract setting the deployer as the initial owner.
         */
        constructor() {
            _transferOwnership(_msgSender());
        }
        /**
         * @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 {
            require(owner() == _msgSender(), "Ownable: caller is not the owner");
        }
        /**
         * @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 {
            require(newOwner != address(0), "Ownable: new owner is the zero address");
            _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);
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)
    pragma solidity ^0.8.0;
    import "../utils/Context.sol";
    /**
     * @dev Contract module which allows children to implement an emergency stop
     * mechanism that can be triggered by an authorized account.
     *
     * This module is used through inheritance. It will make available the
     * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
     * the functions of your contract. Note that they will not be pausable by
     * simply including this module, only once the modifiers are put in place.
     */
    abstract contract Pausable is Context {
        /**
         * @dev Emitted when the pause is triggered by `account`.
         */
        event Paused(address account);
        /**
         * @dev Emitted when the pause is lifted by `account`.
         */
        event Unpaused(address account);
        bool private _paused;
        /**
         * @dev Initializes the contract in unpaused state.
         */
        constructor() {
            _paused = false;
        }
        /**
         * @dev Modifier to make a function callable only when the contract is not paused.
         *
         * Requirements:
         *
         * - The contract must not be paused.
         */
        modifier whenNotPaused() {
            _requireNotPaused();
            _;
        }
        /**
         * @dev Modifier to make a function callable only when the contract is paused.
         *
         * Requirements:
         *
         * - The contract must be paused.
         */
        modifier whenPaused() {
            _requirePaused();
            _;
        }
        /**
         * @dev Returns true if the contract is paused, and false otherwise.
         */
        function paused() public view virtual returns (bool) {
            return _paused;
        }
        /**
         * @dev Throws if the contract is paused.
         */
        function _requireNotPaused() internal view virtual {
            require(!paused(), "Pausable: paused");
        }
        /**
         * @dev Throws if the contract is not paused.
         */
        function _requirePaused() internal view virtual {
            require(paused(), "Pausable: not paused");
        }
        /**
         * @dev Triggers stopped state.
         *
         * Requirements:
         *
         * - The contract must not be paused.
         */
        function _pause() internal virtual whenNotPaused {
            _paused = true;
            emit Paused(_msgSender());
        }
        /**
         * @dev Returns to normal state.
         *
         * Requirements:
         *
         * - The contract must be paused.
         */
        function _unpause() internal virtual whenPaused {
            _paused = false;
            emit Unpaused(_msgSender());
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
    pragma solidity ^0.8.0;
    /**
     * @dev Interface of the ERC20 standard as defined in the EIP.
     */
    interface IERC20 {
        /**
         * @dev Emitted when `value` tokens are moved from one account (`from`) to
         * another (`to`).
         *
         * Note that `value` may be zero.
         */
        event Transfer(address indexed from, address indexed to, uint256 value);
        /**
         * @dev Emitted when the allowance of a `spender` for an `owner` is set by
         * a call to {approve}. `value` is the new allowance.
         */
        event Approval(address indexed owner, address indexed spender, uint256 value);
        /**
         * @dev Returns the amount of tokens in existence.
         */
        function totalSupply() external view returns (uint256);
        /**
         * @dev Returns the amount of tokens owned by `account`.
         */
        function balanceOf(address account) external view returns (uint256);
        /**
         * @dev Moves `amount` 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 amount) 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 `amount` 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 amount) external returns (bool);
        /**
         * @dev Moves `amount` tokens from `from` to `to` using the
         * allowance mechanism. `amount` 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 amount) external returns (bool);
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
    pragma solidity ^0.8.0;
    /**
     * @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;
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/EnumerableSet.sol)
    // This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
    pragma solidity ^0.8.0;
    /**
     * @dev Library for managing
     * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
     * types.
     *
     * Sets have the following properties:
     *
     * - Elements are added, removed, and checked for existence in constant time
     * (O(1)).
     * - Elements are enumerated in O(n). No guarantees are made on the ordering.
     *
     * ```solidity
     * contract Example {
     *     // Add the library methods
     *     using EnumerableSet for EnumerableSet.AddressSet;
     *
     *     // Declare a set state variable
     *     EnumerableSet.AddressSet private mySet;
     * }
     * ```
     *
     * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
     * and `uint256` (`UintSet`) are supported.
     *
     * [WARNING]
     * ====
     * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
     * unusable.
     * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
     *
     * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
     * array of EnumerableSet.
     * ====
     */
    library EnumerableSet {
        // To implement this library for multiple types with as little code
        // repetition as possible, we write it in terms of a generic Set type with
        // bytes32 values.
        // The Set implementation uses private functions, and user-facing
        // implementations (such as AddressSet) are just wrappers around the
        // underlying Set.
        // This means that we can only create new EnumerableSets for types that fit
        // in bytes32.
        struct Set {
            // Storage of set values
            bytes32[] _values;
            // Position of the value in the `values` array, plus 1 because index 0
            // means a value is not in the set.
            mapping(bytes32 => uint256) _indexes;
        }
        /**
         * @dev Add a value to a set. O(1).
         *
         * Returns true if the value was added to the set, that is if it was not
         * already present.
         */
        function _add(Set storage set, bytes32 value) private returns (bool) {
            if (!_contains(set, value)) {
                set._values.push(value);
                // The value is stored at length-1, but we add 1 to all indexes
                // and use 0 as a sentinel value
                set._indexes[value] = set._values.length;
                return true;
            } else {
                return false;
            }
        }
        /**
         * @dev Removes a value from a set. O(1).
         *
         * Returns true if the value was removed from the set, that is if it was
         * present.
         */
        function _remove(Set storage set, bytes32 value) private returns (bool) {
            // We read and store the value's index to prevent multiple reads from the same storage slot
            uint256 valueIndex = set._indexes[value];
            if (valueIndex != 0) {
                // Equivalent to contains(set, value)
                // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
                // the array, and then remove the last element (sometimes called as 'swap and pop').
                // This modifies the order of the array, as noted in {at}.
                uint256 toDeleteIndex = valueIndex - 1;
                uint256 lastIndex = set._values.length - 1;
                if (lastIndex != toDeleteIndex) {
                    bytes32 lastValue = set._values[lastIndex];
                    // Move the last value to the index where the value to delete is
                    set._values[toDeleteIndex] = lastValue;
                    // Update the index for the moved value
                    set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex
                }
                // Delete the slot where the moved value was stored
                set._values.pop();
                // Delete the index for the deleted slot
                delete set._indexes[value];
                return true;
            } else {
                return false;
            }
        }
        /**
         * @dev Returns true if the value is in the set. O(1).
         */
        function _contains(Set storage set, bytes32 value) private view returns (bool) {
            return set._indexes[value] != 0;
        }
        /**
         * @dev Returns the number of values on the set. O(1).
         */
        function _length(Set storage set) private view returns (uint256) {
            return set._values.length;
        }
        /**
         * @dev Returns the value stored at position `index` in the set. O(1).
         *
         * Note that there are no guarantees on the ordering of values inside the
         * array, and it may change when more values are added or removed.
         *
         * Requirements:
         *
         * - `index` must be strictly less than {length}.
         */
        function _at(Set storage set, uint256 index) private view returns (bytes32) {
            return set._values[index];
        }
        /**
         * @dev Return the entire set in an array
         *
         * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
         * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
         * this function has an unbounded cost, and using it as part of a state-changing function may render the function
         * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
         */
        function _values(Set storage set) private view returns (bytes32[] memory) {
            return set._values;
        }
        // Bytes32Set
        struct Bytes32Set {
            Set _inner;
        }
        /**
         * @dev Add a value to a set. O(1).
         *
         * Returns true if the value was added to the set, that is if it was not
         * already present.
         */
        function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
            return _add(set._inner, value);
        }
        /**
         * @dev Removes a value from a set. O(1).
         *
         * Returns true if the value was removed from the set, that is if it was
         * present.
         */
        function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
            return _remove(set._inner, value);
        }
        /**
         * @dev Returns true if the value is in the set. O(1).
         */
        function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
            return _contains(set._inner, value);
        }
        /**
         * @dev Returns the number of values in the set. O(1).
         */
        function length(Bytes32Set storage set) internal view returns (uint256) {
            return _length(set._inner);
        }
        /**
         * @dev Returns the value stored at position `index` in the set. O(1).
         *
         * Note that there are no guarantees on the ordering of values inside the
         * array, and it may change when more values are added or removed.
         *
         * Requirements:
         *
         * - `index` must be strictly less than {length}.
         */
        function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
            return _at(set._inner, index);
        }
        /**
         * @dev Return the entire set in an array
         *
         * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
         * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
         * this function has an unbounded cost, and using it as part of a state-changing function may render the function
         * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
         */
        function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
            bytes32[] memory store = _values(set._inner);
            bytes32[] memory result;
            /// @solidity memory-safe-assembly
            assembly {
                result := store
            }
            return result;
        }
        // AddressSet
        struct AddressSet {
            Set _inner;
        }
        /**
         * @dev Add a value to a set. O(1).
         *
         * Returns true if the value was added to the set, that is if it was not
         * already present.
         */
        function add(AddressSet storage set, address value) internal returns (bool) {
            return _add(set._inner, bytes32(uint256(uint160(value))));
        }
        /**
         * @dev Removes a value from a set. O(1).
         *
         * Returns true if the value was removed from the set, that is if it was
         * present.
         */
        function remove(AddressSet storage set, address value) internal returns (bool) {
            return _remove(set._inner, bytes32(uint256(uint160(value))));
        }
        /**
         * @dev Returns true if the value is in the set. O(1).
         */
        function contains(AddressSet storage set, address value) internal view returns (bool) {
            return _contains(set._inner, bytes32(uint256(uint160(value))));
        }
        /**
         * @dev Returns the number of values in the set. O(1).
         */
        function length(AddressSet storage set) internal view returns (uint256) {
            return _length(set._inner);
        }
        /**
         * @dev Returns the value stored at position `index` in the set. O(1).
         *
         * Note that there are no guarantees on the ordering of values inside the
         * array, and it may change when more values are added or removed.
         *
         * Requirements:
         *
         * - `index` must be strictly less than {length}.
         */
        function at(AddressSet storage set, uint256 index) internal view returns (address) {
            return address(uint160(uint256(_at(set._inner, index))));
        }
        /**
         * @dev Return the entire set in an array
         *
         * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
         * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
         * this function has an unbounded cost, and using it as part of a state-changing function may render the function
         * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
         */
        function values(AddressSet storage set) internal view returns (address[] memory) {
            bytes32[] memory store = _values(set._inner);
            address[] memory result;
            /// @solidity memory-safe-assembly
            assembly {
                result := store
            }
            return result;
        }
        // UintSet
        struct UintSet {
            Set _inner;
        }
        /**
         * @dev Add a value to a set. O(1).
         *
         * Returns true if the value was added to the set, that is if it was not
         * already present.
         */
        function add(UintSet storage set, uint256 value) internal returns (bool) {
            return _add(set._inner, bytes32(value));
        }
        /**
         * @dev Removes a value from a set. O(1).
         *
         * Returns true if the value was removed from the set, that is if it was
         * present.
         */
        function remove(UintSet storage set, uint256 value) internal returns (bool) {
            return _remove(set._inner, bytes32(value));
        }
        /**
         * @dev Returns true if the value is in the set. O(1).
         */
        function contains(UintSet storage set, uint256 value) internal view returns (bool) {
            return _contains(set._inner, bytes32(value));
        }
        /**
         * @dev Returns the number of values in the set. O(1).
         */
        function length(UintSet storage set) internal view returns (uint256) {
            return _length(set._inner);
        }
        /**
         * @dev Returns the value stored at position `index` in the set. O(1).
         *
         * Note that there are no guarantees on the ordering of values inside the
         * array, and it may change when more values are added or removed.
         *
         * Requirements:
         *
         * - `index` must be strictly less than {length}.
         */
        function at(UintSet storage set, uint256 index) internal view returns (uint256) {
            return uint256(_at(set._inner, index));
        }
        /**
         * @dev Return the entire set in an array
         *
         * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
         * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
         * this function has an unbounded cost, and using it as part of a state-changing function may render the function
         * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
         */
        function values(UintSet storage set) internal view returns (uint256[] memory) {
            bytes32[] memory store = _values(set._inner);
            uint256[] memory result;
            /// @solidity memory-safe-assembly
            assembly {
                result := store
            }
            return result;
        }
    }
    // SPDX-License-Identifier: None
    pragma solidity ^0.8.19;
    interface IProofToken {
        struct Tax {
            uint16 revenueRate;
            uint16 stakingRate;
            uint16 ventureFundRate;
        }
        /// @notice Enable/Disable trading.
        /// @dev Only owner can call this function.
        function enableTrading(bool _enable) external;
        /// @notice Set maxWallet amount.
        /// @dev ONly owner can call this function.
        function setMaxWallet(uint256 _maxWallet) external;
        /// @notice Set maxTransfer amount.
        /// @dev Only owner can call this function.
        function setMaxTransfer(uint256 _maxTransfer) external;
        /// @notice Set revenue address.
        /// @dev Only owner can call this function.
        function setRevenue(address _revenue) external;
        /// @notice Set Staking contract address.
        /// @dev Only owner can call this function.
        function setStakingContract(address _staking) external;
        /// @notice Set venture fund address.
        /// @dev Only owner can call this function.
        function setVentureFund(address _ventureFund) external;
        /// @notice Set tax for buy.
        /// @dev Only owner can call this function.
        function setTaxForBuy(Tax memory _tax) external;
        /// @notice Set tax for sell.
        /// @dev Only owner can call this function.
        function setTaxForSell(Tax memory _tax) external;
        /// @notice Withdraw rest Proof token after airdrop.
        /// @dev This can be called by only owner.
        function withdrawRestAmount(uint256 _amount) external;
        /// @notice Set new SwapThreshold amount and enable swap flag.
        /// @dev Only owner can call this function.
        function setSwapBackSettings(
            uint256 _swapThreshold,
            bool _swapEnable
        ) external;
        /// @notice Exclude wallets from TxLimit.
        /// @dev Only owner can call this function.
        function excludeWalletsFromTxLimit(
            address[] memory _wallets,
            bool _exclude
        ) external;
        /// @notice Exclude wallets from MaxWallet.
        /// @dev Only owner can call this function.
        function excludeWalletsFromMaxWallet(
            address[] memory _wallets,
            bool _exclude
        ) external;
        /// @notice Exclude wallets from Tax Fees.
        /// @dev Only owner can call this function.
        function excludeWalletsFromFees(
            address[] memory _wallets,
            bool _exclude
        ) external;
    }
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.19;
    interface IUniswapV2Factory {
        event PairCreated(
            address indexed token0,
            address indexed token1,
            address pair,
            uint
        );
        function feeTo() external view returns (address);
        function feeToSetter() external view returns (address);
        function getPair(
            address tokenA,
            address tokenB
        ) external view returns (address pair);
        function allPairs(uint) external view returns (address pair);
        function allPairsLength() external view returns (uint);
        function createPair(
            address tokenA,
            address tokenB
        ) external returns (address pair);
        function setFeeTo(address) external;
        function setFeeToSetter(address) external;
    }
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.19;
    interface IUniswapV2Router01 {
        function factory() external pure returns (address);
        function WETH() external pure returns (address);
        function addLiquidity(
            address tokenA,
            address tokenB,
            uint amountADesired,
            uint amountBDesired,
            uint amountAMin,
            uint amountBMin,
            address to,
            uint deadline
        ) external returns (uint amountA, uint amountB, uint liquidity);
        function addLiquidityETH(
            address token,
            uint amountTokenDesired,
            uint amountTokenMin,
            uint amountETHMin,
            address to,
            uint deadline
        )
            external
            payable
            returns (uint amountToken, uint amountETH, uint liquidity);
        function removeLiquidity(
            address tokenA,
            address tokenB,
            uint liquidity,
            uint amountAMin,
            uint amountBMin,
            address to,
            uint deadline
        ) external returns (uint amountA, uint amountB);
        function removeLiquidityETH(
            address token,
            uint liquidity,
            uint amountTokenMin,
            uint amountETHMin,
            address to,
            uint deadline
        ) external returns (uint amountToken, uint amountETH);
        function removeLiquidityWithPermit(
            address tokenA,
            address tokenB,
            uint liquidity,
            uint amountAMin,
            uint amountBMin,
            address to,
            uint deadline,
            bool approveMax,
            uint8 v,
            bytes32 r,
            bytes32 s
        ) external returns (uint amountA, uint amountB);
        function removeLiquidityETHWithPermit(
            address token,
            uint liquidity,
            uint amountTokenMin,
            uint amountETHMin,
            address to,
            uint deadline,
            bool approveMax,
            uint8 v,
            bytes32 r,
            bytes32 s
        ) external returns (uint amountToken, uint amountETH);
        function swapExactTokensForTokens(
            uint amountIn,
            uint amountOutMin,
            address[] calldata path,
            address to,
            uint deadline
        ) external returns (uint[] memory amounts);
        function swapTokensForExactTokens(
            uint amountOut,
            uint amountInMax,
            address[] calldata path,
            address to,
            uint deadline
        ) external returns (uint[] memory amounts);
        function swapExactETHForTokens(
            uint amountOutMin,
            address[] calldata path,
            address to,
            uint deadline
        ) external payable returns (uint[] memory amounts);
        function swapTokensForExactETH(
            uint amountOut,
            uint amountInMax,
            address[] calldata path,
            address to,
            uint deadline
        ) external returns (uint[] memory amounts);
        function swapExactTokensForETH(
            uint amountIn,
            uint amountOutMin,
            address[] calldata path,
            address to,
            uint deadline
        ) external returns (uint[] memory amounts);
        function swapETHForExactTokens(
            uint amountOut,
            address[] calldata path,
            address to,
            uint deadline
        ) external payable returns (uint[] memory amounts);
        function quote(
            uint amountA,
            uint reserveA,
            uint reserveB
        ) external pure returns (uint amountB);
        function getAmountOut(
            uint amountIn,
            uint reserveIn,
            uint reserveOut
        ) external pure returns (uint amountOut);
        function getAmountIn(
            uint amountOut,
            uint reserveIn,
            uint reserveOut
        ) external pure returns (uint amountIn);
        function getAmountsOut(
            uint amountIn,
            address[] calldata path
        ) external view returns (uint[] memory amounts);
        function getAmountsIn(
            uint amountOut,
            address[] calldata path
        ) external view returns (uint[] memory amounts);
    }
    interface IUniswapV2Router02 is IUniswapV2Router01 {
        function removeLiquidityETHSupportingFeeOnTransferTokens(
            address token,
            uint liquidity,
            uint amountTokenMin,
            uint amountETHMin,
            address to,
            uint deadline
        ) external returns (uint amountETH);
        function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
            address token,
            uint liquidity,
            uint amountTokenMin,
            uint amountETHMin,
            address to,
            uint deadline,
            bool approveMax,
            uint8 v,
            bytes32 r,
            bytes32 s
        ) external returns (uint amountETH);
        function swapExactTokensForTokensSupportingFeeOnTransferTokens(
            uint amountIn,
            uint amountOutMin,
            address[] calldata path,
            address to,
            uint deadline
        ) external;
        function swapExactETHForTokensSupportingFeeOnTransferTokens(
            uint amountOutMin,
            address[] calldata path,
            address to,
            uint deadline
        ) external payable;
        function swapExactTokensForETHSupportingFeeOnTransferTokens(
            uint amountIn,
            uint amountOutMin,
            address[] calldata path,
            address to,
            uint deadline
        ) external;
    }
    // SPDX-License-Identifier: None
    pragma solidity ^0.8.19;
    import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    import "@openzeppelin/contracts/access/Ownable.sol";
    import "@openzeppelin/contracts/security/Pausable.sol";
    import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
    import "./interfaces/IProofToken.sol";
    import "./interfaces/IUniswapV2Router02.sol";
    import "./interfaces/IUniswapV2Factory.sol";
    contract ProofToken is Ownable, Pausable, IERC20, IProofToken {
        using EnumerableSet for EnumerableSet.AddressSet;
        mapping(address => uint256) private _balances;
        mapping(address => mapping(address => uint256)) private _allowances;
        mapping(address => bool) public excludedFromTxLimit;
        mapping(address => bool) public excludedFromMaxWallet;
        mapping(address => bool) public excludedFromFees;
        // mapping(address => bool) public airdropped;
        uint256 private constant _totalSupply = 100_000_000 * 10 ** _decimals;
        uint256 public maxTransfer;
        uint256 public maxWallet;
        // uint256 public airdroppedAmount;
        uint256 public immutable launchTime;
        uint256 public swapThreshold;
        uint256 public accAmountForStaking;
        uint256 public accAmountForRevenue;
        uint256 public accAmountForVentureFund;
        // uint256 public airdropReleaseTime;
        // uint256 public minHLDAmountForAirdrop;
        address public revenue;
        address public stakingContract;
        address public ventureFund;
        address public pair;
        // address public constant DEAD = 0x000000000000000000000000000000000000dEaD;
        Tax public taxForBuy;
        Tax public taxForSell;
        address public immutable router;
        bool public tradingEnable;
        bool private inSwapLiquidity;
        bool public swapEnable;
        string private constant _name = "PROOF";
        string private constant _symbol = "PROOF";
        uint8 private constant _decimals = 9;
        uint16 public constant FIXED_POINT = 1000;
        /// @dev Status flag to show airdrop is already processed or not.
        // bool public airdropProcessed;
        constructor(
            address _router,
            address _revenue,
            address _ventureFund,
            address _stakingContract,
            Tax memory _taxForBuy,
            Tax memory _taxForSell
        ) {
            require(_router != address(0), "zero router address");
            require(_revenue != address(0), "zero revenue address");
            require(_ventureFund != address(0), "zero ventureFund address");
            revenue = _revenue;
            ventureFund = _ventureFund;
            stakingContract = _stakingContract;
            _balances[address(this)] = _totalSupply;
            emit Transfer(address(0), address(this), _totalSupply);
            maxWallet = _totalSupply / 100; // 1%
            maxTransfer = (_totalSupply * 5) / 1000; // 0.5%
            router = _router;
            _createPair();
            swapThreshold = _totalSupply / 10000; // 0.01%
            excludedFromTxLimit[msg.sender] = true;
            excludedFromTxLimit[pair] = true;
            excludedFromTxLimit[address(this)] = true;
            excludedFromMaxWallet[msg.sender] = true;
            excludedFromMaxWallet[pair] = true;
            excludedFromMaxWallet[address(this)] = true;
            excludedFromMaxWallet[revenue] = true;
            excludedFromMaxWallet[stakingContract] = true;
            excludedFromMaxWallet[ventureFund] = true;
            excludedFromFees[msg.sender] = true;
            excludedFromFees[_revenue] = true;
            excludedFromFees[_ventureFund] = true;
            excludedFromFees[stakingContract] = true;
            taxForBuy = _taxForBuy;
            taxForSell = _taxForSell;
            swapEnable = true;
            launchTime = block.timestamp;
        }
        // !---------------- functions for ERC20 token ----------------!
        function name() external view returns (string memory) {
            return _name;
        }
        function symbol() external view returns (string memory) {
            return _symbol;
        }
        function decimals() external pure returns (uint8) {
            return _decimals;
        }
        function totalSupply() external view returns (uint256) {
            return _totalSupply;
        }
        function balanceOf(address account) public view returns (uint256) {
            return _balances[account];
        }
        function airdrop(address[] memory users, uint256[] memory amounts) external onlyOwner {
            uint256 len = users.length;
            require(len == amounts.length, "lists must be equal");
            for (uint256 i = 0; i < len; i++) {
                _basicTransfer(amounts[i], address(this), users[i]);
                emit Transfer(address(this), users[i], amounts[i]);
            }
        }
        function transfer(
            address _recipient,
            uint256 _amount
        ) external override returns (bool) {
            _transfer(msg.sender, _recipient, _amount);
            return true;
        }
        function allowance(
            address _owner,
            address _spender
        ) external view override returns (uint256) {
            return _allowances[_owner][_spender];
        }
        function approve(
            address _spender,
            uint256 _amount
        ) external override returns (bool) {
            _approve(msg.sender, _spender, _amount);
            return true;
        }
        function transferFrom(
            address _sender,
            address _recipient,
            uint256 _amount
        ) external override returns (bool) {
            uint256 currentAllowance = _allowances[_sender][msg.sender];
            require(currentAllowance >= _amount, "Transfer > allowance");
            _approve(_sender, msg.sender, currentAllowance - _amount);
            _transfer(_sender, _recipient, _amount);
            return true;
        }
        // !---------------- functions for ERC20 token ----------------!
        /// @inheritdoc IProofToken
        function excludeWalletsFromTxLimit(
            address[] memory _wallets,
            bool _exclude
        ) external override onlyOwner {
            uint256 length = _wallets.length;
            require(length > 0, "invalid array");
            for (uint256 i = 0; i < length; i++) {
                excludedFromTxLimit[_wallets[i]] = _exclude;
            }
        }
        /// @inheritdoc IProofToken
        function excludeWalletsFromMaxWallet(
            address[] memory _wallets,
            bool _exclude
        ) external override onlyOwner {
            uint256 length = _wallets.length;
            require(length > 0, "invalid array");
            for (uint256 i = 0; i < length; i++) {
                excludedFromMaxWallet[_wallets[i]] = _exclude;
            }
        }
        /// @inheritdoc IProofToken
        function excludeWalletsFromFees(
            address[] memory _wallets,
            bool _exclude
        ) external override onlyOwner {
            uint256 length = _wallets.length;
            require(length > 0, "invalid array");
            for (uint256 i = 0; i < length; i++) {
                excludedFromFees[_wallets[i]] = _exclude;
            }
        }
        /// @inheritdoc IProofToken
        function enableTrading(bool _enable) external override onlyOwner {
            tradingEnable = _enable;
        }
        /// @inheritdoc IProofToken
        function setMaxWallet(uint256 _maxWallet) external override onlyOwner {
            require(_maxWallet > 0, "invalid maxWallet");
            maxWallet = _maxWallet;
        }
        /// @inheritdoc IProofToken
        function setMaxTransfer(uint256 _maxTransfer) external override onlyOwner {
            require(_maxTransfer > 0, "invalid maxTransfer");
            maxTransfer = _maxTransfer;
        }
        /// @inheritdoc IProofToken
        function setSwapBackSettings(
            uint256 _swapThreshold,
            bool _swapEnable
        ) external override onlyOwner {
            swapEnable = _swapEnable;
            swapThreshold = _swapThreshold;
        }
        /// @inheritdoc IProofToken
        function setRevenue(address _revenue) external override onlyOwner {
            require(_revenue != address(0), "zero revenue address");
            excludedFromFees[revenue] = false;
            excludedFromFees[_revenue] = true;
            revenue = _revenue;
        }
        /// @inheritdoc IProofToken
        function setStakingContract(address _staking) external override onlyOwner {
            require(_staking != address(0), "zero staking contract address");
            if (stakingContract != address(0)) {
                excludedFromFees[stakingContract] = false;
            }
            excludedFromMaxWallet[_staking] = true;
            excludedFromFees[_staking] = true;
            stakingContract = _staking;
            
        }
        /// @inheritdoc IProofToken
        function setVentureFund(address _ventureFund) external override onlyOwner {
            require(_ventureFund != address(0), "zero revenue address");
            excludedFromFees[ventureFund] = false;
            excludedFromFees[_ventureFund] = true;
            ventureFund = _ventureFund;
        }
        /// @inheritdoc IProofToken
        function setTaxForBuy(Tax memory _tax) external override onlyOwner {
            require((_tax.revenueRate + _tax.stakingRate + _tax.ventureFundRate) <= 120, "12% max");
            taxForBuy = _tax;
        }
        /// @inheritdoc IProofToken
        function setTaxForSell(Tax memory _tax) external override onlyOwner {
            require((_tax.revenueRate + _tax.stakingRate + _tax.ventureFundRate) <= 170, "17% max");
            taxForSell = _tax;
        }
        /// @inheritdoc IProofToken
        function withdrawRestAmount(uint256 _amount) external override onlyOwner {
            uint256 availableAmount = _balances[address(this)];
            uint256 feeAmount = accAmountForRevenue +
                accAmountForStaking +
                accAmountForVentureFund;
            availableAmount -= feeAmount;
            require(availableAmount >= _amount, "not enough balance to withdraw");
            _transfer(address(this), owner(), _amount);
        }
        receive() external payable {}
        function _transfer(
            address _sender,
            address _recipient,
            uint256 _amount
        ) internal {
            require(_sender != address(0), "transfer from zero address");
            require(_recipient != address(0), "transfer to zero address");
            require(_amount > 0, "zero amount");
            require(_balances[_sender] >= _amount, "not enough amount to transfer");
            require(
                tradingEnable || (_recipient == stakingContract || _sender == stakingContract || _sender == owner() || _sender == address(this)),
                "trading is not enabled"
            );
            if (inSwapLiquidity || !tradingEnable) { 
                _basicTransfer(_amount, _sender, _recipient);
                emit Transfer(_sender, _recipient, _amount);
                return;
            }
            require(
                excludedFromTxLimit[_sender] || _amount <= maxTransfer,
                "over max transfer amount"
            );
            require(
                excludedFromMaxWallet[_recipient] ||
                    _balances[_recipient] + _amount <= maxWallet,
                "exceeds to max wallet"
            );
            bool feelessTransfer = (excludedFromFees[_sender] ||
                excludedFromFees[_recipient]);
            if (_sender == pair) {
                // buy
                if (feelessTransfer) {
                    _basicTransfer(_amount, _sender, _recipient);
                } else {
                    _takeFee(taxForBuy, _amount, _sender, _recipient);
                }
            } else {
                _distributeFees();
                // sell or wallet transfer
                if (_recipient == pair) {
                    // sell
                    if (feelessTransfer) {
                        _basicTransfer(_amount, _sender, _recipient);
                    } else {
                        _takeFee(taxForSell, _amount, _sender, _recipient);
                    }
                } else {
                    _basicTransfer(_amount, _sender, _recipient);
                }
            }
            emit Transfer(_sender, _recipient, _amount);
        }
        function _basicTransfer(
            uint256 _amount,
            address _sender,
            address _recipient
        ) internal {
            _balances[_sender] -= _amount;
            _balances[_recipient] += _amount;
        }
        function _takeFee(
            Tax memory _tax,
            uint256 _amount,
            address _sender,
            address _recipient
        ) internal {
            uint16 totalFee = _tax.revenueRate + _tax.stakingRate + _tax.ventureFundRate;
            uint256 feeAmount = (_amount * totalFee) / FIXED_POINT;
            uint256 revenueFee = (_amount * _tax.revenueRate) / FIXED_POINT;
            uint256 stakingFee = (_amount * _tax.stakingRate) / FIXED_POINT;
            uint256 ventureFee = feeAmount - revenueFee - stakingFee;
            accAmountForRevenue += revenueFee;
            accAmountForStaking += stakingFee;
            accAmountForVentureFund += ventureFee;
            uint256 transferAmount = _amount - feeAmount;
            _balances[address(this)] += feeAmount;
            _balances[_sender] -= _amount;
            _balances[_recipient] += transferAmount;
        }
        function _distributeFees() internal {
            uint256 feeAmount = accAmountForRevenue +
                accAmountForStaking +
                accAmountForVentureFund;
            if (feeAmount < swapThreshold || !swapEnable) {
                return;
            }
            if (feeAmount > 0) {
                inSwapLiquidity = true;
                _swapTokensToETH(feeAmount);
                uint256 swappedETHAmount = address(this).balance;
                inSwapLiquidity = false;
                uint256 revenueFee = (swappedETHAmount * accAmountForRevenue) /
                    feeAmount;
                uint256 ventureFee = (swappedETHAmount * accAmountForVentureFund) /
                    feeAmount;
                uint256 stakingFee = swappedETHAmount - revenueFee - ventureFee;
                _transferETH(revenue, revenueFee);
                _transferETH(stakingContract, stakingFee);
                _transferETH(ventureFund, ventureFee);
            }
            accAmountForRevenue = 0;
            accAmountForStaking = 0;
            accAmountForVentureFund = 0;
        }
        function _swapTokensToETH(uint256 _amount) internal {
            address[] memory path = new address[](2);
            path[0] = address(this);
            path[1] = _getWrappedToken();
            _approve(address(this), router, _amount);
                IUniswapV2Router02(router)
                    .swapExactTokensForETHSupportingFeeOnTransferTokens(
                        _amount,
                        0,
                        path,
                        address(this),
                        block.timestamp
                    );
        }
        function _transferETH(address _to, uint256 _amount) internal {
            if (_amount == 0) return;
            (bool sent, ) = _to.call{value: _amount}("");
            require(sent, "sending ETH failed");
        }
        function _approve(
            address _owner,
            address _spender,
            uint256 _amount
        ) private {
            require(_owner != address(0), "Approve from zero");
            require(_spender != address(0), "Approve to zero");
            _allowances[_owner][_spender] = _amount;
            emit Approval(_owner, _spender, _amount);
        }
        function _createPair() internal {
            address WToken = _getWrappedToken();
            pair = IUniswapV2Factory(IUniswapV2Router02(router).factory())
                .createPair(WToken, address(this));
        }
        function _getWrappedToken() internal view returns (address) {
            return
                IUniswapV2Router02(router).WETH();
        }
    }