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
104 changes: 104 additions & 0 deletions src/contracts/interfaces/IDurationVaultStrategy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// 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 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 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);
}
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