// File: src/Staking.sol



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

import {ReentrancyGuard} from "solmate/utils/ReentrancyGuard.sol";
import {IDepositContract} from "src/interfaces/IDepositContract.sol";
import {IStaking} from "src/interfaces/IStaking.sol";
import {Owned} from "src/lib/Owned.sol";
import {Pausable} from "src/lib/Pausable.sol";
import {SafeTransferLib} from "src/lib/SafeTransferLib.sol";
import {FeeRecipient} from "src/FeeRecipient.sol";
import {StakingConstants} from "src/StakingConstants.sol";

contract Staking is IStaking, ReentrancyGuard, Owned, Pausable, StakingConstants {
    /// @notice Ethereum staking deposit contract address.
    address public immutable depositContract;
    /// @notice stakewith.us treasury which receives share of profit from execution layer rewards.
    address public treasury;
    /// @notice One-time fee for creating a new validator.
    uint256 public oneTimeFee;
    /// @notice Performance fee percentage from execution layer rewards / `FEE_BASIS`, i.e. 10_000 represents 100%.
    uint256 public performanceFee;
    /// @notice Delay before a user can initiate a refund of pending unstaked ETH.
    uint256 public refundDelay;
    /// @notice Total number pending unstaked deposits across all users.
    uint256 public totalPendingValidators;

    /// @notice Mapping of users to FeeRecipient contracts which users collect their execution layer rewards from.
    mapping(address => address) public registry;
    /// @notice Mapping of users to number of pending unstaked deposits for that user.
    mapping(address => uint256) public pendingValidators;
    /// @notice Mapping of users to timestamp of their last deposit.
    mapping(address => uint256) public lastDepositTimestamp;

    uint256 internal constant _DEPOSIT_AMOUNT = 32 ether;
    uint256 internal constant _MAXIMUM_REFUND_DELAY = 7 days;

    error InvalidAmount();
    error InvalidLength();
    error PendingValidators();
    error NoDeposit();
    error BeforeRefundDelay();
    error SameValue();

    constructor(
        address owner_,
        address operator_,
        address depositContract_,
        address treasury_,
        uint256 oneTimeFee_,
        uint256 performanceFee_,
        uint256 refundDelay_
    ) Owned(owner_, operator_) {
        if (depositContract_ == address(0)) revert ZeroAddress();
        depositContract = depositContract_;
        _setTreasury(treasury_);
        _setOneTimeFee(oneTimeFee_);
        _setPerformanceFee(performanceFee_);
        _setRefundDelay(refundDelay_);
    }

    /// @dev Costs less gas than `deposit()` if user if depositing for their own address.
    receive() external payable {
        _deposit(msg.sender);
    }

    /*//////////////////////////////////////
	            PUBLIC FUNCTIONS
	//////////////////////////////////////*/

    /**
     * @notice Deposits ETH into this contract for stakewith.us to create a new validator node on user's behalf.
     * @param user_ User's withdrawal address which receives consensus rewards and can claim execution layer rewards.
     * @dev `msg.value` must be a multiple of `_DEPOSIT_AMOUNT (32 ether) + oneTimeFee`
     */
    function deposit(address user_) external payable {
        _deposit(user_);
    }

    function _deposit(address user_) internal whenNotPaused nonReentrant {
        if (user_ == address(0)) revert ZeroAddress();
        if (pendingValidators[user_] > 0) revert PendingValidators();

        uint256 perValidator = _DEPOSIT_AMOUNT + oneTimeFee;
        if (msg.value == 0 || msg.value % perValidator != 0) revert InvalidAmount();

        // Deploy FeeRecipient for address if its their first deposit.
        if (registry[user_] == address(0)) {
            address feeRecipient = address(new FeeRecipient(user_));
            registry[user_] = feeRecipient;
            emit UserRegistered(user_, feeRecipient);
        }

        uint256 validators = msg.value / perValidator;

        pendingValidators[user_] += validators;
        totalPendingValidators += validators;
        lastDepositTimestamp[user_] = block.timestamp;

        emit Deposit(msg.sender, user_, validators);
    }

    /**
     * @notice Refunds unstaked ETH to user. User must wait for at least `refundDelay` after depositing before
     * initiating a refund.
     */
    function refund() external nonReentrant {
        uint256 validators = pendingValidators[msg.sender];
        if (block.timestamp < lastDepositTimestamp[msg.sender] + refundDelay) revert BeforeRefundDelay();

        _refund(msg.sender, validators);
    }

    /*////////////////////////////////////////
	            OPERATOR FUNCTIONS
	////////////////////////////////////////*/

    function stake(address user_, DepositData[] memory data_) external onlyOperator {
        uint256 length = data_.length;
        if (length == 0) revert InvalidLength();

        // This underflows, throwing an error if length > pendingValidators[user_]
        pendingValidators[user_] -= length;
        totalPendingValidators -= length;

        if (oneTimeFee > 0) SafeTransferLib.safeTransferETH(treasury, length * oneTimeFee);

        bytes[] memory pubkeys = new bytes[](length);

        for (uint256 i = 0; i < length; ++i) {
            bytes memory pubkey = data_[i].pubkey;

            IDepositContract(depositContract).deposit{value: _DEPOSIT_AMOUNT}({
                pubkey: pubkey,
                withdrawal_credentials: abi.encodePacked(true, uint88(0), user_), // true | 11 bytes padding | address
                signature: data_[i].signature,
                deposit_data_root: data_[i].deposit_data_root
            });

            pubkeys[i] = pubkey;
        }

        emit Staked(user_, pubkeys);
    }

    function refundUser(address user_, uint256 validators_) external onlyOperator {
        _refund(user_, validators_);
    }

    function pause() external onlyOperator {
        _pause();
    }

    function unpause() external onlyOperator {
        _unpause();
    }

    /*/////////////////////////////////////
	            OWNER FUNCTIONS
	/////////////////////////////////////*/

    function setOneTimeFee(uint256 oneTimeFee_) external onlyOwner {
        if (oneTimeFee_ == oneTimeFee) revert SameValue();
        _setOneTimeFee(oneTimeFee_);
    }

    function setPerformanceFee(uint256 performanceFee_) external onlyOwner {
        if (performanceFee_ == performanceFee) revert SameValue();
        _setPerformanceFee(performanceFee_);
    }

    function setTreasury(address treasury_) external onlyOwner {
        if (treasury_ == address(0)) revert ZeroAddress();
        if (treasury_ == treasury) revert SameValue();
        _setTreasury(treasury_);
    }

    function setRefundDelay(uint256 refundDelay_) external onlyOwner {
        if (refundDelay_ == refundDelay) revert SameValue();
        _setRefundDelay(refundDelay_);
    }

    /*////////////////////////////////////////
	            INTERNAL FUNCTIONS
	////////////////////////////////////////*/

    function _refund(address user_, uint256 validators_) internal {
        if (validators_ == 0) revert NoDeposit();

        // This underflows, throwing an error if validators_ > pendingValidators[user_]
        pendingValidators[user_] -= validators_;
        totalPendingValidators -= validators_;

        SafeTransferLib.safeTransferETH(user_, validators_ * (_DEPOSIT_AMOUNT + oneTimeFee));
        emit Refund(user_, validators_);
    }

    /// @dev One-time fee cannot be adjusted while there are still pending validators. Pause contract and stake/refund
    /// all pending validators before changing one-time fee.
    function _setOneTimeFee(uint256 oneTimeFee_) internal {
        if (totalPendingValidators != 0) revert PendingValidators();
        oneTimeFee = oneTimeFee_;
        emit OneTimeFeeSet(oneTimeFee_);
    }

    function _setPerformanceFee(uint256 performanceFee_) internal {
        if (performanceFee_ > FEE_BASIS) revert InvalidAmount();
        performanceFee = performanceFee_;
        emit PerformanceFeeSet(performanceFee_);
    }

    function _setTreasury(address treasury_) internal {
        emit NewTreasury(treasury, treasury_);
        treasury = treasury_;
    }

    function _setRefundDelay(uint256 refundDelay_) internal {
        if (refundDelay_ > _MAXIMUM_REFUND_DELAY) revert InvalidAmount();
        refundDelay = refundDelay_;
        emit RefundDelaySet(refundDelay_);
    }
}

