L1RewardManager

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/L1RewardManager.sol": {
      "content": "// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0 <0.9.0;

import { AccessManagedUpgradeable } from
    "@openzeppelin/contracts-upgradeable/access/manager/AccessManagedUpgradeable.sol";
import { IL1RewardManager } from "./interface/IL1RewardManager.sol";
import { PufferVaultV5 } from "./PufferVaultV5.sol";
import { UUPSUpgradeable } from "@openzeppelin-contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import { Unauthorized } from "mainnet-contracts/src/Errors.sol";
import { L1RewardManagerStorage } from "./L1RewardManagerStorage.sol";
import { L2RewardManagerStorage } from "l2-contracts/src/L2RewardManagerStorage.sol";
import { IOFT } from "./interface/LayerZero/IOFT.sol";
import { IOAppComposer } from "@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppComposer.sol";
import { OFTComposeMsgCodec } from "@layerzerolabs/oft-evm/contracts/libs/OFTComposeMsgCodec.sol";
import { OptionsBuilder } from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OptionsBuilder.sol";

/**
 * @title L1RewardManager
 * @author Puffer Finance
 * @custom:security-contact security@puffer.fi
 */
contract L1RewardManager is
    IL1RewardManager,
    L1RewardManagerStorage,
    AccessManagedUpgradeable,
    UUPSUpgradeable,
    IOAppComposer
{
    using OptionsBuilder for bytes;

    /**
     * @notice The PufferVault contract on Ethereum Mainnet
     */
    PufferVaultV5 public immutable PUFFER_VAULT;

    /**
     * @notice The pufETH OFT address for singleton design
     * @dev Immutable since it is known at deployment time on L1 and cannot be changed afterwards
     */
    IOFT public immutable PUFETH_OFT;

    /**
     * @notice The Rewards Manager contract on L2
     */
    address public immutable L2_REWARDS_MANAGER;

    constructor(address pufETH, address l2RewardsManager, address pufETH_OFT) {
        if (pufETH == address(0) || l2RewardsManager == address(0) || pufETH_OFT == address(0)) {
            revert InvalidAddress();
        }
        PUFFER_VAULT = PufferVaultV5(payable(pufETH));
        PUFETH_OFT = IOFT(payable(pufETH_OFT));
        L2_REWARDS_MANAGER = l2RewardsManager;
        _disableInitializers();
    }

    function initialize(address accessManager) external initializer {
        __AccessManaged_init(accessManager);
        _setAllowedRewardMintFrequency(20 hours);
    }

    /**
     * @inheritdoc IL1RewardManager
     */
    function setL2RewardClaimer(address claimer) external payable {
        RewardManagerStorage storage $ = _getRewardManagerStorage();

        bytes memory options =
            OptionsBuilder.newOptions().addExecutorLzReceiveOption(50000, 0).addExecutorLzComposeOption(0, 50000, 0);

        PUFETH_OFT.send{ value: msg.value }(
            IOFT.SendParam({
                dstEid: $.destinationEID,
                to: bytes32(uint256(uint160(L2_REWARDS_MANAGER))),
                amountLD: 0,
                minAmountLD: 0,
                extraOptions: options,
                composeMsg: abi.encode(
                    BridgingParams({
                        bridgingType: BridgingType.SetClaimer,
                        data: abi.encode(SetClaimerParams({ account: msg.sender, claimer: claimer }))
                    })
                ),
                oftCmd: bytes("")
            }),
            IOFT.MessagingFee({ nativeFee: msg.value, lzTokenFee: 0 }),
            msg.sender // refundAddress
        );
        emit L2RewardClaimerUpdated(msg.sender, claimer);
    }

    /**
     * @notice Mints pufETH, locks into the pufETHAdapter and bridges it to the L2RewardManager contract on L2 according to the provided parameters.
     * @dev Restricted access to `ROLE_ID_OPERATIONS_PAYMASTER`
     *
     * The oft must be allowlisted in the contract and the amount must be less than the allowed mint amount.
     * The minting can be done at most once per allowed frequency.
     *
     * This action can be reverted by the L2RewardManager contract on L2.
     * The L2RewardManager can revert this action by bridging back the assets to this contract (see lzCompose).
     */
    function mintAndBridgeRewards(MintAndBridgeParams calldata params) external payable restricted {
        RewardManagerStorage storage $ = _getRewardManagerStorage();

        if (params.rewardsRoot == bytes32(0)) {
            revert InvalidRewardsRoot();
        }

        if (params.rewardsAmount > $.allowedRewardMintAmount) {
            revert InvalidMintAmount();
        }

        if (($.lastRewardMintTimestamp + $.allowedRewardMintFrequency) > block.timestamp) {
            revert NotAllowedMintFrequency();
        }

        // Update lastIntervalEndEpoch to the previous currentIntervalEndEpoch
        $.lastIntervalEndEpoch = $.currentIntervalEndEpoch;

        // Check that startEpoch is greater than the last processed end epoch
        if (params.startEpoch <= $.lastIntervalEndEpoch) {
            revert InvalidStartEpoch();
        }

        // Update current interval end epoch
        $.currentIntervalEndEpoch = params.endEpoch;

        // Update the last mint timestamp
        $.lastRewardMintTimestamp = uint48(block.timestamp);

        // Mint the rewards and lock them into the pufETHAdapter to be bridged to L2
        (uint256 ethToPufETHRate, uint256 shares) = PUFFER_VAULT.mintRewards(params.rewardsAmount);

        PUFFER_VAULT.approve(address(PUFETH_OFT), shares);

        MintAndBridgeData memory bridgingCalldata = MintAndBridgeData({
            rewardsAmount: params.rewardsAmount,
            ethToPufETHRate: ethToPufETHRate,
            startEpoch: params.startEpoch,
            endEpoch: params.endEpoch,
            rewardsRoot: params.rewardsRoot,
            rewardsURI: params.rewardsURI
        });

        bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(50000, 0) // Gas for lzReceive
            .addExecutorLzComposeOption(0, 50000, 0); // Gas for lzCompose

        PUFETH_OFT.send{ value: msg.value }(
            IOFT.SendParam({
                dstEid: $.destinationEID,
                to: bytes32(uint256(uint160(L2_REWARDS_MANAGER))),
                amountLD: shares,
                minAmountLD: 0,
                extraOptions: options,
                composeMsg: abi.encode(
                    BridgingParams({ bridgingType: BridgingType.MintAndBridge, data: abi.encode(bridgingCalldata) })
                ),
                oftCmd: bytes("")
            }),
            IOFT.MessagingFee({ nativeFee: msg.value, lzTokenFee: 0 }),
            msg.sender // refundAddress
        );

        emit MintedAndBridgedRewards({
            rewardsAmount: params.rewardsAmount,
            startEpoch: params.startEpoch,
            endEpoch: params.endEpoch,
            rewardsRoot: params.rewardsRoot,
            ethToPufETHRate: ethToPufETHRate,
            rewardsURI: params.rewardsURI
        });
    }

    /**
     * @notice Handles incoming composed messages from LayerZero Endpoint on L1
     * @notice Revert the original mintAndBridge call
     * @dev Ensures the message comes from the correct OApp and is sent through the authorized endpoint.
     * @dev Restricted to the LayerZero Endpoint contract on L1
     *
     * @param oft The address of the pufETH OFTAdapter that is sending the composed message.
     * @param message The calldata received from L2RewardManager.
     */
    function lzCompose(
        address oft,
        bytes32, /* _guid */
        bytes calldata message,
        address, /* _executor */
        bytes calldata /* _extraData */
    ) external payable override restricted {
        // Ensure that only the whitelisted pufETH OFT can call this function
        if (oft != address(PUFETH_OFT)) {
            revert Unauthorized();
        }

        // Decode the OFT compose message to extract the original sender and validate authenticity
        bytes32 composeFrom = OFTComposeMsgCodec.composeFrom(message);
        bytes memory actualMessage = OFTComposeMsgCodec.composeMsg(message);

        // Validate that the original sender is our legitimate L2RewardManager
        address originalSender = address(uint160(uint256(composeFrom)));
        if (originalSender != L2_REWARDS_MANAGER) {
            revert Unauthorized();
        }

        // We decode the actual message to get the amount of shares(pufETH) and the ETH amount.
        L2RewardManagerStorage.EpochRecord memory epochRecord =
            abi.decode(actualMessage, (L2RewardManagerStorage.EpochRecord));

        // This contract has already received the pufETH from pufETHAdapter after bridging back to L1
        // The PufferVault will burn the pufETH from this contract and subtract the ETH amount from the ethRewardsAmount
        PUFFER_VAULT.revertMintRewards({ pufETHAmount: epochRecord.pufETHAmount, ethAmount: epochRecord.ethAmount });

        // When reverted, set current end epoch back to last end epoch
        RewardManagerStorage storage $ = _getRewardManagerStorage();
        $.currentIntervalEndEpoch = $.lastIntervalEndEpoch;

        // We emit the event to the L1RewardManager contract
        emit RevertedRewards({
            rewardsAmount: epochRecord.ethAmount,
            startEpoch: epochRecord.startEpoch,
            endEpoch: epochRecord.endEpoch,
            rewardsRoot: epochRecord.rewardRoot
        });
    }
    /**
     * @notice Sets the allowed reward mint amount.
     * @param newAmount The new allowed reward mint amount.
     * @dev Restricted access to `ROLE_ID_DAO`
     */

    function setAllowedRewardMintAmount(uint104 newAmount) external restricted {
        RewardManagerStorage storage $ = _getRewardManagerStorage();

        emit AllowedRewardMintAmountUpdated($.allowedRewardMintAmount, newAmount);

        $.allowedRewardMintAmount = newAmount;
    }

    /**
     * @notice Sets the allowed reward mint frequency.
     * @param newFrequency The new allowed reward mint frequency.
     * @dev Restricted access to `ROLE_ID_DAO`
     */
    function setAllowedRewardMintFrequency(uint104 newFrequency) external restricted {
        _setAllowedRewardMintFrequency(newFrequency);
    }

    /**
     * @notice Sets the destination endpoint ID
     * @param newDestinationEID The new destination endpoint ID
     */
    function setDestinationEID(uint32 newDestinationEID) external restricted {
        RewardManagerStorage storage $ = _getRewardManagerStorage();
        emit DestinationEIDUpdated({ oldDestinationEID: $.destinationEID, newDestinationEID: newDestinationEID });
        $.destinationEID = newDestinationEID;
    }

    /**
     * @notice Returns the destination endpoint ID
     */
    function getDestinationEID() external view returns (uint32) {
        RewardManagerStorage storage $ = _getRewardManagerStorage();
        return $.destinationEID;
    }

    /**
     * @notice Returns the last successfully processed interval end epoch
     */
    function getLastIntervalEndEpoch() external view returns (uint256) {
        RewardManagerStorage storage $ = _getRewardManagerStorage();
        return $.lastIntervalEndEpoch;
    }

    /**
     * @notice Returns the current interval end epoch being processed
     */
    function getCurrentIntervalEndEpoch() external view returns (uint256) {
        RewardManagerStorage storage $ = _getRewardManagerStorage();
        return $.currentIntervalEndEpoch;
    }

    function _setAllowedRewardMintFrequency(uint104 newFrequency) internal {
        if (newFrequency < 20 hours) {
            revert InvalidMintFrequency();
        }
        RewardManagerStorage storage $ = _getRewardManagerStorage();

        emit AllowedRewardMintFrequencyUpdated($.allowedRewardMintFrequency, newFrequency);

        $.allowedRewardMintFrequency = newFrequency;
    }

    function _authorizeUpgrade(address newImplementation) internal virtual override restricted { }
}
"
    },
    "node_modules/@openzeppelin/contracts-upgradeable/access/manager/AccessManagedUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/manager/AccessManaged.sol)

