// File: @openzeppelin/contracts/token/ERC20/IERC20.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.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);
}

// File: @rari-capital/solmate/src/auth/Owned.sol



// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Simple single owner authorization mixin.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/auth/Owned.sol)
abstract contract Owned {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event OwnerUpdated(address indexed user, address indexed newOwner);

    /*//////////////////////////////////////////////////////////////
                            OWNERSHIP STORAGE
    //////////////////////////////////////////////////////////////*/

    address public owner;

    modifier onlyOwner() virtual {
        require(msg.sender == owner, "UNAUTHORIZED");

        _;
    }

    /*//////////////////////////////////////////////////////////////
                               CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    constructor(address _owner) {
        owner = _owner;

        emit OwnerUpdated(address(0), _owner);
    }

    /*//////////////////////////////////////////////////////////////
                             OWNERSHIP LOGIC
    //////////////////////////////////////////////////////////////*/

    function setOwner(address newOwner) public virtual onlyOwner {
        owner = newOwner;

        emit OwnerUpdated(msg.sender, newOwner);
    }
}

// File: @rari-capital/solmate/src/utils/ReentrancyGuard.sol



// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Gas optimized reentrancy protection for smart contracts.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/ReentrancyGuard.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol)
abstract contract ReentrancyGuard {
    uint256 private locked = 1;

    modifier nonReentrant() virtual {
        require(locked == 1, "REENTRANCY");

        locked = 2;

        _;

        locked = 1;
    }
}

// File: contracts/libraries/FullMath.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