// File: lib/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/transmissions11/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: src/interfaces/IDepositContract.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.13;

interface IDepositContract {
    function deposit(
        bytes calldata pubkey,
        bytes calldata withdrawal_credentials,
        bytes calldata signature,
        bytes32 deposit_data_root
    ) external payable;
}

// File: src/interfaces/IStaking.sol



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

interface IStaking {
    struct DepositData {
        bytes pubkey;
        bytes signature;
        bytes32 deposit_data_root;
    }

    event UserRegistered(address indexed user, address indexed feeRecipient);
    event Deposit(address indexed from, address indexed user, uint256 validators);
    event Staked(address indexed user, bytes[] pubkeys);
    event Refund(address indexed user, uint256 validators);
    event OneTimeFeeSet(uint256);
    event PerformanceFeeSet(uint256);
    event NewTreasury(address indexed oldTreasury, address indexed newTreasury);
    event RefundDelaySet(uint256);

    function depositContract() external view returns (address);

    function treasury() external view returns (address);

    function oneTimeFee() external view returns (uint256);

    function performanceFee() external view returns (uint256);

    function registry(address) external view returns (address);

    function pendingValidators(address) external view returns (uint256);

    function deposit(address user) external payable;
}

// File: src/lib/Owned.sol



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

/**
 * @notice Auth contract with two levels of access - `owner` can access both `onlyOwner` and `onlyOperator` functions,
 *  while `operator` can only access `onlyOperator` functions.
 */
