Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion script/deploy/devnet/deploy_from_scratch.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,8 @@ contract DeployFromScratch is Script, Test {
StrategyFactory.initialize.selector,
executorMultisig,
0, // initial paused status
IBeacon(strategyBeacon)
IBeacon(strategyBeacon),
IBeacon(address(0))
)
);

Expand Down
2 changes: 1 addition & 1 deletion script/releases/TestUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -967,7 +967,7 @@ library TestUtils {
StrategyFactory strategyFactory
) internal {
vm.expectRevert(errInit);
strategyFactory.initialize(address(0), 0, UpgradeableBeacon(address(0)));
strategyFactory.initialize(address(0), 0, UpgradeableBeacon(address(0)), UpgradeableBeacon(address(0)));
}

/// multichain/
Expand Down
3 changes: 3 additions & 0 deletions script/utils/ExistingDeploymentParser.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import "../../src/contracts/permissions/PermissionController.sol";
import "../../src/contracts/strategies/StrategyFactory.sol";
import "../../src/contracts/strategies/StrategyBase.sol";
import "../../src/contracts/strategies/StrategyBaseTVLLimits.sol";
import "../../src/contracts/strategies/DurationVaultStrategy.sol";
import "../../src/contracts/strategies/EigenStrategy.sol";

import "../../src/contracts/pods/EigenPod.sol";
Expand Down Expand Up @@ -103,6 +104,7 @@ contract ExistingDeploymentParser is Script, Logger {
PauserRegistry public eigenLayerPauserReg;
UpgradeableBeacon public eigenPodBeacon;
UpgradeableBeacon public strategyBeacon;
UpgradeableBeacon public durationVaultBeacon;

/// @dev AllocationManager
IAllocationManager public allocationManager;
Expand Down Expand Up @@ -138,6 +140,7 @@ contract ExistingDeploymentParser is Script, Logger {
StrategyFactory public strategyFactory;
StrategyFactory public strategyFactoryImplementation;
StrategyBase public baseStrategyImplementation;
DurationVaultStrategy public durationVaultImplementation;
StrategyBase public strategyFactoryBeaconImplementation;

// Token
Expand Down
7 changes: 7 additions & 0 deletions src/contracts/core/StrategyManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,9 @@ contract StrategyManager is
require(staker != address(0), StakerAddressZero());
require(shares != 0, SharesAmountZero());

// allow the strategy to veto or enforce constraints on adding shares
strategy.beforeAddShares(staker, shares);

uint256 prevDepositShares = stakerDepositShares[staker][strategy];

// if they dont have prevDepositShares of this strategy, add it to their strats
Expand Down Expand Up @@ -336,6 +339,10 @@ contract StrategyManager is
// This check technically shouldn't actually ever revert because depositSharesToRemove is already
// checked to not exceed max amount of shares when the withdrawal was queued in the DelegationManager
require(depositSharesToRemove <= userDepositShares, SharesAmountTooHigh());

// allow the strategy to veto or enforce constraints on removing shares
strategy.beforeRemoveShares(staker, depositSharesToRemove);

userDepositShares = userDepositShares - depositSharesToRemove;

// subtract the shares from the staker's existing shares for this strategy
Expand Down
113 changes: 113 additions & 0 deletions src/contracts/interfaces/IDurationVaultStrategy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import "./IStrategy.sol";
import "./IDelegationManager.sol";
import "./IAllocationManager.sol";
import "../libraries/OperatorSetLib.sol";

/// @title Interface for time-bound EigenLayer vault strategies.
/// @author Layr Labs, Inc.
/// @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
interface IDurationVaultStrategy is IStrategy {
enum VaultState {
UNINITIALIZED,
DEPOSITS,
ALLOCATIONS,
WITHDRAWALS
}

struct VaultConfig {
IERC20 underlyingToken;
address vaultAdmin;
uint32 duration;
uint256 maxPerDeposit;
uint256 stakeCap;
string metadataURI;
OperatorSet operatorSet;
bytes operatorSetRegistrationData;
address delegationApprover;
uint32 operatorAllocationDelay;
string operatorMetadataURI;
}

/// @dev Thrown when attempting to use a zero-address vault admin.
error InvalidVaultAdmin();
/// @dev Thrown when attempting to configure a zero duration.
error InvalidDuration();
/// @dev Thrown when attempting to mutate configuration from a non-admin.
error OnlyVaultAdmin();
/// @dev Thrown when attempting to lock an already locked vault.
error VaultAlreadyLocked();
/// @dev Thrown when attempting to deposit after the vault has been locked.
error DepositsLocked();
/// @dev Thrown when attempting to withdraw while funds remain locked.
error WithdrawalsLocked();
/// @dev Thrown when attempting to remove shares during the allocations period.
error WithdrawalsLockedDuringAllocations();
/// @dev Thrown when attempting to add shares when not delegated to the vault operator.
error MustBeDelegatedToVaultOperator();
/// @dev Thrown when attempting to mark the vault as matured before duration elapses.
error DurationNotElapsed();
/// @dev Thrown when operator integration inputs are missing or invalid.
error OperatorIntegrationInvalid();

event VaultInitialized(
address indexed vaultAdmin,
IERC20 indexed underlyingToken,
uint32 duration,
uint256 maxPerDeposit,
uint256 stakeCap,
string metadataURI
);

event VaultLocked(uint32 lockedAt, uint32 unlockAt);

event VaultMatured(uint32 maturedAt);

event MetadataURIUpdated(string newMetadataURI);

/// @notice Locks the vault, preventing further deposits / withdrawals until maturity.
function lock() external;

/// @notice Marks the vault as matured once the configured duration has elapsed.
/// @dev After maturation, withdrawals are permitted while deposits remain disabled.
function markMatured() external;

/// @notice Updates the vault metadata URI.
function updateMetadataURI(
string calldata newMetadataURI
) external;

/// @notice Updates the TVL limits for max deposit per transaction and total stake cap.
/// @dev Only callable by the vault admin while deposits are open (before lock).
function updateTVLLimits(
uint256 newMaxPerDeposit,
uint256 newStakeCap
) external;

function vaultAdmin() external view returns (address);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of vaultAdmin, is it worth just inheriting Ownable? Reduces the surface area of code IMO.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want it to be transferable?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't think we need it to be transferrable? Fine to just leave as an immutable at deploy

function duration() external view returns (uint32);
function lockedAt() external view returns (uint32);
function unlockTimestamp() external view returns (uint32);
function isLocked() external view returns (bool);
function isMatured() external view returns (bool);
function state() external view returns (VaultState);
function metadataURI() external view returns (string memory);
function stakeCap() external view returns (uint256);
function maxPerDeposit() external view returns (uint256);
function maxTotalDeposits() external view returns (uint256);
function depositsOpen() external view returns (bool);
function withdrawalsOpen() external view returns (bool);
function delegationManager() external view returns (IDelegationManager);
function allocationManager() external view returns (IAllocationManager);
function operatorIntegrationConfigured() external view returns (bool);
function operatorSetRegistered() external view returns (bool);
function allocationsActive() external view returns (bool);
function operatorSetInfo() external view returns (address avs, uint32 operatorSetId);

/// @notice Underlying amount currently queued for withdrawal but not yet completed.
function queuedUnderlying() external view returns (uint256);
}
16 changes: 16 additions & 0 deletions src/contracts/interfaces/IStrategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,22 @@ interface IStrategy is IStrategyErrors, IStrategyEvents {
uint256 amountShares
) external returns (uint256);

/// @notice Hook invoked by the StrategyManager before adding deposit shares for a staker.
/// @param staker The address receiving shares.
/// @param shares The number of shares being added.
function beforeAddShares(
address staker,
uint256 shares
) external;

/// @notice Hook invoked by the StrategyManager before removing deposit shares for a staker.
/// @param staker The address losing shares.
/// @param shares The number of shares being removed.
function beforeRemoveShares(
address staker,
uint256 shares
) external;

/// @notice Used to convert a number of shares to the equivalent amount of underlying tokens for this strategy.
/// For a staker using this function and trying to calculate the amount of underlying tokens they have in total they
/// should input into `amountShares` their withdrawable shares read from the `DelegationManager` contract.
Expand Down
39 changes: 39 additions & 0 deletions src/contracts/interfaces/IStrategyFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ pragma solidity ^0.8.27;
import "@openzeppelin/contracts/proxy/beacon/IBeacon.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./IStrategy.sol";
import "./IDurationVaultStrategy.sol";
import "./ISemVerMixin.sol";

/// @title Interface for the `StrategyFactory` contract.
/// @author Layr Labs, Inc.
Expand All @@ -16,12 +18,17 @@ interface IStrategyFactory {
error StrategyAlreadyExists();
/// @dev Thrown when attempting to blacklist a token that is already blacklisted
error AlreadyBlacklisted();
/// @dev Thrown when attempting to deploy a duration vault before its beacon has been configured.
error DurationVaultBeaconNotSet();

event TokenBlacklisted(IERC20 token);

/// @notice Upgradeable beacon which new Strategies deployed by this contract point to
function strategyBeacon() external view returns (IBeacon);

/// @notice Upgradeable beacon which duration vault strategies deployed by this contract point to
function durationVaultBeacon() external view returns (IBeacon);

/// @notice Mapping token => Strategy contract for the token
/// The strategies in this mapping are deployed by the StrategyFactory.
/// The factory can only deploy a single strategy per token address
Expand All @@ -42,6 +49,17 @@ interface IStrategyFactory {
IERC20 token
) external returns (IStrategy newStrategy);

/// @notice Deploys a new duration-bound vault strategy contract.
/// @dev Enforces the same blacklist semantics as vanilla strategies.
function deployDurationVaultStrategy(
IDurationVaultStrategy.VaultConfig calldata config
) external returns (IDurationVaultStrategy newVault);

/// @notice Returns all duration vaults that have ever been deployed for a given token.
function getDurationVaults(
IERC20 token
) external view returns (IDurationVaultStrategy[] memory);

/// @notice Owner-only function to pass through a call to `StrategyManager.addStrategiesToDepositWhitelist`
function whitelistStrategies(
IStrategy[] calldata strategiesToWhitelist
Expand All @@ -52,9 +70,30 @@ interface IStrategyFactory {
IStrategy[] calldata strategiesToRemoveFromWhitelist
) external;

/// @notice Owner-only function to update the beacon used for deploying duration vault strategies.
function setDurationVaultBeacon(
IBeacon newDurationVaultBeacon
) external;

/// @notice Emitted when the `strategyBeacon` is changed
event StrategyBeaconModified(IBeacon previousBeacon, IBeacon newBeacon);

/// @notice Emitted when the `durationVaultBeacon` is changed
event DurationVaultBeaconModified(IBeacon previousBeacon, IBeacon newBeacon);

/// @notice Emitted whenever a slot is set in the `tokenStrategy` mapping
event StrategySetForToken(IERC20 token, IStrategy strategy);

/// @notice Emitted whenever a duration vault is deployed. The vault address uniquely identifies the deployment.
event DurationVaultDeployed(
IDurationVaultStrategy indexed vault,
IERC20 indexed underlyingToken,
address indexed vaultAdmin,
uint32 duration,
uint256 maxPerDeposit,
uint256 stakeCap,
string metadataURI,
address operatorSetAVS,
uint32 operatorSetId
);
}
Loading
Loading