/// @title Contains 512-bit math functions
/// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision
/// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits
/// @dev Adapted to pragma solidity 0.8 from https://github.com/Uniswap/v3-core/blob/main/contracts/libraries/FullMath.sol
library FullMath {
    /// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
    /// @param a The multiplicand
    /// @param b The multiplier
    /// @param denominator The divisor
    /// @return result The 256-bit result
    /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv
    function mulDiv(
        uint256 a,
        uint256 b,
        uint256 denominator
    ) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = a * b
            // Compute the product mod 2**256 and mod 2**256 - 1
            // then use the Chinese Remainder Theorem to reconstruct
            // the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2**256 + prod0
            uint256 prod0; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(a, b, not(0))
                prod0 := mul(a, b)
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division
            if (prod1 == 0) {
                require(denominator > 0);
                assembly {
                    result := div(prod0, denominator)
                }
                return result;
            }

            // Make sure the result is less than 2**256.
            // Also prevents denominator == 0
            require(denominator > prod1);

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

            // Make division exact by subtracting the remainder from [prod1 prod0]
            // Compute remainder using mulmod
            uint256 remainder;
            assembly {
                remainder := mulmod(a, b, denominator)
            }
            // Subtract 256 bit number from 512 bit number
            assembly {
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator
            // Compute largest power of two divisor of denominator.
            // Always >= 1.
            uint256 twos = (0 - denominator) & denominator;
            // Divide denominator by power of two
            assembly {
                denominator := div(denominator, twos)
            }

            // Divide [prod1 prod0] by the factors of two
            assembly {
                prod0 := div(prod0, twos)
            }
            // Shift in bits from prod1 into prod0. For this we need
            // to flip `twos` such that it is 2**256 / twos.
            // If twos is zero, then it becomes one
            assembly {
                twos := add(div(sub(0, twos), twos), 1)
            }
            prod0 |= prod1 * twos;

            // Invert denominator mod 2**256
            // Now that denominator is an odd number, it has an inverse
            // modulo 2**256 such that denominator * inv = 1 mod 2**256.
            // Compute the inverse by starting with a seed that is correct
            // correct for four bits. That is, denominator * inv = 1 mod 2**4
            uint256 inv = (3 * denominator) ^ 2;
            // Now use Newton-Raphson iteration to improve the precision.
            // Thanks to Hensel's lifting lemma, this also works in modular
            // arithmetic, doubling the correct bits in each step.
            inv *= 2 - denominator * inv; // inverse mod 2**8
            inv *= 2 - denominator * inv; // inverse mod 2**16
            inv *= 2 - denominator * inv; // inverse mod 2**32
            inv *= 2 - denominator * inv; // inverse mod 2**64
            inv *= 2 - denominator * inv; // inverse mod 2**128
            inv *= 2 - denominator * inv; // inverse mod 2**256

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

    /// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
    /// @param a The multiplicand
    /// @param b The multiplier
    /// @param denominator The divisor
    /// @return result The 256-bit result
    function mulDivRoundingUp(
        uint256 a,
        uint256 b,
        uint256 denominator
    ) internal pure returns (uint256 result) {
        result = mulDiv(a, b, denominator);
        if (mulmod(a, b, denominator) > 0) {
            require(result < type(uint256).max);
            result++;
        }
    }
}

// File: contracts/rocketPool/RocketDepositPoolInterface.sol



pragma solidity >0.5.0 <0.9.0;

// SPDX-License-Identifier: GPL-3.0-only

interface RocketDepositPoolInterface {
    function getBalance() external view returns (uint256);
    function getExcessBalance() external view returns (uint256);
    function deposit() external payable;
    function recycleDissolvedDeposit() external payable;
    function recycleExcessCollateral() external payable;
    function recycleLiquidatedStake() external payable;
    function assignDeposits() external;
    function withdrawExcessBalance(uint256 _amount) external;
}
// File: contracts/rocketPool/RocketStorageInterface.sol



pragma solidity >0.5.0 <0.9.0;

// SPDX-License-Identifier: GPL-3.0-only

interface RocketStorageInterface {

    // Deploy status
    function getDeployedStatus() external view returns (bool);

    // Guardian
    function getGuardian() external view returns(address);
    function setGuardian(address _newAddress) external;
    function confirmGuardian() external;

    // Getters
    function getAddress(bytes32 _key) external view returns (address);
    function getUint(bytes32 _key) external view returns (uint);
    function getString(bytes32 _key) external view returns (string memory);
    function getBytes(bytes32 _key) external view returns (bytes memory);
    function getBool(bytes32 _key) external view returns (bool);
    function getInt(bytes32 _key) external view returns (int);
    function getBytes32(bytes32 _key) external view returns (bytes32);

    // Setters
    function setAddress(bytes32 _key, address _value) external;
    function setUint(bytes32 _key, uint _value) external;
    function setString(bytes32 _key, string calldata _value) external;
    function setBytes(bytes32 _key, bytes calldata _value) external;
    function setBool(bytes32 _key, bool _value) external;
    function setInt(bytes32 _key, int _value) external;
    function setBytes32(bytes32 _key, bytes32 _value) external;

    // Deleters
    function deleteAddress(bytes32 _key) external;
    function deleteUint(bytes32 _key) external;
    function deleteString(bytes32 _key) external;
    function deleteBytes(bytes32 _key) external;
    function deleteBool(bytes32 _key) external;
    function deleteInt(bytes32 _key) external;
    function deleteBytes32(bytes32 _key) external;

    // Arithmetic
    function addUint(bytes32 _key, uint256 _amount) external;
    function subUint(bytes32 _key, uint256 _amount) external;

    // Protected storage
    function getNodeWithdrawalAddress(address _nodeAddress) external view returns (address);
    function getNodePendingWithdrawalAddress(address _nodeAddress) external view returns (address);
    function setWithdrawalAddress(address _nodeAddress, address _newWithdrawalAddress, bool _confirm) external;
    function confirmWithdrawalAddress(address _nodeAddress) external;
}

// File: contracts/rocketPool/RocketTokenRETHInterface.sol



pragma solidity >0.5.0 <0.9.0;

// SPDX-License-Identifier: GPL-3.0-only

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface RocketTokenRETHInterface is IERC20 {
    function getEthValue(uint256 _rethAmount) external view returns (uint256);
    function getRethValue(uint256 _ethAmount) external view returns (uint256);
    function getExchangeRate() external view returns (uint256);
    function getTotalCollateral() external view returns (uint256);
    function getCollateralRate() external view returns (uint256);
    function depositExcess() external payable;
    function depositExcessCollateral() external;
    function mint(uint256 _ethAmount, address _to) external;
    function burn(uint256 _rethAmount) external;
}
// File: contracts/StakingContract.sol



// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@rari-capital/solmate/src/utils/ReentrancyGuard.sol";
import "@rari-capital/solmate/src/auth/Owned.sol";
import "./rocketPool/RocketStorageInterface.sol";
import "./rocketPool/RocketDepositPoolInterface.sol";
import "./rocketPool/RocketTokenRETHInterface.sol";
import "./libraries/FullMath.sol";

contract StakingContract is Owned, ReentrancyGuard {
    address private constant DEAD_ADDRESS =
        0x000000000000000000000000000000000000dEaD;
    address public immutable POS32;
    address public protocolWallet;

    RocketStorageInterface public immutable rocketStorage;

    uint256 public endTime;
    uint256 public lastRewardTime;
    uint256 public rewardRemaining;
    uint256 public totalStaked;
    uint256 public tokenStaked;
    uint256 public tokenBurnBIPS;

    uint256 public eTH2TokenNumerator;
    uint256 public eTH2TokenDenominator;

    // Initial value of rewardPerLiquidity can be arbitrarily set to a non-zero value.
    uint256 public rewardPerLiquidity = type(uint256).max / 2;

    /// @dev rewardPerLiquidityLast[user]
    mapping(address => uint256) public rewardPerLiquidityLast;

    /// @dev userStakes[user]
    mapping(address => UserStake) public userStakes;

    struct UserStake {
        uint128 liquidity;
        uint128 tokenStaked;
    }

    error ZeroDenominator();
    error InvalidTimeFrame();
    error PercentageTooHigh();
    error InsufficientRethMinted();
    error InsufficientStakedAmount();

    event IncentiveUpdated(
        uint256 newStartTime,
        uint256 newEndTime,
        uint256 changeAmount
    );
    event Stake(
        address indexed user,
        uint256 amountETH,
        uint256 amountToken,
        uint256 amountTokenLeft
    );
    event Unstake(address indexed user, uint256 amountETH, uint256 amountToken);
    event Claim(address indexed user, uint256 amountETH);
    event ETH2TokenRaioUpdated(uint256 numerator, uint256 denominator);
    event BurnBIPSUpdated(uint256 newBIPS);
    event ProtocolWalletUpdated(address newAddress);

    constructor(
        address _pos32,
        address _rocketStorageAddress,
        address _protocolWallet,
        uint256 _eTH2TokenNumerator,
        uint256 _eTH2TokenDenominator,
        uint256 _tokenBurnBIPS
    ) Owned(msg.sender) {
        POS32 = _pos32;
        rocketStorage = RocketStorageInterface(_rocketStorageAddress);
        _updateProtocolWallet(_protocolWallet);
        _updateTokenBurnBIPS(_tokenBurnBIPS);
        _updateETH2TokenRatio(_eTH2TokenNumerator, _eTH2TokenDenominator);
    }

    function updateETH2TokenRatio(
        uint256 numerator,
        uint256 denominator
    ) external onlyOwner {
        _updateETH2TokenRatio(numerator, denominator);
    }

    function updateTokenBurnBIPS(uint256 newBIPS) external onlyOwner {
        _updateTokenBurnBIPS(newBIPS);
    }

    function updateProtocolWallet(address newAddress) external onlyOwner {
        _updateProtocolWallet(newAddress);
    }

    function updateIncentive(
        uint256 newStartTime,
        uint256 newEndTime
    ) external payable onlyOwner nonReentrant {
        _accrueRewards();

        if (newStartTime != 0) {
            if (newStartTime < block.timestamp) newStartTime = block.timestamp;
            lastRewardTime = newStartTime;
        }

        if (newEndTime != 0) {
            if (newEndTime < block.timestamp) newEndTime = block.timestamp;
            endTime = newEndTime;
        }

        if (lastRewardTime >= endTime) revert InvalidTimeFrame();

        if (msg.value != 0) {
            rewardRemaining += msg.value;
        }

        emit IncentiveUpdated(lastRewardTime, endTime, msg.value);
    }

    function stake(bool transferExistingRewards) external payable nonReentrant {
        // Get both ETH, token into the contract
        uint256 amountETH = msg.value;
        uint256 amountToken = calculateToken(amountETH);
        _receivePOS32From(msg.sender, amountToken);

        // Calculate and burn required amount of token
        uint256 amountTokenBurn = calculateTokenBurn(amountToken);
        uint256 amountTokenLeft = amountToken - amountTokenBurn;
        if (amountTokenBurn != 0) {
            _burnPOS32(amountTokenBurn);
        }

        // Fetch user stake info
        UserStake storage userStake = userStakes[msg.sender];
        uint128 previousLiquidity = userStake.liquidity;

        uint256 amountETHNet = _depositToRocketPool(amountETH);

        // Update user stake info
        userStake.liquidity += uint128(amountETHNet);
        userStake.tokenStaked += uint128(amountTokenLeft);

        // Accrue and handle rewards
        uint256 rewards;
        _accrueRewards();
        if (transferExistingRewards) {
            rewards = _claimReward(previousLiquidity);
        } else {
            _saveReward(previousLiquidity, userStake.liquidity);
        }

        // Update global trackers
        totalStaked += amountETHNet;
        tokenStaked += amountTokenLeft;

        _sendETH(msg.sender, rewards);

        emit Stake(msg.sender, amountETHNet, amountToken, amountTokenLeft);
    }

    function unstake(
        uint128 amountETH,
        bool transferExistingRewards
    ) external nonReentrant {
        // Fetch user stake info
        UserStake storage userStake = userStakes[msg.sender];

        // Check limits
        uint128 previousLiquidity = userStake.liquidity;
        if (amountETH > previousLiquidity) revert InsufficientStakedAmount();

        // Calculate token to return
        uint256 amountToken = FullMath.mulDiv(
            userStake.tokenStaked,
            amountETH,
            previousLiquidity
        );

        // Update user stake info
        userStake.liquidity -= amountETH;
        userStake.tokenStaked -= uint128(amountToken);

        // Accrue and handle rewards
        uint256 rewards;
        _accrueRewards();
        if (transferExistingRewards || userStake.liquidity == 0) {
            rewards = _claimReward(previousLiquidity);
        } else {
            _saveReward(previousLiquidity, userStake.liquidity);
        }

        address rocketTokenRETHAddress = rocketStorage.getAddress(
            keccak256(abi.encodePacked("contract.address", "rocketTokenRETH"))
        );

        uint256 equivalentReth = (RocketTokenRETHInterface(
            rocketTokenRETHAddress
        ).balanceOf(address(this)) * amountETH) / totalStaked;
        uint256 principalReth = RocketTokenRETHInterface(rocketTokenRETHAddress)
            .getRethValue(amountETH);

        // Update global trackers
        totalStaked -= amountETH;
        tokenStaked -= amountToken;

        // Distribute principal
        _sendRethTo(rocketTokenRETHAddress, msg.sender, principalReth);
        _sendRethTo(
            rocketTokenRETHAddress,
            protocolWallet,
            equivalentReth - principalReth
        );
        _sendPOS32To(msg.sender, amountToken);

        // Distribute rewards
        _sendETH(msg.sender, rewards);

        emit Unstake(msg.sender, amountETH, amountToken);
    }

    function accrueRewards() external {
        _accrueRewards();
    }

    function claim() external nonReentrant {
        _accrueRewards();
        uint256 rewards = _claimReward(userStakes[msg.sender].liquidity);
        _sendETH(msg.sender, rewards);
    }

    function getAPR() external view returns (uint256) {
        if (endTime < block.timestamp || totalStaked == 0) {
            return 0;
        }

        uint256 timeRemaining = endTime - block.timestamp;
        uint256 secondsInAYear = 31_536_000;
        return
            (rewardRemaining * secondsInAYear * 10_000) /
            (timeRemaining * totalStaked);
    }

    function pendingRewards(address user) external view returns (uint256) {
        return _calculateReward(user, userStakes[user].liquidity);
    }

    function calculateToken(uint256 amountETH) public view returns (uint256) {
        return (amountETH * eTH2TokenNumerator) / eTH2TokenDenominator;
    }

    function calculateTokenBurn(
        uint256 amountToken
    ) public view returns (uint256) {
        return (amountToken * tokenBurnBIPS) / 10_000;
    }

    // This returns the net eth value deposited at RocketPool (net of their fees)
    function _depositToRocketPool(
        uint256 amountETH
    ) internal returns (uint256) {
        // Load contracts
        address rocketDepositPoolAddress = rocketStorage.getAddress(
            keccak256(abi.encodePacked("contract.address", "rocketDepositPool"))
        );
        address rocketTokenRETHAddress = rocketStorage.getAddress(
            keccak256(abi.encodePacked("contract.address", "rocketTokenRETH"))
        );

        // Forward deposit to RP & get amount of Reth minted
        uint256 rethBalance1 = RocketTokenRETHInterface(rocketTokenRETHAddress)
            .balanceOf(address(this));
        RocketDepositPoolInterface(rocketDepositPoolAddress).deposit{
            value: amountETH
        }();
        uint256 rethBalance2 = RocketTokenRETHInterface(rocketTokenRETHAddress)
            .balanceOf(address(this));

        if (rethBalance2 == rethBalance1) revert InsufficientRethMinted();

        return
            RocketTokenRETHInterface(rocketTokenRETHAddress).getEthValue(
                rethBalance2 - rethBalance1
            );
    }

    function _updateETH2TokenRatio(
        uint256 numerator,
        uint256 denominator
    ) internal {
        if (denominator == 0) revert ZeroDenominator();

        eTH2TokenNumerator = numerator;
        eTH2TokenDenominator = denominator;

        emit ETH2TokenRaioUpdated(numerator, denominator);
    }

    function _accrueRewards() internal {
        uint256 lastRewardTimeLocal = lastRewardTime;
        uint256 totalStakedLocal = totalStaked;
        uint256 endTimeLocal = endTime;

        unchecked {
            uint256 maxTime = block.timestamp < endTimeLocal
                ? block.timestamp
                : endTimeLocal;
            if (totalStakedLocal > 0 && lastRewardTimeLocal < maxTime) {
                uint256 totalTime = endTimeLocal - lastRewardTimeLocal;
                uint256 passedTime = maxTime - lastRewardTimeLocal;
                uint256 reward = (uint256(rewardRemaining) * passedTime) /
                    totalTime;

                // Overflow is unrealistic.
                rewardPerLiquidity +=
                    (reward * type(uint128).max) /
                    totalStakedLocal;
                rewardRemaining -= reward;
                lastRewardTime = maxTime;
            } else if (
                totalStakedLocal == 0 && lastRewardTimeLocal < block.timestamp
            ) {
                lastRewardTime = maxTime;
            }
        }
    }

    /*
    This function DOES NOT SEND the rewards. It returns the reward amount to the caller function
    */
    function _claimReward(
        uint128 usersLiquidity
    ) internal returns (uint256 reward) {
        reward = _calculateReward(msg.sender, usersLiquidity);
        rewardPerLiquidityLast[msg.sender] = rewardPerLiquidity;
        emit Claim(msg.sender, reward);
    }

    // We offset the rewardPerLiquidityLast snapshot so that the current reward is included next time we call _claimReward.
    function _saveReward(
        uint128 usersLiquidity,
        uint128 newLiquidity
    ) internal returns (uint256 reward) {
        reward = _calculateReward(msg.sender, usersLiquidity);
        uint256 rewardPerLiquidityDelta = (reward * type(uint128).max) /
            newLiquidity;
        rewardPerLiquidityLast[msg.sender] =
            rewardPerLiquidity -
            rewardPerLiquidityDelta;
    }

    function _calculateReward(
        address user,
        uint128 usersLiquidity
    ) internal view returns (uint256 reward) {
        uint256 userRewardPerLiquidtyLast = rewardPerLiquidityLast[user];
        uint256 rewardPerLiquidityDelta;

        unchecked {
            rewardPerLiquidityDelta =
                rewardPerLiquidity -
                userRewardPerLiquidtyLast;
        }

        reward = FullMath.mulDiv(
            rewardPerLiquidityDelta,
            usersLiquidity,
            type(uint128).max
        );
    }

    function _updateTokenBurnBIPS(uint256 newBIPS) internal {
        if (newBIPS > 1000) revert PercentageTooHigh();
        tokenBurnBIPS = newBIPS;
        emit BurnBIPSUpdated(newBIPS);
    }

    function _updateProtocolWallet(address newAddress) internal {
        protocolWallet = newAddress;
        emit ProtocolWalletUpdated(newAddress);
    }

    function _receivePOS32From(address to, uint256 amount) internal {
        IERC20(POS32).transferFrom(to, address(this), amount);
    }

    function _sendRethTo(
        address rocketTokenRETHAddress,
        address to,
        uint256 amount
    ) internal {
        if(amount != 0) {
            IERC20(rocketTokenRETHAddress).transfer(to, amount);
        }
    }

    function _sendPOS32To(address to, uint256 amount) internal {
        IERC20(POS32).transfer(to, amount);
    }

    function _burnPOS32(uint256 amount) internal {
        IERC20(POS32).transfer(DEAD_ADDRESS, amount);
    }

    function _sendETH(address to, uint256 amount) internal {
        if (amount != 0) {
            bool success;
            /// @solidity memory-safe-assembly
            assembly {
                // Transfer the ETH and store if it succeeded or not.
                success := call(gas(), to, amount, 0, 0, 0, 0)
            }
            require(success, "ETH_TRANSFER_FAILED");
        }
    }
}