abstract contract Owned {
    address public owner;
    address public nominatedOwner;
    address public operator;

    event NewOwner(address indexed oldOwner, address indexed newOwner);
    event NominatedOwner(address indexed nominatedOwner);
    event NewOperator(address indexed oldOperator, address indexed newOperator);

    error Unauthorized();
    error ZeroAddress();

    modifier onlyOwner() virtual {
        if (msg.sender != owner) revert Unauthorized();
        _;
    }

    modifier onlyNominatedOwner() virtual {
        if (msg.sender != nominatedOwner) revert Unauthorized();
        _;
    }

    modifier onlyOperator() virtual {
        if (msg.sender != owner && msg.sender != operator) revert Unauthorized();
        _;
    }

    constructor(address owner_, address operator_) {
        if (owner_ == address(0)) revert ZeroAddress();
        if (operator_ == address(0)) revert ZeroAddress();
        emit NewOwner(address(0), owner_);
        emit NewOperator(address(0), operator_);
        owner = owner_;
        operator = operator_;
    }

    function setOperator(address operator_) public virtual onlyOwner {
        if (operator_ == address(0)) revert ZeroAddress();
        emit NewOperator(operator, operator_);
        operator = operator_;
    }

    function nominateOwner(address nominatedOwner_) public virtual onlyOwner {
        emit NominatedOwner(nominatedOwner_);
        nominatedOwner = nominatedOwner_;
    }

    function acceptOwnership() public virtual onlyNominatedOwner {
        emit NewOwner(owner, nominatedOwner);
        owner = nominatedOwner;
        nominatedOwner = address(0);
    }
}

// File: src/lib/Pausable.sol



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

abstract contract Pausable {
    bool public paused;

    event Paused(address indexed sender);
    event Unpaused(address indexed sender);

    error IsPaused();
    error NotPaused();

    function _pause() internal whenNotPaused {
        paused = true;
        emit Paused(msg.sender);
    }

    function _unpause() internal whenPaused {
        paused = false;
        emit Unpaused(msg.sender);
    }

    modifier whenNotPaused() {
        if (paused) revert IsPaused();
        _;
    }

    modifier whenPaused() {
        if (!paused) revert NotPaused();
        _;
    }
}

// File: src/lib/SafeTransferLib.sol



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