pragma solidity ^0.8.20;

import {IAuthority} from "@openzeppelin/contracts/access/manager/IAuthority.sol";
import {AuthorityUtils} from "@openzeppelin/contracts/access/manager/AuthorityUtils.sol";
import {IAccessManager} from "@openzeppelin/contracts/access/manager/IAccessManager.sol";
import {IAccessManaged} from "@openzeppelin/contracts/access/manager/IAccessManaged.sol";
import {ContextUpgradeable} from "../../utils/ContextUpgradeable.sol";
import {Initializable} from "../../proxy/utils/Initializable.sol";

/**
 * @dev This contract module makes available a {restricted} modifier. Functions decorated with this modifier will be
 * permissioned according to an "authority": a contract like {AccessManager} that follows the {IAuthority} interface,
 * implementing a policy that allows certain callers to access certain functions.
 *
 * IMPORTANT: The `restricted` modifier should never be used on `internal` functions, judiciously used in `public`
 * functions, and ideally only used in `external` functions. See {restricted}.
 */
abstract contract AccessManagedUpgradeable is Initializable, ContextUpgradeable, IAccessManaged {
    /// @custom:storage-location erc7201:openzeppelin.storage.AccessManaged
    struct AccessManagedStorage {
        address _authority;

        bool _consumingSchedule;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.AccessManaged")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant AccessManagedStorageLocation = 0xf3177357ab46d8af007ab3fdb9af81da189e1068fefdc0073dca88a2cab40a00;

    function _getAccessManagedStorage() private pure returns (AccessManagedStorage storage $) {
        assembly {
            $.slot := AccessManagedStorageLocation
        }
    }

    /**
     * @dev Initializes the contract connected to an initial authority.
     */
    function __AccessManaged_init(address initialAuthority) internal onlyInitializing {
        __AccessManaged_init_unchained(initialAuthority);
    }

    function __AccessManaged_init_unchained(address initialAuthority) internal onlyInitializing {
        _setAuthority(initialAuthority);
    }

    /**
     * @dev Restricts access to a function as defined by the connected Authority for this contract and the
     * caller and selector of the function that entered the contract.
     *
     * [IMPORTANT]
     * ====
     * In general, this modifier should only be used on `external` functions. It is okay to use it on `public`
     * functions that are used as external entry points and are not called internally. Unless you know what you're
     * doing, it should never be used on `internal` functions. Failure to follow these rules can have critical security
     * implications! This is because the permissions are determined by the function that entered the contract, i.e. the
     * function at the bottom of the call stack, and not the function where the modifier is visible in the source code.
     * ====
     *
     * [WARNING]
     * ====
     * Avoid adding this modifier to the https://docs.soliditylang.org/en/v0.8.20/contracts.html#receive-ether-function[`receive()`]
     * function or the https://docs.soliditylang.org/en/v0.8.20/contracts.html#fallback-function[`fallback()`]. These
     * functions are the only execution paths where a function selector cannot be unambiguosly determined from the calldata
     * since the selector defaults to `0x00000000` in the `receive()` function and similarly in the `fallback()` function
     * if no calldata is provided. (See {_checkCanCall}).
     *
     * The `receive()` function will always panic whereas the `fallback()` may panic depending on the calldata length.
     * ====
     */
    modifier restricted() {
        _checkCanCall(_msgSender(), _msgData());
        _;
    }

    /// @inheritdoc IAccessManaged
    function authority() public view virtual returns (address) {
        AccessManagedStorage storage $ = _getAccessManagedStorage();
        return $._authority;
    }

    /// @inheritdoc IAccessManaged
    function setAuthority(address newAuthority) public virtual {
        address caller = _msgSender();
        if (caller != authority()) {
            revert AccessManagedUnauthorized(caller);
        }
        if (newAuthority.code.length == 0) {
            revert AccessManagedInvalidAuthority(newAuthority);
        }
        _setAuthority(newAuthority);
    }

    /// @inheritdoc IAccessManaged
    function isConsumingScheduledOp() public view returns (bytes4) {
        AccessManagedStorage storage $ = _getAccessManagedStorage();
        return $._consumingSchedule ? this.isConsumingScheduledOp.selector : bytes4(0);
    }

    /**
     * @dev Transfers control to a new authority. Internal function with no access restriction. Allows bypassing the
     * permissions set by the current authority.
     */
    function _setAuthority(address newAuthority) internal virtual {
        AccessManagedStorage storage $ = _getAccessManagedStorage();
        $._authority = newAuthority;
        emit AuthorityUpdated(newAuthority);
    }

    /**
     * @dev Reverts if the caller is not allowed to call the function identified by a selector. Panics if the calldata
     * is less than 4 bytes long.
     */
    function _checkCanCall(address caller, bytes calldata data) internal virtual {
        AccessManagedStorage storage $ = _getAccessManagedStorage();
        (bool immediate, uint32 delay) = AuthorityUtils.canCallWithDelay(
            authority(),
            caller,
            address(this),
            bytes4(data[0:4])
        );
        if (!immediate) {
            if (delay > 0) {
                $._consumingSchedule = true;
                IAccessManager(authority()).consumeScheduledOp(caller, data);
                $._consumingSchedule = false;
            } else {
                revert AccessManagedUnauthorized(caller);
            }
        }
    }
}
"
    },
    "src/interface/IL1RewardManager.sol": {
      "content": "// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0 <0.9.0;

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

/**
 * @title IL1RewardManager interface
 * @author Puffer Finance
 * @custom:security-contact security@puffer.fi
 */
interface IL1RewardManager {
    /**
     * @notice Sets the rewards claimer on L2.
     * Smart contracts might not be able to to own the same address on L2. This function allows to set a different address as the claimer.
     * msg.value is used to pay for the relayer fee on the destination chain.
     *
     * @param claimer The address of the new claimer.
     */
    function setL2RewardClaimer(address claimer) external payable;

    enum BridgingType {
        MintAndBridge,
        SetClaimer
    }

    /**
     * @notice Parameters for bridging actions.
     * @param bridgingType The type of bridging action.
     * @param data The data associated with the bridging action.
     */
    struct BridgingParams {
        IL1RewardManager.BridgingType bridgingType;
        bytes data;
    }

    /**
     * @notice Parameters for minting and bridging rewards.
     * @param rewardsAmount The amount of rewards to be bridged.
     * @param startEpoch The starting epoch for the rewards.
     * @param endEpoch The ending epoch for the rewards.
     * @param rewardsRoot The merkle root of the rewards.
     * @param rewardsURI The URI for the rewards metadata.
     */
    struct MintAndBridgeParams {
        uint256 rewardsAmount;
        uint256 startEpoch;
        uint256 endEpoch;
        bytes32 rewardsRoot;
        string rewardsURI;
    }

    /**
     * @notice Error indicating an invalid mint amount.
     */
    error InvalidMintAmount();

    /**
     * @notice Error indicating a disallowed mint frequency.
     */
    error InvalidMintFrequency();

    /**
     * @notice Error indicating a disallowed mint frequency.
     */
    error NotAllowedMintFrequency();

    /**
     * @notice Error indicating the bridge is not allowlisted.
     */
    error BridgeNotAllowlisted();

    /**
     * @notice Error indicating an invalid address.
     */
    error InvalidAddress();

    /**
     * @notice Error indicating an invalid rewards root.
     */
    error InvalidRewardsRoot();

    /**
     * @notice Error indicating that the start epoch is invalid (not greater than last processed end epoch).
     */
    error InvalidStartEpoch();

    /**
     * @notice Event emitted when rewards are minted and bridged.
     * @param rewardsAmount The amount of rewards minted and bridged.
     * @param startEpoch The starting epoch for the rewards.
     * @param endEpoch The ending epoch for the rewards.
     * @param rewardsRoot The merkle root of the rewards.
     * @param ethToPufETHRate The exchange rate from ETH to pufETH.
     * @param rewardsURI The URI for the rewards metadata.
     */
    event MintedAndBridgedRewards(
        uint256 rewardsAmount,
        uint256 startEpoch,
        uint256 endEpoch,
        bytes32 indexed rewardsRoot,
        uint256 ethToPufETHRate,
        string rewardsURI
    );

    /**
     * @param rewardsAmount The amount of rewards reverted.
     * @param startEpoch The starting epoch for the rewards.
     * @param endEpoch The ending epoch for the rewards.
     * @param rewardsRoot The merkle root of the rewards.
     */
    event RevertedRewards(uint256 rewardsAmount, uint256 startEpoch, uint256 endEpoch, bytes32 indexed rewardsRoot);

    /**
     * @notice Event emitted when the allowed reward mint amount is updated.
     * @param oldAmount The old allowed reward mint amount.
     * @param newAmount The new allowed reward mint amount.
     */
    event AllowedRewardMintAmountUpdated(uint256 oldAmount, uint256 newAmount);

    /**
     * @notice Event emitted when the allowed reward mint frequency is updated.
     * @param oldFrequency The old allowed reward mint frequency.
     * @param newFrequency The new allowed reward mint frequency.
     */
    event AllowedRewardMintFrequencyUpdated(uint256 oldFrequency, uint256 newFrequency);

    /**
     * @notice Event emitted when L2 reward claimer is updated.
     * @param account The address of the account.
     * @param claimer The address of the new claimer.
     */
    event L2RewardClaimerUpdated(address indexed account, address indexed claimer);

    /**
     * @notice Event emitted when the destination EID is updated
     * @param oldDestinationEID The old destination EID
     * @param newDestinationEID The new destination EID
     */
    event DestinationEIDUpdated(uint32 oldDestinationEID, uint32 newDestinationEID);
}
"
    },
    "src/PufferVaultV5.sol": {
      "content": "// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0 <0.9.0;

import { PufferVaultStorage } from "./PufferVaultStorage.sol";
import { IERC721Receiver } from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import { UUPSUpgradeable } from "@openzeppelin-contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import { AccessManagedUpgradeable } from
    "@openzeppelin-contracts-upgradeable/access/manager/AccessManagedUpgradeable.sol";
import { ERC4626Upgradeable } from "@openzeppelin-contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol";
import { ERC20Upgradeable } from "@openzeppelin-contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import { ERC20PermitUpgradeable } from
    "@openzeppelin-contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol";
import { IStETH } from "./interface/Lido/IStETH.sol";
import { ILidoWithdrawalQueue } from "./interface/Lido/ILidoWithdrawalQueue.sol";
import { IWETH } from "./interface/Other/IWETH.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
import { EnumerableMap } from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol";
import { IPufferVaultV5 } from "./interface/IPufferVaultV5.sol";
import { IPufferOracleV2 } from "./interface/IPufferOracleV2.sol";
import { IPufferRevenueDepositor } from "./interface/IPufferRevenueDepositor.sol";

/**
 * @title PufferVaultV5
 * @dev Implementation of the PufferVault version 5.
 * @custom:security-contact security@puffer.fi
 */
contract PufferVaultV5 is
    IPufferVaultV5,
    IERC721Receiver,
    PufferVaultStorage,
    AccessManagedUpgradeable,
    ERC20PermitUpgradeable,
    ERC4626Upgradeable,
    UUPSUpgradeable
{
    using SafeERC20 for address;
    using EnumerableMap for EnumerableMap.UintToUintMap;
    using Math for uint256;

    uint256 private constant _BASIS_POINT_SCALE = 1e4;
    IStETH internal immutable _ST_ETH;
    ILidoWithdrawalQueue internal immutable _LIDO_WITHDRAWAL_QUEUE;
    IWETH internal immutable _WETH;
    IPufferOracleV2 public immutable PUFFER_ORACLE;
    IPufferRevenueDepositor public immutable RESTAKING_REWARDS_DEPOSITOR;

    constructor(
        IStETH stETH,
        ILidoWithdrawalQueue lidoWithdrawalQueue,
        IWETH weth,
        IPufferOracleV2 pufferOracle,
        IPufferRevenueDepositor revenueDepositor
    ) {
        _ST_ETH = stETH;
        _LIDO_WITHDRAWAL_QUEUE = lidoWithdrawalQueue;
        _WETH = weth;
        PUFFER_ORACLE = pufferOracle;
        RESTAKING_REWARDS_DEPOSITOR = revenueDepositor;
        _disableInitializers();
    }

    /**
     * @notice Accept ETH from anywhere
     */
    receive() external payable virtual { }

    /**
     * @notice Initializes the PufferVaultV5 contract
     * @dev This function is only used for Unit Tests, we will not use it in mainnet
     */
    // nosemgrep tin-unprotected-initialize
    function initialize(address accessManager) public initializer {
        __AccessManaged_init(accessManager);
        __ERC20Permit_init("pufETH");
        __ERC4626_init(_WETH);
        __ERC20_init("pufETH", "pufETH");
        _setExitFeeBasisPoints(100); // 1%
    }

    modifier markDeposit() virtual {
        //solhint-disable-next-line no-inline-assembly
        assembly {
            tstore(_DEPOSIT_TRACKER_LOCATION, 1) // Store `1` in the deposit tracker location
        }
        _;
    }

    modifier revertIfDeposited() virtual {
        //solhint-disable-next-line no-inline-assembly
        assembly {
            // If the deposit tracker location is set to `1`, revert with `DepositAndWithdrawalForbidden()`
            if tload(_DEPOSIT_TRACKER_LOCATION) {
                mstore(0x00, 0x39b79d11) // Store the error signature `0x39b79d11` for `error DepositAndWithdrawalForbidden()` in memory.
                revert(0x1c, 0x04) // Revert by returning those 4 bytes. `revert DepositAndWithdrawalForbidden()`
            }
        }
        _;
    }

    /**
     * @dev See {IERC4626-totalAssets}.
     * pufETH, the shares of the vault, will be backed primarily by the WETH asset.
     * However, at any point in time, the full backings may be a combination of stETH, WETH, and ETH.
     * `totalAssets()` is calculated by summing the following:
     * + WETH held in the vault contract
     * + ETH  held in the vault contract
     * + PUFFER_ORACLE.getLockedEthAmount(), which is the oracle-reported Puffer validator ETH locked in the Beacon chain
     * + getTotalRewardMintAmount(), which is the total amount of rewards minted
     * - getTotalRewardDepositAmount(), which is the total amount of rewards deposited to the Vault
     * - RESTAKING_REWARDS_DEPOSITOR.getPendingDistributionAmount(), which is the total amount of rewards pending distribution
     * - callvalue(), which is the amount of ETH deposited by the caller in the transaction, it will be non zero only when the caller is depositing ETH
     *
     * NOTE on the native ETH deposits:
     * When dealing with NATIVE ETH deposits, we need to deduct callvalue from the balance.
     * The contract calculates the amount of shares(pufETH) to mint based on the total assets.
     * When a user sends ETH, the msg.value is immediately added to address(this).balance.
     * Since address(this.balance)` is used in calculating `totalAssets()`, we must deduct the `callvalue()` from the balance to prevent the user from minting excess shares.
     * `msg.value` cannot be accessed from a view function, so we use assembly to get the callvalue.
     */
    function totalAssets() public view virtual override returns (uint256) {
        uint256 callValue;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            callValue := callvalue()
        }
        return _ST_ETH.balanceOf(address(this)) + getPendingLidoETHAmount() + _WETH.balanceOf(address(this))
            + (address(this).balance - callValue) + PUFFER_ORACLE.getLockedEthAmount() + getTotalRewardMintAmount()
            - getTotalRewardDepositAmount() - RESTAKING_REWARDS_DEPOSITOR.getPendingDistributionAmount();
    }

    /**
     * @notice Deposits native ETH into the Puffer Vault
     * @param receiver The recipient of pufETH tokens
     * @return shares The amount of pufETH received from the deposit
     *
     * @dev Restricted in this context is like `whenNotPaused` modifier from Pausable.sol
     */
    function depositETH(address receiver) public payable virtual markDeposit restricted returns (uint256) {
        uint256 maxAssets = maxDeposit(receiver);
        if (msg.value > maxAssets) {
            revert ERC4626ExceededMaxDeposit(receiver, msg.value, maxAssets);
        }

        uint256 shares = previewDeposit(msg.value);
        _mint(receiver, shares);
        emit Deposit(_msgSender(), receiver, msg.value, shares);

        return shares;
    }

    /**
     * @notice Deposits stETH into the Puffer Vault
     * @param stETHSharesAmount The shares amount of stETH to deposit
     * @param receiver The recipient of pufETH tokens
     * @return shares The amount of pufETH received from the deposit
     *
     * @dev Restricted in this context is like `whenNotPaused` modifier from Pausable.sol
     */
    function depositStETH(uint256 stETHSharesAmount, address receiver)
        public
        virtual
        markDeposit
        restricted
        returns (uint256)
    {
        uint256 maxAssets = maxDeposit(receiver);

        // Get the amount of assets (stETH) that corresponds to `stETHSharesAmount` so that we can use it in our calculation
        uint256 assets = _ST_ETH.getPooledEthByShares(stETHSharesAmount);

        if (assets > maxAssets) {
            revert ERC4626ExceededMaxDeposit(receiver, assets, maxAssets);
        }

        uint256 shares = previewDeposit(assets);
        // Transfer the exact number of stETH shares from the user to the vault
        _ST_ETH.transferSharesFrom({ _sender: msg.sender, _recipient: address(this), _sharesAmount: stETHSharesAmount });
        _mint(receiver, shares);

        emit Deposit(_msgSender(), receiver, assets, shares);

        return shares;
    }

    /**
     * @dev Restricted in this context is like `whenNotPaused` modifier from Pausable.sol
     */
    function deposit(uint256 assets, address receiver)
        public
        virtual
        override
        markDeposit
        restricted
        returns (uint256)
    {
        return super.deposit(assets, receiver);
    }

    /**
     * @dev Restricted in this context is like `whenNotPaused` modifier from Pausable.sol
     */
    function mint(uint256 shares, address receiver) public virtual override markDeposit restricted returns (uint256) {
        return super.mint(shares, receiver);
    }

    /**
     * @notice Mints pufETH rewards for the L1RewardManager contract and returns the exchange rate.
     *
     * @dev Restricted to L1RewardManager
     */
    function mintRewards(uint256 rewardsAmount)
        external
        restricted
        returns (uint256 ethToPufETHRate, uint256 pufETHAmount)
    {
        ethToPufETHRate = convertToShares(1 ether);
        // calculate the shares using this formula since calling convertToShares again is costly
        pufETHAmount = ethToPufETHRate.mulDiv(rewardsAmount, 1 ether, Math.Rounding.Floor);

        VaultStorage storage $ = _getPufferVaultStorage();

        uint256 previousRewardsAmount = $.totalRewardMintAmount;
        uint256 newTotalRewardsAmount = previousRewardsAmount + rewardsAmount;
        $.totalRewardMintAmount = newTotalRewardsAmount;

        emit UpdatedTotalRewardsAmount(previousRewardsAmount, newTotalRewardsAmount, 0);

        // msg.sender is the L1RewardManager contract
        _mint(msg.sender, pufETHAmount);

        return (ethToPufETHRate, pufETHAmount);
    }

    /**
     * @notice Deposits the rewards amount to the vault and updates the total reward deposit amount.
     *
     * @dev Restricted to PufferModuleManager
     */
    function depositRewards() external payable restricted {
        VaultStorage storage $ = _getPufferVaultStorage();
        uint256 previousRewardsAmount = $.totalRewardDepositAmount;
        uint256 newTotalRewardsAmount = previousRewardsAmount + msg.value;
        $.totalRewardDepositAmount = newTotalRewardsAmount;

        emit UpdatedTotalRewardsAmount(previousRewardsAmount, newTotalRewardsAmount, msg.value);
    }

    /**
     * @notice Reverts the `mintRewards` action.
     *
     * @dev Restricted to L1RewardManager
     */
    function revertMintRewards(uint256 pufETHAmount, uint256 ethAmount) external restricted {
        VaultStorage storage $ = _getPufferVaultStorage();

        uint256 previousMintAmount = $.totalRewardMintAmount;
        // nosemgrep basic-arithmetic-underflow
        uint256 newMintAmount = previousMintAmount - ethAmount;
        $.totalRewardMintAmount = newMintAmount;

        emit UpdatedTotalRewardsAmount(previousMintAmount, newMintAmount, 0);

        // msg.sender is the L1RewardManager contract
        _burn(msg.sender, pufETHAmount);
    }

    /**
     * @notice Withdrawals WETH assets from the vault, burning the `owner`'s (pufETH) shares.
     * The caller of this function does not have to be the `owner` if the `owner` has approved the caller to spend their pufETH.
     * Copied the original ERC4626 code back to override `PufferVault` + wrap ETH logic
     * @param assets The amount of assets (WETH) to withdraw
     * @param receiver The address to receive the assets (WETH)
     * @param owner The address of the owner for which the shares (pufETH) are burned.
     * @return shares The amount of shares (pufETH) burned
     *
     * @dev Restricted in this context is like `whenNotPaused` modifier from Pausable.sol
     */
    function withdraw(uint256 assets, address receiver, address owner)
        public
        virtual
        override
        revertIfDeposited
        restricted
        returns (uint256)
    {
        uint256 maxAssets = maxWithdraw(owner);
        if (assets > maxAssets) {
            revert ERC4626ExceededMaxWithdraw(owner, assets, maxAssets);
        }

        _wrapETH(assets);

        uint256 shares = previewWithdraw(assets);
        _withdraw({ caller: _msgSender(), receiver: receiver, owner: owner, assets: assets, shares: shares });

        return shares;
    }

    /**
     * @notice Redeems (pufETH) `shares` to receive (WETH) assets from the vault, burning the `owner`'s (pufETH) `shares`.
     * The caller of this function does not have to be the `owner` if the `owner` has approved the caller to spend their pufETH.
     * Copied the original ERC4626 code back to override `PufferVault` + wrap ETH logic
     * @param shares The amount of shares (pufETH) to withdraw
     * @param receiver The address to receive the assets (WETH)
     * @param owner The address of the owner for which the shares (pufETH) are burned.
     * @return assets The amount of assets (WETH) redeemed
     *
     * @dev Restricted in this context is like `whenNotPaused` modifier from Pausable.sol
     */
    function redeem(uint256 shares, address receiver, address owner)
        public
        virtual
        override
        revertIfDeposited
        restricted
        returns (uint256)
    {
        uint256 maxShares = maxRedeem(owner);
        if (shares > maxShares) {
            revert ERC4626ExceededMaxRedeem(owner, shares, maxShares);
        }

        uint256 assets = previewRedeem(shares);

        _wrapETH(assets);

        _withdraw({ caller: _msgSender(), receiver: receiver, owner: owner, assets: assets, shares: shares });

        return assets;
    }

    /**
     * @notice Initiates ETH withdrawals from Lido
     * @param amounts An array of stETH amounts to queue
     * @return requestIds An array of request IDs for the withdrawals
     *
     * @dev Restricted to Operations Multisig
     */
    function initiateETHWithdrawalsFromLido(uint256[] calldata amounts)
        external
        virtual
        restricted
        returns (uint256[] memory requestIds)
    {
        require(amounts.length != 0);
        VaultStorage storage $ = _getPufferVaultStorage();

        uint256 lockedAmount;
        for (uint256 i = 0; i < amounts.length; ++i) {
            lockedAmount += amounts[i];
        }
        $.lidoLockedETH += lockedAmount;

        SafeERC20.safeIncreaseAllowance(_ST_ETH, address(_LIDO_WITHDRAWAL_QUEUE), lockedAmount);
        requestIds = _LIDO_WITHDRAWAL_QUEUE.requestWithdrawals(amounts, address(this));

        // nosemgrep array-length-outside-loop
        for (uint256 i = 0; i < requestIds.length; ++i) {
            $.lidoWithdrawalAmounts.set(requestIds[i], amounts[i]);
        }
        emit RequestedWithdrawals(requestIds);
        return requestIds;
    }

    /**
     * @notice Claims ETH withdrawals from Lido
     * @param requestIds An array of request IDs for the withdrawals
     *
     * @dev Restricted to Operations Multisig
     */
    function claimWithdrawalsFromLido(uint256[] calldata requestIds) external virtual restricted {
        require(requestIds.length != 0);
        VaultStorage storage $ = _getPufferVaultStorage();

        // ETH balance before the claim
        uint256 balanceBefore = address(this).balance;

        uint256 expectedWithdrawal = 0;

        for (uint256 i = 0; i < requestIds.length; ++i) {
            // .get reverts if requestId is not present
            expectedWithdrawal += $.lidoWithdrawalAmounts.get(requestIds[i]);
            $.lidoWithdrawalAmounts.remove(requestIds[i]);

            // slither-disable-next-line calls-loop
            _LIDO_WITHDRAWAL_QUEUE.claimWithdrawal(requestIds[i]);
        }

        // ETH balance after the claim
        uint256 balanceAfter = address(this).balance;
        uint256 actualWithdrawal = balanceAfter - balanceBefore;
        // Deduct from the locked amount the expected amount
        // nosemgrep basic-arithmetic-underflow
        $.lidoLockedETH -= expectedWithdrawal;

        emit ClaimedWithdrawals(requestIds);
        emit LidoWithdrawal(expectedWithdrawal, actualWithdrawal);
    }

    /**
     * @notice Transfers ETH to a specified address.
     * @dev It is used to transfer ETH to PufferModules to fund Puffer validators.
     * @param to The address of the PufferModule to transfer ETH to
     * @param ethAmount The amount of ETH to transfer
     *
     * @dev Restricted to PufferProtocol|PufferWithdrawalManager smart contracts
     */
    function transferETH(address to, uint256 ethAmount) external restricted {
        // Our Vault holds ETH & WETH
        // If we don't have enough ETH for the transfer, unwrap WETH
        uint256 ethBalance = address(this).balance;
        if (ethBalance < ethAmount) {
            // Reverts if no WETH to unwrap
            // nosemgrep basic-arithmetic-underflow
            _WETH.withdraw(ethAmount - ethBalance);
        }

        // slither-disable-next-line arbitrary-send-eth
        (bool success,) = to.call{ value: ethAmount }("");

        if (!success) {
            revert ETHTransferFailed();
        }

        emit TransferredETH(to, ethAmount);
    }

    /**
     * @notice Allows the `msg.sender` to burn their (pufETH) shares
     * It is used to burn portions of Puffer validator bonds due to inactivity or slashing
     * @param shares The amount of shares to burn
     *
     * @dev Restricted to PufferProtocol|PufferWithdrawalManager smart contracts
     */
    function burn(uint256 shares) public restricted {
        _burn(msg.sender, shares);
    }

    /**
     * @param newExitFeeBasisPoints is the new exit fee basis points
     *
     * @dev Restricted to the DAO
     */
    function setExitFeeBasisPoints(uint256 newExitFeeBasisPoints) external restricted {
        _setExitFeeBasisPoints(newExitFeeBasisPoints);
    }

    /**
     * @notice Returns the maximum amount of assets that can be withdrawn from the vault for a given owner
     * If the user has more assets than the available vault's liquidity, the user will be able to withdraw up to the available liquidity
     * else the user will be able to withdraw up to their assets
     * @param owner The address to check the maximum withdrawal amount for
     * @return maxAssets The maximum amount of assets that can be withdrawn
     */
    function maxWithdraw(address owner) public view virtual override returns (uint256 maxAssets) {
        uint256 maxUserAssets = previewRedeem(balanceOf(owner));

        uint256 vaultLiquidity = (_WETH.balanceOf(address(this)) + (address(this).balance));
        // Return the minimum of user's assets and available liquidity
        return Math.min(maxUserAssets, vaultLiquidity);
    }

    /**
     * @notice Returns the maximum amount of shares that can be redeemed from the vault for a given owner
     * If the user has more shares than what can be redeemed given the available liquidity, the user will be able to redeem up to the available liquidity
     * else the user will be able to redeem all their shares
     * @param owner The address to check the maximum redeemable shares for
     * @return maxShares The maximum amount of shares that can be redeemed
     */
    function maxRedeem(address owner) public view virtual override returns (uint256 maxShares) {
        uint256 shares = balanceOf(owner);
        // Calculate max shares based on available liquidity (WETH + ETH balance)
        uint256 availableLiquidity = _WETH.balanceOf(address(this)) + (address(this).balance);
        // Calculate how many shares can be redeemed from the available liquidity after fees
        uint256 maxSharesFromLiquidity = previewWithdraw(availableLiquidity);
        // Return the minimum of user's shares and shares from available liquidity
        return Math.min(shares, maxSharesFromLiquidity);
    }

    /**
     * @dev Preview adding an exit fee on withdraw. See {IERC4626-previewWithdraw}.
     */
    function previewWithdraw(uint256 assets) public view virtual override returns (uint256) {
        uint256 fee = _feeOnRaw(assets, getExitFeeBasisPoints());
        return super.previewWithdraw(assets + fee);
    }

    /**
     * @dev Preview taking an exit fee on redeem. See {IERC4626-previewRedeem}.
     */
    function previewRedeem(uint256 shares) public view virtual override returns (uint256) {
        uint256 assets = super.previewRedeem(shares);
        // nosemgrep basic-arithmetic-underflow
        return assets - _feeOnTotal(assets, getExitFeeBasisPoints());
    }

    /**
     * @notice Returns the current exit fee basis points
     */
    function getExitFeeBasisPoints() public view virtual returns (uint256) {
        VaultStorage storage $ = _getPufferVaultStorage();
        return $.exitFeeBasisPoints;
    }

    /**
     * @notice Returns the amount of ETH that is pending withdrawal from Lido
     * @return The amount of ETH pending withdrawal
     */
    function getPendingLidoETHAmount() public view virtual returns (uint256) {
        VaultStorage storage $ = _getPufferVaultStorage();
        return $.lidoLockedETH;
    }

    /**
     * @notice Returns the total reward mint amount
     * @return The total minted rewards amount
     */
    function getTotalRewardMintAmount() public view returns (uint256) {
        VaultStorage storage $ = _getPufferVaultStorage();
        return $.totalRewardMintAmount;
    }

    /**
     * @notice Returns the total reward deposit amount
     * @return The total deposited rewards amount
     */
    function getTotalRewardDepositAmount() public view returns (uint256) {
        VaultStorage storage $ = _getPufferVaultStorage();
        return $.totalRewardDepositAmount;
    }

    /**
     * @notice Returns the amount of shares (pufETH) for the `assets` amount rounded up
     * @param assets The amount of assets
     */
    function convertToSharesUp(uint256 assets) public view returns (uint256) {
        return _convertToShares(assets, Math.Rounding.Ceil);
    }

    /**
     * @notice Required by the ERC721 Standard to receive Lido withdrawal NFT
     */
    function onERC721Received(address, address, uint256, bytes calldata) external virtual returns (bytes4) {
        return IERC721Receiver.onERC721Received.selector;
    }

    /**
     * @notice Returns the number of decimals used to get its user representation
     */
    function decimals() public pure override(ERC20Upgradeable, ERC4626Upgradeable) returns (uint8) {
        return 18;
    }

    /**
     * @dev Calculates the fees that should be added to an amount `assets` that does not already include fees.
     * Used in {IERC4626-withdraw}.
     */
    function _feeOnRaw(uint256 assets, uint256 feeBasisPoints) internal pure virtual returns (uint256) {
        return assets.mulDiv(feeBasisPoints, _BASIS_POINT_SCALE, Math.Rounding.Ceil);
    }

    /**
     * @dev Calculates the fee part of an amount `assets` that already includes fees.
     * Used in {IERC4626-redeem}.
     */
    function _feeOnTotal(uint256 assets, uint256 feeBasisPoints) internal pure virtual returns (uint256) {
        return assets.mulDiv(feeBasisPoints, feeBasisPoints + _BASIS_POINT_SCALE, Math.Rounding.Ceil);
    }

    /**
     * @notice Updates the exit fee basis points
     * @dev 200 Basis points = 2% is the maximum exit fee
     */
    function _setExitFeeBasisPoints(uint256 newExitFeeBasisPoints) internal virtual {
        VaultStorage storage $ = _getPufferVaultStorage();
        // 2% is the maximum exit fee
        if (newExitFeeBasisPoints > 200) {
            revert InvalidExitFeeBasisPoints();
        }
        emit ExitFeeBasisPointsSet($.exitFeeBasisPoints, newExitFeeBasisPoints);
        $.exitFeeBasisPoints = newExitFeeBasisPoints;
    }

    /**
     * @notice Wraps the vault's ETH balance to WETH
     * @dev Used to provide WETH liquidity
     */
    function _wrapETH(uint256 assets) internal virtual {
        uint256 wethBalance = _WETH.balanceOf(address(this));

        if (wethBalance < assets) {
            _WETH.deposit{ value: assets - wethBalance }();
        }
    }

    /**
     * @dev Authorizes an upgrade to a new implementation
     * Restricted access
     * @param newImplementation The address of the new implementation
     */
    // slither-disable-next-line dead-code
    function _authorizeUpgrade(address newImplementation) internal virtual override restricted { }
}
"
    },
    "node_modules/@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/UUPSUpgradeable.sol)

