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
Submitted on: 2025-09-18 12:31:16
Comments
Log in to comment.
No comments yet.