-
Notifications
You must be signed in to change notification settings - Fork 462
feat: duration vaults #1671
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: release-dev/duration-vaults
Are you sure you want to change the base?
feat: duration vaults #1671
Conversation
ypatil12
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! General state machine + readability improvements. Big question is still around guarantees we need around staker delegation at the time of deposit.
| require(!isBlacklisted[underlyingToken], BlacklistedToken()); | ||
| require(address(durationVaultBeacon) != address(0), DurationVaultBeaconNotSet()); | ||
|
|
||
| newVault = IDurationVaultStrategy( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want these to be deterministic deploys?
| address newVaultAdmin | ||
| ) external; | ||
|
|
||
| function vaultAdmin() external view returns (address); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
| address newVaultAdmin | ||
| ) external; | ||
|
|
||
| function vaultAdmin() external view returns (address); |
There was a problem hiding this comment.
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
| * @notice Locks the vault, preventing new deposits and withdrawals until maturity. | ||
| */ | ||
| function lock() external override onlyVaultAdmin { | ||
| require(_state == VaultState.Deposits, VaultAlreadyLocked()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want a startTime for the vault? Unsure what the user story is for when the vault can begin allocation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having a unique start time per vault enables each vault to have a deterministic deployment... again unsure if that's a relevant user story
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We used to have defined deposit windows but removed to make things easier for AVSs. Insurance AVS can lock at an arbitrary time but it's expected they will say publicly
ypatil12
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will review the integration tests a bit later
| _; | ||
| } | ||
|
|
||
| constructor( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to set the operator fee here for rewards? Operators (and this strategy) by default get 10% of rewards. We don't have a way of claiming those rewards with the current implementation. Should the fee just be set to zero since presumably the operator and AVS are the (same?) entity? cc @non-fungible-nelson
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's set in configureOperatorIntegration
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha. Btw, it takes 7 days for this fee to become active on mainnet. We should document this as an edge case if the allocation state ends up being hit prior to the fee becoming active
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i guess allocation delay is 14 days , so this won't be possible?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the activationDelay:
eigenlayer-contracts/src/contracts/core/RewardsCoordinator.sol
Lines 308 to 325 in 8e25c9d
| /// @inheritdoc IRewardsCoordinator | |
| function setOperatorPISplit( | |
| address operator, | |
| uint16 split | |
| ) external onlyWhenNotPaused(PAUSED_OPERATOR_PI_SPLIT) checkCanCall(operator) { | |
| uint32 activatedAt = uint32(block.timestamp) + activationDelay; | |
| uint16 oldSplit = _getOperatorSplit(_operatorPISplitBips[operator]); | |
| _setOperatorSplit(_operatorPISplitBips[operator], split, activatedAt); | |
| emit OperatorPISplitBipsSet(msg.sender, operator, activatedAt, oldSplit, split); | |
| } | |
| /// @inheritdoc IRewardsCoordinator | |
| function setOperatorSetSplit( | |
| address operator, | |
| OperatorSet calldata operatorSet, | |
| uint16 split | |
| ) external onlyWhenNotPaused(PAUSED_OPERATOR_SET_SPLIT) checkCanCall(operator) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, need to set the operator PI split to 0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assuming this strategy doesn't get PI? Probably something to validate with product.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i mean , split is set on initialization and after 7 days it becomes active
- allocation in the other hand will be active only after allocation delay (14 days after lock ) which we set in initialization as well ,so by the time of reward , the split will be active cause rewards only starts after allocation is active (correct?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, good point. So we should be fine here :)
| @@ -0,0 +1,466 @@ | |||
| // SPDX-License-Identifier: BUSL-1.1 | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For sanity, in the upgrade folder can we add an upgrade test against a deposit that is before the upgrade for already existing strategies (Eigen strat, strategy factory, and tvl limits)
- Deposit into strategy, upgrade, queue withdrawal (tests remove shares), complete withdrawal as shares (tests add shares)
| _transferOwnership(_initialOwner); | ||
| _setPausedStatus(_initialPausedStatus); | ||
| _setStrategyBeacon(_strategyBeacon); | ||
| if (address(_durationVaultBeacon) != address(0)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems unnecessary if we don't check strategyBeacon is address(0)?
fb1301d to
8829ce2
Compare
| function explanation() external pure virtual override returns (string memory) { | ||
| return "Base Strategy implementation to inherit from for more complex implementations"; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you may override the explanation function to return an explanation of the duration vault strategy, it's weird that it returns baseStrategy explanation
| delegationManager = _delegationManager; | ||
| allocationManager = _allocationManager; | ||
| rewardsCoordinator = _rewardsCoordinator; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add _disableInitializers() to the constructor for impl
| function getDurationVaults( | ||
| IERC20 token | ||
| ) external view returns (IDurationVaultStrategy[] memory) { | ||
| return durationVaultsByToken[token]; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is the only way to fetch the duration vaults for a token, but it can lead to OOG issue for on-chain integrations, specially that anyone can increase the length of the array, by creating a new duration vaults.
consider making the durationVaultsByToken public for safe on-chain reads
| function allocationsActive() public view override returns (bool) { | ||
| return _state == VaultState.ALLOCATIONS; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note that this doesn't always means that allocation on AM is active, since we have a delay of minWithdrawalDelayBlocks()
| /// Storage slots used: vaultAdmin (1) + duration/lockedAt/unlockAt/maturedAt/_state (packed, 1) + | ||
| /// metadataURI (1) + _operatorSet (2) + maxPerDeposit (1) + maxTotalDeposits (1) = 8. | ||
| /// Gap: 50 - 8 = 42. | ||
| uint256[42] private __gap; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
__gap should be 44 , (50 - 6 )
| assertTrue( | ||
| address(Env.impl.strategyManager()) != address(0), "StrategyManager implementation should be deployed" | ||
| ); | ||
| assertTrue( | ||
| address(Env.impl.strategyManager().delegation()) == address(Env.proxy.delegationManager()), | ||
| "StrategyManager: delegationManager mismatch" | ||
| ); | ||
|
|
||
| // Validate EigenStrategy | ||
| assertTrue(address(Env.impl.eigenStrategy()) != address(0), "EigenStrategy implementation should be deployed"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If deployment fails silently, Env.impl.* falls back to the old mainnet address from environment variables which won't be address(0) for most
- better to add :
function _validateDeployedContracts() internal view {
+ assertEq(deploys().length, 7, "Deployed contracts should be 7");
Motivation:
Explain here the context, and why you're making that change. What is the problem you're trying to solve.
Modifications:
Describe the modifications you've done.
Result:
After your change, what will change.