pragma solidity ^0.8.20;

import {IERC1822Proxiable} from "@openzeppelin/contracts/interfaces/draft-IERC1822.sol";
import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
import {Initializable} from "./Initializable.sol";

/**
 * @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an
 * {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy.
 *
 * A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is
 * reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing
 * `UUPSUpgradeable` with a custom implementation of upgrades.
 *
 * The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism.
 */
abstract contract UUPSUpgradeable is Initializable, IERC1822Proxiable {
    /// @custom:oz-upgrades-unsafe-allow state-variable-immutable
    address private immutable __self = address(this);

    /**
     * @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgradeTo(address)`
     * and `upgradeToAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called,
     * while `upgradeToAndCall` will invoke the `receive` function if the second argument is the empty byte string.
     * If the getter returns `"5.0.0"`, only `upgradeToAndCall(address,bytes)` is present, and the second argument must
     * be the empty byte string if no function should be called, making it impossible to invoke the `receive` function
     * during an upgrade.
     */
    string public constant UPGRADE_INTERFACE_VERSION = "5.0.0";

    /**
     * @dev The call is from an unauthorized context.
     */
    error UUPSUnauthorizedCallContext();

    /**
     * @dev The storage `slot` is unsupported as a UUID.
     */
    error UUPSUnsupportedProxiableUUID(bytes32 slot);

    /**
     * @dev Check that the execution is being performed through a delegatecall call and that the execution context is
     * a proxy contract with an implementation (as defined in ERC1967) pointing to self. This should only be the case
     * for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a
     * function through ERC1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to
     * fail.
     */
    modifier onlyProxy() {
        _checkProxy();
        _;
    }

    /**
     * @dev Check that the execution is not being performed through a delegate call. This allows a function to be
     * callable on the implementing contract but not through proxies.
     */
    modifier notDelegated() {
        _checkNotDelegated();
        _;
    }

    function __UUPSUpgradeable_init() internal onlyInitializing {
    }

    function __UUPSUpgradeable_init_unchained() internal onlyInitializing {
    }
    /**
     * @dev Implementation of the ERC1822 {proxiableUUID} function. This returns the storage slot used by the
     * implementation. It is used to validate the implementation's compatibility when performing an upgrade.
     *
     * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
     * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
     * function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.
     */
    function proxiableUUID() external view virtual notDelegated returns (bytes32) {
        return ERC1967Utils.IMPLEMENTATION_SLOT;
    }

    /**
     * @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call
     * encoded in `data`.
     *
     * Calls {_authorizeUpgrade}.
     *
     * Emits an {Upgraded} event.
     *
     * @custom:oz-upgrades-unsafe-allow-reachable delegatecall
     */
    function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy {
        _authorizeUpgrade(newImplementation);
        _upgradeToAndCallUUPS(newImplementation, data);
    }

    /**
     * @dev Reverts if the execution is not performed via delegatecall or the execution
     * context is not of a proxy with an ERC1967-compliant implementation pointing to self.
     * See {_onlyProxy}.
     */
    function _checkProxy() internal view virtual {
        if (
            address(this) == __self || // Must be called through delegatecall
            ERC1967Utils.getImplementation() != __self // Must be called through an active proxy
        ) {
            revert UUPSUnauthorizedCallContext();
        }
    }

    /**
     * @dev Reverts if the execution is performed via delegatecall.
     * See {notDelegated}.
     */
    function _checkNotDelegated() internal view virtual {
        if (address(this) != __self) {
            // Must not be called through delegatecall
            revert UUPSUnauthorizedCallContext();
        }
    }

    /**
     * @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by
     * {upgradeToAndCall}.
     *
     * Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}.
     *
     * ```solidity
     * function _authorizeUpgrade(address) internal onlyOwner {}
     * ```
     */
    function _authorizeUpgrade(address newImplementation) internal virtual;

    /**
     * @dev Performs an implementation upgrade with a security check for UUPS proxies, and additional setup call.
     *
     * As a security check, {proxiableUUID} is invoked in the new implementation, and the return value
     * is expected to be the implementation slot in ERC1967.
     *
     * Emits an {IERC1967-Upgraded} event.
     */
    function _upgradeToAndCallUUPS(address newImplementation, bytes memory data) private {
        try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
            if (slot != ERC1967Utils.IMPLEMENTATION_SLOT) {
                revert UUPSUnsupportedProxiableUUID(slot);
            }
            ERC1967Utils.upgradeToAndCall(newImplementation, data);
        } catch {
            // The implementation is not UUPS
            revert ERC1967Utils.ERC1967InvalidImplementation(newImplementation);
        }
    }
}
"
    },
    "node_modules/mainnet-contracts/src/Errors.sol": {
      "content": "// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0 <0.9.0;

/**
 * @notice Thrown when the operation is not authorized
 * @dev Signature "0x82b42900"
 */
error Unauthorized();

/**
 * @notice Thrown if the address supplied is not valid
 * @dev Signature "0xe6c4247b"
 */
error InvalidAddress();

/**
 * @notice Thrown when amount is not valid
 * @dev Signature "0x2c5211c6"
 */
error InvalidAmount();

/**
 * @notice Thrown when transfer fails
 * @dev Signature "0x90b8ec18"
 */
error TransferFailed();
"
    },
    "src/L1RewardManagerStorage.sol": {
      "content": "// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0 <0.9.0;

/**
 * @title L1RewardManagerStorage
 * @author Puffer Finance
 * @custom:security-contact security@puffer.fi
 */
abstract contract L1RewardManagerStorage {
    /**
     * @notice Parameters for setting a claimer.
     * @param account The account setting the claimer.
     * @param claimer The address of the new claimer.
     */
    struct SetClaimerParams {
        address account;
        address claimer;
    }

    /**
     * @notice Parameters for minting and bridging rewards (calldata).
     * @param rewardsAmount The amount of rewards to be bridged.
     * @param ethToPufETHRate The exchange rate from ETH to pufETH.
     * @param startEpoch The starting epoch for the rewards.
     * @param endEpoch The ending epoch for the rewards.
     * @param rewardsRoot The merkle root of the rewards.
     * @param rewardsURI The URI for the rewards metadata.
     */
    struct MintAndBridgeData {
        uint256 rewardsAmount;
        uint256 ethToPufETHRate;
        uint256 startEpoch;
        uint256 endEpoch;
        bytes32 rewardsRoot;
        string rewardsURI;
    }

    /**
     * @custom:storage-location erc7201:l1rewardmanager.storage
     * @dev +-----------------------------------------------------------+
     *      |                                                           |
     *      | DO NOT CHANGE, REORDER, REMOVE EXISTING STORAGE VARIABLES |
     *      |                                                           |
     *      +-----------------------------------------------------------+
     */
    struct RewardManagerStorage {
        uint104 allowedRewardMintAmount;
        uint104 allowedRewardMintFrequency;
        uint48 lastRewardMintTimestamp;
        /**
         * @dev DEPRECATED: Unused bridge mapping, kept for storage layout compatibility
         */
        mapping(address bridge => uint32 deprecatedDestinationDomainId) _deprecatedBridges;
        /**
         * @notice The destination endpoint ID for LayerZero bridging
         */
        uint32 destinationEID;
        /**
         * @notice The last successfully processed interval end epoch
         */
        uint256 lastIntervalEndEpoch;
        /**
         * @notice The current interval end epoch being processed
         */
        uint256 currentIntervalEndEpoch;
    }

    // keccak256(abi.encode(uint256(keccak256("l1rewardmanager.storage")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant _REWARD_MANAGER_STORAGE_LOCATION =
        0xb18045c429f6c4e33b477568e1a40f795629ac8937518d2b48a302e4c0fbb700;

    function _getRewardManagerStorage() internal pure returns (RewardManagerStorage storage $) {
        // solhint-disable-next-line no-inline-assembly
        assembly {
            $.slot := _REWARD_MANAGER_STORAGE_LOCATION
        }
    }
}
"
    },
    "node_modules/l2-contracts/src/L2RewardManagerStorage.sol": {
      "content": "// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0 <0.9.0;

/**
 * @title L2RewardManagerStorage
 * @author Puffer Finance
 * @custom:security-contact security@puffer.fi
 */
abstract contract L2RewardManagerStorage {
    /**
     * @notice A record of a single epoch for storing the rate and root.
     * @param startEpoch The start epoch of the interval
     * @param endEpoch The end epoch of the interval
     * @param ethToPufETHRate The exchange rate from ETH to pufETH.
     * @param rewardRoot The merkle root of the rewards.
     * @param timeBridged The timestamp of then the rewars were bridged to L2.
     * @param pufETHAmount The xPufETH amount minted and bridged.
     * @param ethAmount The ETH amount that was converted.
     *
     * @dev +-----------------------------------------------------------+
     *      |                                                           |
     *      | DO NOT CHANGE, REORDER, REMOVE EXISTING STORAGE VARIABLES |
     *      |                                                           |
     *      +-----------------------------------------------------------+
     */
    struct EpochRecord {
        uint104 startEpoch; // packed slot 0
        uint104 endEpoch; // packed slot 0
        uint48 timeBridged; // packed slot 0
        uint128 pufETHAmount; // packed slot 1
        uint128 ethAmount; // packed slot 1
        uint256 ethToPufETHRate; // slot 2
        bytes32 rewardRoot;
    }

    /**
     * @custom:storage-location erc7201:L2RewardManager.storage
     * @dev +-----------------------------------------------------------+
     *      |                                                           |
     *      | DO NOT CHANGE, REORDER, REMOVE EXISTING STORAGE VARIABLES |
     *      |                                                           |
     *      +-----------------------------------------------------------+
     */
    struct RewardManagerStorage {
        /**
         * @notice Mapping to track the exchange rate from ETH to pufETH and reward root for each unique epoch range
         * @dev `rewardsInterval` is calculated as `keccak256(abi.encodePacked(startEpoch, endEpoch))`
         * we are using that instead of the merkle root, because we want to prevent double posting of the same epoch range
         */
        mapping(bytes32 rewardsInterval => EpochRecord) epochRecords;
        /**
         * @notice Mapping to track claimed tokens for users for each unique epoch range
         * @dev `rewardsInterval` is calculated as `keccak256(abi.encodePacked(startEpoch, endEpoch))`
         * we are using that instead of the merkle root, because we want to prevent double posting of the same epoch range
         */
        mapping(bytes32 rewardsInterval => mapping(address account => bool claimed)) claimedRewards;
        /**
         * @notice Mapping to track the custom claimer set by specific accounts
         */
        mapping(address account => address claimer) rewardsClaimers;
        /**
         * @dev DEPRECATED: Unused bridge mapping, kept for storage layout compatibility
         */
        mapping(address bridge => uint32 deprecatedDestinationDomainId) _deprecatedBridges;
        /**
         * @notice This period is used to delay the rewards claim for the users
         * After the rewards have been bridged from L1, we will wait for this period before allowing the users to claim the rewards for that rewards interval
         */
        uint256 claimingDelay;
        /**
         * @notice The pufETH OFT address for singleton design
         */
        address pufETHOFT;
        /**
         * @notice The destination endpoint ID for LayerZero bridging
         */
        uint32 destinationEID;
    }

    // keccak256(abi.encode(uint256(keccak256("L2RewardManager.storage")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 internal constant _REWARD_MANAGER_STORAGE_LOCATION =
        0x7f1aa0bc41c09fbe61ccc14f95edc9998b7136087969b5ccb26131ec2cbbc800;

    function _getRewardManagerStorage() internal pure returns (RewardManagerStorage storage $) {
        // solhint-disable-next-line
        assembly {
            $.slot := _REWARD_MANAGER_STORAGE_LOCATION
        }
    }
}
"
    },
    "src/interface/LayerZero/IOFT.sol": {
      "content": "// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0 <0.9.0;

interface IOFT {
    /**
     * @dev Struct representing token parameters for the OFT send() operation.
     */
    struct SendParam {
        uint32 dstEid; // Destination endpoint ID.
        bytes32 to; // Recipient address.
        uint256 amountLD; // Amount to send in local decimals.
        uint256 minAmountLD; // Minimum amount to send in local decimals.
        bytes extraOptions; // Additional options supplied by the caller to be used in the LayerZero message.
        bytes composeMsg; // The composed message for the send() operation.
        bytes oftCmd; // The OFT command to be executed, unused in default OFT implementations.
    }

    /**
     * @dev Struct representing the messaging fee for the send() operation.
     */
    struct MessagingFee {
        uint256 nativeFee; // The native fee.
        uint256 lzTokenFee; // The lzToken fee.
    }

    /**
     * @notice Executes the send() operation.
     * @param _sendParam The parameters for the send operation.
     * @param _fee The fee information supplied by the caller.
     *      - nativeFee: The native fee.
     *      - lzTokenFee: The lzToken fee.
     * @param _refundAddress The address to receive any excess funds from fees etc. on the src.
     */
    function send(SendParam calldata _sendParam, MessagingFee calldata _fee, address _refundAddress) external payable;
}
"
    },
    "node_modules/@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppComposer.sol": {
      "content": "// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import { ILayerZeroComposer } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroComposer.sol";

/**
 * @title IOAppComposer
 * @dev This interface defines the OApp Composer, allowing developers to inherit o

Tags:
ERC20, Multisig, Mintable, Burnable, Swap, Liquidity, Yield, Voting, Timelock, Upgradeable, Multi-Signature, Factory, Oracle|addr:0xc9b0813d9e7f45b5bce84a24f07f33ec6de0b583|verified:true|block:23388512|tx:0xc6d8ea31b2fd47bc1bf52d0466abe2c36211b92c330e6a96eaeecd1c20a76b3f|first_check:1758191474

Submitted on: 2025-09-18 12:31:16

Comments

Log in to comment.

No comments yet.