/// @notice Gas-efficient safe ETH transfer library that gracefully handles missing return values.
/// @author Copied from Solmate with ERC20 code removed.
/// @author https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol
library SafeTransferLib {
    /*//////////////////////////////////////////////////////////////
                             ETH OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function safeTransferETH(address to, uint256 amount) internal {
        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");
    }
}

// File: src/FeeRecipient.sol



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

import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol";
import {IStaking} from "src/interfaces/IStaking.sol";
import {SafeTransferLib} from "src/lib/SafeTransferLib.sol";
import {StakingConstants} from "src/StakingConstants.sol";

/**
 * @notice Contract set as the `fee_recipient` for all validators belonging to a user. Receives execution layer rewards
 * from block production gas tips and MEV bribes which users can claim via `claimRewards()` function.
 */
contract FeeRecipient is StakingConstants {
    using FixedPointMathLib for uint256;

    IStaking public immutable staking;
    address public immutable user;

    /// @dev Unclaimed rewards that fully belong to user.
    uint256 internal _userRewards;

    event ClaimRewards(uint256 amount);
    event TreasuryClaim(uint256 amount);

    error Unauthorized();
    error NothingToClaim();

    constructor(address user_) {
        staking = IStaking(payable(msg.sender));
        user = user_;
    }

    /// @dev To receive MEV bribes directly transferred to `fee_recipient`.
    receive() external payable {}

    function unclaimedRewards() public view returns (uint256) {
        return address(this).balance - _calcToTreasury(address(this).balance - _userRewards);
    }

    function claimRewards() external onlyUser {
        if (unclaimedRewards() == 0) revert NothingToClaim();

        _treasuryClaim();

        emit ClaimRewards(address(this).balance);
        _userRewards = 0;

        SafeTransferLib.safeTransferETH(user, address(this).balance);
    }

    function treasuryClaim() external onlyTreasury {
        if (_calcToTreasury(address(this).balance - _userRewards) == 0) revert NothingToClaim();
        _treasuryClaim();
    }

    function _treasuryClaim() internal {
        uint256 share = address(this).balance - _userRewards;
        uint256 toTreasury = _calcToTreasury(share);
        if (toTreasury == 0) return; // Do nothing as treasury has nothing to claim.

        emit TreasuryClaim(toTreasury);
        _userRewards += share - toTreasury;

        SafeTransferLib.safeTransferETH(staking.treasury(), toTreasury);
    }

    function _calcToTreasury(uint256 amount_) internal view returns (uint256) {
        return amount_.mulDivDown(staking.performanceFee(), FEE_BASIS);
    }

    modifier onlyUser() {
        if (msg.sender != user) revert Unauthorized();
        _;
    }

    modifier onlyTreasury() {
        if (msg.sender != staking.treasury()) revert Unauthorized();
        _;
    }
}

// File: src/StakingConstants.sol



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

/// @notice Contract for shared values between Staking.sol and FeeRecipient.sol.
abstract contract StakingConstants {
    /// @notice Denominator for calculating performance fees, i.e. a `performanceFee` of 10_000 represents 100%.
    uint256 public constant FEE_BASIS = 10_000;
}

// File: lib/solmate/src/utils/FixedPointMathLib.sol



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

/// @notice Arithmetic library with operations for fixed-point numbers.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)
/// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
library FixedPointMathLib {
    /*//////////////////////////////////////////////////////////////
                    SIMPLIFIED FIXED POINT OPERATIONS
    //////////////////////////////////////////////////////////////*/

    uint256 internal constant MAX_UINT256 = 2**256 - 1;

    uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s.

    function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.
    }

    function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up.
    }

    function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down.
    }

    function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up.
    }

    /*//////////////////////////////////////////////////////////////
                    LOW LEVEL FIXED POINT OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function mulDivDown(
        uint256 x,
        uint256 y,
        uint256 denominator
    ) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
            if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
                revert(0, 0)
            }

            // Divide x * y by the denominator.
            z := div(mul(x, y), denominator)
        }
    }

    function mulDivUp(
        uint256 x,
        uint256 y,
        uint256 denominator
    ) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
            if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
                revert(0, 0)
            }

            // If x * y modulo the denominator is strictly greater than 0,
            // 1 is added to round up the division of x * y by the denominator.
            z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator))
        }
    }

    function rpow(
        uint256 x,
        uint256 n,
        uint256 scalar
    ) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            switch x
            case 0 {
                switch n
                case 0 {
                    // 0 ** 0 = 1
                    z := scalar
                }
                default {
                    // 0 ** n = 0
                    z := 0
                }
            }
            default {
                switch mod(n, 2)
                case 0 {
                    // If n is even, store scalar in z for now.
                    z := scalar
                }
                default {
                    // If n is odd, store x in z for now.
                    z := x
                }

                // Shifting right by 1 is like dividing by 2.
                let half := shr(1, scalar)

                for {
                    // Shift n right by 1 before looping to halve it.
                    n := shr(1, n)
                } n {
                    // Shift n right by 1 each iteration to halve it.
                    n := shr(1, n)
                } {
                    // Revert immediately if x ** 2 would overflow.
                    // Equivalent to iszero(eq(div(xx, x), x)) here.
                    if shr(128, x) {
                        revert(0, 0)
                    }

                    // Store x squared.
                    let xx := mul(x, x)

                    // Round to the nearest number.
                    let xxRound := add(xx, half)

                    // Revert if xx + half overflowed.
                    if lt(xxRound, xx) {
                        revert(0, 0)
                    }

                    // Set x to scaled xxRound.
                    x := div(xxRound, scalar)

                    // If n is even:
                    if mod(n, 2) {
                        // Compute z * x.
                        let zx := mul(z, x)

                        // If z * x overflowed:
                        if iszero(eq(div(zx, x), z)) {
                            // Revert if x is non-zero.
                            if iszero(iszero(x)) {
                                revert(0, 0)
                            }
                        }

                        // Round to the nearest number.
                        let zxRound := add(zx, half)

                        // Revert if zx + half overflowed.
                        if lt(zxRound, zx) {
                            revert(0, 0)
                        }

                        // Return properly scaled zxRound.
                        z := div(zxRound, scalar)
                    }
                }
            }
        }
    }

    /*//////////////////////////////////////////////////////////////
                        GENERAL NUMBER UTILITIES
    //////////////////////////////////////////////////////////////*/

    function sqrt(uint256 x) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            let y := x // We start y at x, which will help us make our initial estimate.

            z := 181 // The "correct" value is 1, but this saves a multiplication later.

            // This segment is to get a reasonable initial estimate for the Babylonian method. With a bad
            // start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.

            // We check y >= 2^(k + 8) but shift right by k bits
            // each branch to ensure that if x >= 256, then y >= 256.
            if iszero(lt(y, 0x10000000000000000000000000000000000)) {
                y := shr(128, y)
                z := shl(64, z)
            }
            if iszero(lt(y, 0x1000000000000000000)) {
                y := shr(64, y)
                z := shl(32, z)
            }
            if iszero(lt(y, 0x10000000000)) {
                y := shr(32, y)
                z := shl(16, z)
            }
            if iszero(lt(y, 0x1000000)) {
                y := shr(16, y)
                z := shl(8, z)
            }

            // Goal was to get z*z*y within a small factor of x. More iterations could
            // get y in a tighter range. Currently, we will have y in [256, 256*2^16).
            // We ensured y >= 256 so that the relative difference between y and y+1 is small.
            // That's not possible if x < 256 but we can just verify those cases exhaustively.

            // Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256.
            // Correctness can be checked exhaustively for x < 256, so we assume y >= 256.
            // Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps.

            // For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range
            // (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256.

            // Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate
            // sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18.

            // There is no overflow risk here since y < 2^136 after the first branch above.
            z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181.

            // Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))

            // If x+1 is a perfect square, the Babylonian method cycles between
            // floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor.
            // See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division
            // Since the ceil is rare, we save gas on the assignment and repeat division in the rare case.
            // If you don't care whether the floor or ceil square root is returned, you can remove this statement.
            z := sub(z, lt(div(x, z), z))
        }
    }

    function unsafeMod(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Mod x by y. Note this will return
            // 0 instead of reverting if y is zero.
            z := mod(x, y)
        }
    }

    function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 r) {
        /// @solidity memory-safe-assembly
        assembly {
            // Divide x by y. Note this will return
            // 0 instead of reverting if y is zero.
            r := div(x, y)
        }
    }

    function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Add 1 to x * y if x % y > 0. Note this will
            // return 0 instead of reverting if y is zero.
            z := add(gt(mod(x, y), 0), div(x, y))
        }
    }
}