From 8dd948d6dbb8203b43c498197a368f00920297fb Mon Sep 17 00:00:00 2001 From: Kamil Mukhametzyanov Date: Wed, 18 Jun 2025 15:51:32 +0300 Subject: [PATCH 01/35] Fix issue: Storage collision due to increasing the storage gap --- projects/vaults/contracts/adapter-handler/AdapterHandler.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/vaults/contracts/adapter-handler/AdapterHandler.sol b/projects/vaults/contracts/adapter-handler/AdapterHandler.sol index cd3ff2b8..d4ac852c 100644 --- a/projects/vaults/contracts/adapter-handler/AdapterHandler.sol +++ b/projects/vaults/contracts/adapter-handler/AdapterHandler.sol @@ -90,7 +90,7 @@ contract AdapterHandler is InceptionAssetsHandler, IAdapterHandler { * @dev Reserved storage gap to allow for future upgrades without shifting storage layout. * @notice Occupies 38 slots (50 total slots minus 12 used). */ - uint256[50 - 11] private __gap; + uint256[50 - 12] private __gap; modifier onlyOperator() { require(msg.sender == _operator, OnlyOperatorAllowed()); From babaa6fd4e4476d03607697548215140bec6789a Mon Sep 17 00:00:00 2001 From: Kamil Mukhametzyanov Date: Wed, 18 Jun 2025 15:55:05 +0300 Subject: [PATCH 02/35] Fix issue: Emergency withdrawals that the MellowVault processes instantly will cause accounting issues --- .../vaults/contracts/adapters/InceptionWstETHMellowAdapter.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/vaults/contracts/adapters/InceptionWstETHMellowAdapter.sol b/projects/vaults/contracts/adapters/InceptionWstETHMellowAdapter.sol index e5951e0f..71e845b1 100644 --- a/projects/vaults/contracts/adapters/InceptionWstETHMellowAdapter.sol +++ b/projects/vaults/contracts/adapters/InceptionWstETHMellowAdapter.sol @@ -222,7 +222,7 @@ contract InceptionWstETHMellowAdapter is ); } - if (undelegatedAmount == 0) _removePendingClaimer(claimer); + if (undelegatedAmount == 0 && !emergency) _removePendingClaimer(claimer); emit MellowWithdrawn(undelegatedAmount, claimedAmount, claimer); return (undelegatedAmount, claimedAmount); From 262bc5ac29d5ee410e6d39c0e8a3e4a3fb58330d Mon Sep 17 00:00:00 2001 From: Kamil Mukhametzyanov Date: Wed, 18 Jun 2025 15:57:13 +0300 Subject: [PATCH 03/35] Fix issue: The ratioFeed shall be legacy --- .../vaults/Symbiotic/InceptionVault_S.sol | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/projects/vaults/contracts/vaults/Symbiotic/InceptionVault_S.sol b/projects/vaults/contracts/vaults/Symbiotic/InceptionVault_S.sol index 606ca221..37e365f7 100644 --- a/projects/vaults/contracts/vaults/Symbiotic/InceptionVault_S.sol +++ b/projects/vaults/contracts/vaults/Symbiotic/InceptionVault_S.sol @@ -5,7 +5,6 @@ import {AdapterHandler, IERC20} from "../../adapter-handler/AdapterHandler.sol"; import {Convert} from "../../lib/Convert.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import {IInceptionRatioFeed} from "../../interfaces/common/IInceptionRatioFeed.sol"; import {IInceptionToken} from "../../interfaces/common/IInceptionToken.sol"; import {IInceptionVault_S} from "../../interfaces/symbiotic-vault/IInceptionVault_S.sol"; import {InceptionLibrary} from "../../lib/InceptionLibrary.sol"; @@ -37,7 +36,7 @@ contract InceptionVault_S is AdapterHandler, IInceptionVault_S { /// @dev 100% uint64 public constant MAX_PERCENT = 100 * 1e8; - IInceptionRatioFeed public ratioFeed; + address private __deprecated_ratioFeed; address public treasury; uint64 public protocolFee; @@ -732,17 +731,6 @@ contract InceptionVault_S is AdapterHandler, IInceptionVault_S { treasury = newTreasury; } - /** - * @dev Sets the ratio feed - * @param newRatioFeed New ratio feed address - */ - function setRatioFeed(IInceptionRatioFeed newRatioFeed) external onlyOwner { - if (address(newRatioFeed) == address(0)) revert NullParams(); - - emit RatioFeedChanged(address(ratioFeed), address(newRatioFeed)); - ratioFeed = newRatioFeed; - } - /** * @dev Sets the operator address * @param newOperator New operator address From 4005e018d7eee1c59274b35a360823ebf38392ae Mon Sep 17 00:00:00 2001 From: Kamil Mukhametzyanov Date: Wed, 18 Jun 2025 16:08:24 +0300 Subject: [PATCH 04/35] Fix issue: Withdrawal Epoch Can Get Permanently Stuck if a Symbiotic Vault Is 100% Slashed --- .../interfaces/common/IWithdrawalQueue.sol | 3 --- .../withdrawal-queue/WithdrawalQueue.sol | 15 ++------------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/projects/vaults/contracts/interfaces/common/IWithdrawalQueue.sol b/projects/vaults/contracts/interfaces/common/IWithdrawalQueue.sol index 9aff046e..760a7945 100644 --- a/projects/vaults/contracts/interfaces/common/IWithdrawalQueue.sol +++ b/projects/vaults/contracts/interfaces/common/IWithdrawalQueue.sol @@ -24,9 +24,6 @@ interface IWithdrawalQueue is IWithdrawalQueueErrors { mapping(address => uint256) userShares; mapping(address => mapping(address => uint256)) adapterUndelegated; - - uint8 adaptersUndelegatedCounter; - uint8 adaptersClaimedCounter; } /* diff --git a/projects/vaults/contracts/withdrawal-queue/WithdrawalQueue.sol b/projects/vaults/contracts/withdrawal-queue/WithdrawalQueue.sol index 76c74714..dd842ec7 100644 --- a/projects/vaults/contracts/withdrawal-queue/WithdrawalQueue.sol +++ b/projects/vaults/contracts/withdrawal-queue/WithdrawalQueue.sol @@ -197,15 +197,10 @@ contract WithdrawalQueue is // update withdrawal data withdrawal.adapterUndelegated[adapter][vault] = undelegatedAmount; withdrawal.totalUndelegatedAmount += undelegatedAmount; - withdrawal.adaptersUndelegatedCounter++; if (claimedAmount > 0) { withdrawal.totalClaimedAmount += claimedAmount; } - - if (claimedAmount > 0 && undelegatedAmount == 0) { - withdrawal.adaptersClaimedCounter++; - } } /** @@ -285,7 +280,6 @@ contract WithdrawalQueue is // update withdrawal state withdrawal.totalClaimedAmount += claimedAmount; - withdrawal.adaptersClaimedCounter++; } /** @@ -300,8 +294,6 @@ contract WithdrawalQueue is address[] calldata adapters, address[] calldata vaults ) internal { - require(withdrawal.adaptersClaimedCounter == withdrawal.adaptersUndelegatedCounter, ClaimNotCompleted()); - _isSlashed(withdrawal) ? _resetEpoch(epoch, withdrawal, adapters, vaults) : _makeRedeemable(withdrawal); @@ -331,8 +323,7 @@ contract WithdrawalQueue is /** * @notice Marks a withdrawal epoch as redeemable and updates global state - * @dev Ensures all adapters have completed claiming by checking if the claimed counter equals the undelegated counter. - * Sets the epoch as redeemable, updates the total redeemable amount, and reduces the total shares queued for withdrawal + * @dev Sets the epoch as redeemable, updates the total redeemable amount, and reduces the total shares queued for withdrawal * @param withdrawal The storage reference to the withdrawal epoch */ function _makeRedeemable(WithdrawalEpoch storage withdrawal) internal { @@ -343,7 +334,7 @@ contract WithdrawalQueue is /** * @notice Resets the state of a withdrawal epoch to its initial values. - * @dev Clears the total claimed amount, total undelegated amount, and adapter counters for the specified withdrawal epoch. + * @dev Clears the total claimed amount, total undelegated amount for the specified withdrawal epoch. * @param withdrawal The storage reference to the WithdrawalEpoch struct to be refreshed. * @param adapters Array of adapter addresses * @param vaults Array of vault addresses @@ -356,8 +347,6 @@ contract WithdrawalQueue is ) internal { withdrawal.totalClaimedAmount = 0; withdrawal.totalUndelegatedAmount = 0; - withdrawal.adaptersClaimedCounter = 0; - withdrawal.adaptersUndelegatedCounter = 0; for (uint256 i = 0; i < adapters.length; i++) { delete withdrawal.adapterUndelegated[adapters[i]][vaults[i]]; From 95116ed759588c5048e37fcca14264e976251cf5 Mon Sep 17 00:00:00 2001 From: Kamil Mukhametzyanov Date: Wed, 18 Jun 2025 16:09:16 +0300 Subject: [PATCH 05/35] Fix issue: All Epochs Incorrectly Marked as "Slashed" Due to Yield and Rounding --- .../vaults/contracts/withdrawal-queue/WithdrawalQueue.sol | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/projects/vaults/contracts/withdrawal-queue/WithdrawalQueue.sol b/projects/vaults/contracts/withdrawal-queue/WithdrawalQueue.sol index dd842ec7..2222247a 100644 --- a/projects/vaults/contracts/withdrawal-queue/WithdrawalQueue.sol +++ b/projects/vaults/contracts/withdrawal-queue/WithdrawalQueue.sol @@ -308,7 +308,11 @@ contract WithdrawalQueue is function _isSlashed(WithdrawalEpoch storage withdrawal) internal view returns (bool) { uint256 currentAmount = IERC4626(inceptionVault).convertToAssets(withdrawal.totalRequestedShares); - if (withdrawal.totalClaimedAmount >= withdrawal.totalUndelegatedAmount) { + if ( + withdrawal.totalClaimedAmount >= withdrawal.totalUndelegatedAmount ? + withdrawal.totalClaimedAmount - withdrawal.totalUndelegatedAmount <= MAX_CONVERT_THRESHOLD : + withdrawal.totalUndelegatedAmount - withdrawal.totalClaimedAmount <= MAX_CONVERT_THRESHOLD + ) { if (currentAmount < withdrawal.totalClaimedAmount && withdrawal.totalClaimedAmount - currentAmount > MAX_CONVERT_THRESHOLD) { return true; } From 5eea8d46b088d372ff38f6962d3d9fcae1724544 Mon Sep 17 00:00:00 2001 From: Kamil Mukhametzyanov Date: Wed, 18 Jun 2025 16:11:56 +0300 Subject: [PATCH 06/35] Fix issue: Some functions can be griefed via a tiny donation --- .../contracts/adapter-handler/AdapterHandler.sol | 5 +++-- .../contracts/adapters/InceptionSymbioticAdapter.sol | 6 ++++-- .../adapters/InceptionWstETHMellowAdapter.sol | 11 +++++++---- .../contracts/vaults/Symbiotic/InceptionVault_S.sol | 6 +++--- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/projects/vaults/contracts/adapter-handler/AdapterHandler.sol b/projects/vaults/contracts/adapter-handler/AdapterHandler.sol index d4ac852c..b3c57e56 100644 --- a/projects/vaults/contracts/adapter-handler/AdapterHandler.sol +++ b/projects/vaults/contracts/adapter-handler/AdapterHandler.sol @@ -516,10 +516,11 @@ contract AdapterHandler is InceptionAssetsHandler, IAdapterHandler { /** * @notice Removes an adapter from the system * @param adapter Address of the adapter to remove + * @param skipEmptyCheck Skip check adapter to empty */ - function removeAdapter(address adapter) external onlyOwner { + function removeAdapter(address adapter, bool skipEmptyCheck) external onlyOwner { require(_adapters.contains(adapter), AdapterNotFound()); - require(IInceptionBaseAdapter(adapter).getTotalBalance() == 0, AdapterNotEmpty()); + require(skipEmptyCheck || IInceptionBaseAdapter(adapter).getTotalBalance() == 0, AdapterNotEmpty()); emit AdapterRemoved(adapter); _adapters.remove(adapter); diff --git a/projects/vaults/contracts/adapters/InceptionSymbioticAdapter.sol b/projects/vaults/contracts/adapters/InceptionSymbioticAdapter.sol index 52a21835..845baa4f 100644 --- a/projects/vaults/contracts/adapters/InceptionSymbioticAdapter.sol +++ b/projects/vaults/contracts/adapters/InceptionSymbioticAdapter.sol @@ -343,17 +343,19 @@ contract InceptionSymbioticAdapter is /** * @notice Removes a vault from the adapter * @param vaultAddress Address of the vault to remove + * @param skipEmptyCheck Skip check vault to empty */ - function removeVault(address vaultAddress) external onlyOwner { + function removeVault(address vaultAddress, bool skipEmptyCheck) external onlyOwner { require(vaultAddress != address(0), ZeroAddress()); require(Address.isContract(vaultAddress), NotContract()); require(_symbioticVaults.contains(vaultAddress), NotAdded()); if ( + !skipEmptyCheck && ( getDeposited(vaultAddress) != 0 || _pendingWithdrawalAmount(vaultAddress, false) > 0 || _pendingWithdrawalAmount(vaultAddress, true) > 0 - ) revert VaultNotEmpty(); + )) revert VaultNotEmpty(); _symbioticVaults.remove(vaultAddress); diff --git a/projects/vaults/contracts/adapters/InceptionWstETHMellowAdapter.sol b/projects/vaults/contracts/adapters/InceptionWstETHMellowAdapter.sol index 71e845b1..1423e444 100644 --- a/projects/vaults/contracts/adapters/InceptionWstETHMellowAdapter.sol +++ b/projects/vaults/contracts/adapters/InceptionWstETHMellowAdapter.sol @@ -281,13 +281,16 @@ contract InceptionWstETHMellowAdapter is /** * @notice Remove a Mellow vault from the adapter * @param vault Address of the mellow vault to be removed + * @param skipEmptyCheck Skip check vault to empty */ - function removeVault(address vault) external onlyOwner { + function removeVault(address vault, bool skipEmptyCheck) external onlyOwner { require(vault != address(0), ZeroAddress()); require( - getDeposited(vault) == 0 && - pendingWithdrawalAmount(vault, true) == 0 && - pendingWithdrawalAmount(vault, false) == 0, + skipEmptyCheck || ( + getDeposited(vault) == 0 && + pendingWithdrawalAmount(vault, true) == 0 && + pendingWithdrawalAmount(vault, false) == 0 + ), VaultNotEmpty() ); diff --git a/projects/vaults/contracts/vaults/Symbiotic/InceptionVault_S.sol b/projects/vaults/contracts/vaults/Symbiotic/InceptionVault_S.sol index 37e365f7..8bb57b29 100644 --- a/projects/vaults/contracts/vaults/Symbiotic/InceptionVault_S.sol +++ b/projects/vaults/contracts/vaults/Symbiotic/InceptionVault_S.sol @@ -470,10 +470,10 @@ contract InceptionVault_S is AdapterHandler, IInceptionVault_S { /** * @dev Migrates deposit bonus to a new vault - * @param newVault Address of the new vault + * @param skipEmptyCheck Skip check vault to empty */ - function migrateDepositBonus(address newVault) external onlyOwner { - require(getTotalDelegated() == 0, ValueZero()); + function migrateDepositBonus(address newVault, bool skipEmptyCheck) external onlyOwner { + require(skipEmptyCheck || getTotalDelegated() == 0, ValueZero()); require(newVault != address(0), InvalidAddress()); require(depositBonusAmount > 0, NullParams()); From 4113093952b3c63367598edb35c951adfa52b627 Mon Sep 17 00:00:00 2001 From: Kamil Mukhametzyanov Date: Wed, 18 Jun 2025 16:15:49 +0300 Subject: [PATCH 07/35] Fix issue: withdraw() will queue less tokens than expected --- .../contracts/adapters/InceptionEigenAdapter.sol | 12 +++++++++--- .../adapters/InceptionEigenAdapterWrap.sol | 12 +++++++++--- .../eigen-core/IDelegationManager.sol | 14 ++++++++++++++ 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/projects/vaults/contracts/adapters/InceptionEigenAdapter.sol b/projects/vaults/contracts/adapters/InceptionEigenAdapter.sol index 9c3b3f8a..162434f3 100644 --- a/projects/vaults/contracts/adapters/InceptionEigenAdapter.sol +++ b/projects/vaults/contracts/adapters/InceptionEigenAdapter.sol @@ -173,13 +173,19 @@ contract InceptionEigenAdapter is InceptionBaseAdapter, IInceptionEigenLayerAdap ) external override onlyTrustee whenNotPaused returns (uint256, uint256) { require(_data.length == 0, InvalidDataLength(0, _data.length)); - uint256[] memory sharesToWithdraw = new uint256[](1); + address staker = address(this); + uint256[] memory withdrawableShares = new uint256[](1); IStrategy[] memory strategies = new IStrategy[](1); strategies[0] = _strategy; - sharesToWithdraw[0] = _strategy.underlyingToShares(amount); + withdrawableShares[0] = _strategy.underlyingToShares(amount); + + uint256[] memory sharesToWithdraw = _delegationManager.convertToDepositShares( + staker, + strategies, + withdrawableShares + ); - address staker = address(this); uint256 nonce = _delegationManager.cumulativeWithdrawalsQueued(staker); if (emergency) _emergencyQueuedWithdrawals[nonce] = true; diff --git a/projects/vaults/contracts/adapters/InceptionEigenAdapterWrap.sol b/projects/vaults/contracts/adapters/InceptionEigenAdapterWrap.sol index 4fce3943..2f7daa17 100644 --- a/projects/vaults/contracts/adapters/InceptionEigenAdapterWrap.sol +++ b/projects/vaults/contracts/adapters/InceptionEigenAdapterWrap.sol @@ -180,15 +180,21 @@ contract InceptionEigenAdapterWrap is InceptionBaseAdapter, IInceptionEigenLayer ) external override onlyTrustee whenNotPaused returns (uint256, uint256) { require(_data.length == 0, InvalidDataLength(0, _data.length)); - uint256[] memory sharesToWithdraw = new uint256[](1); + address staker = address(this); + uint256[] memory withdrawableShares = new uint256[](1); IStrategy[] memory strategies = new IStrategy[](1); strategies[0] = _strategy; - sharesToWithdraw[0] = _strategy.underlyingToShares( + withdrawableShares[0] = _strategy.underlyingToShares( wrappedAsset().getStETHByWstETH(amount) ); - address staker = address(this); + uint256[] memory sharesToWithdraw = _delegationManager.convertToDepositShares( + staker, + strategies, + withdrawableShares + ); + uint256 nonce = _delegationManager.cumulativeWithdrawalsQueued(staker); if (emergency) _emergencyQueuedWithdrawals[nonce] = true; diff --git a/projects/vaults/contracts/interfaces/eigenlayer-vault/eigen-core/IDelegationManager.sol b/projects/vaults/contracts/interfaces/eigenlayer-vault/eigen-core/IDelegationManager.sol index 3cae9262..bc64eaa4 100644 --- a/projects/vaults/contracts/interfaces/eigenlayer-vault/eigen-core/IDelegationManager.sol +++ b/projects/vaults/contracts/interfaces/eigenlayer-vault/eigen-core/IDelegationManager.sol @@ -116,4 +116,18 @@ interface IDelegationManager { address staker ) external view returns (bytes32[] memory); + /** + * @notice Converts shares for a set of strategies to deposit shares, likely in order to input into `queueWithdrawals`. + * This function will revert from a division by 0 error if any of the staker's strategies have a slashing factor of 0. + * @param staker the staker to convert shares for + * @param strategies the strategies to convert shares for + * @param withdrawableShares the shares to convert + * @return the deposit shares + * @dev will be a few wei off due to rounding errors + */ + function convertToDepositShares( + address staker, + IStrategy[] memory strategies, + uint256[] memory withdrawableShares + ) external view returns (uint256[] memory); } From 4c3b3ea2efbf0e429c008f3315c96e02265938ad Mon Sep 17 00:00:00 2001 From: Kamil Mukhametzyanov Date: Wed, 18 Jun 2025 16:16:24 +0300 Subject: [PATCH 08/35] Fix issue: Wrong event emission, the old reward treasury is emitted instead of the new one --- .../vaults/contracts/assets-handler/InceptionAssetsHandler.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/vaults/contracts/assets-handler/InceptionAssetsHandler.sol b/projects/vaults/contracts/assets-handler/InceptionAssetsHandler.sol index 170bce97..2df116f0 100644 --- a/projects/vaults/contracts/assets-handler/InceptionAssetsHandler.sol +++ b/projects/vaults/contracts/assets-handler/InceptionAssetsHandler.sol @@ -65,7 +65,7 @@ contract InceptionAssetsHandler is function setRewardsTreasury(address treasury) external onlyOwner { require(treasury != address(0), NullParams()); - emit SetRewardsTreasury(rewardsTreasury); + emit SetRewardsTreasury(treasury); rewardsTreasury = treasury; } From ec657f96e42b093240392864c4cc20cfa2cbb897 Mon Sep 17 00:00:00 2001 From: Kamil Mukhametzyanov Date: Wed, 18 Jun 2025 16:16:52 +0300 Subject: [PATCH 09/35] Fix issue: Precision loss due to division before multiplication --- .../vaults/contracts/assets-handler/InceptionAssetsHandler.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/vaults/contracts/assets-handler/InceptionAssetsHandler.sol b/projects/vaults/contracts/assets-handler/InceptionAssetsHandler.sol index 2df116f0..daaf6e59 100644 --- a/projects/vaults/contracts/assets-handler/InceptionAssetsHandler.sol +++ b/projects/vaults/contracts/assets-handler/InceptionAssetsHandler.sol @@ -54,7 +54,7 @@ contract InceptionAssetsHandler is uint256 elapsedDays = (block.timestamp - startTimeline) / 1 days; uint256 totalDays = rewardsTimeline / 1 days; if (elapsedDays > totalDays) return _asset.balanceOf(address(this)); - uint256 reservedRewards = (currentRewards / totalDays) * (totalDays - elapsedDays); + uint256 reservedRewards = (currentRewards * (totalDays - elapsedDays)) / totalDays; return (_asset.balanceOf(address(this)) - reservedRewards); } From 2593dfe0ead9f6e3ffc378210b50e6a9e1152014 Mon Sep 17 00:00:00 2001 From: Kamil Mukhametzyanov Date: Wed, 18 Jun 2025 16:18:15 +0300 Subject: [PATCH 10/35] Fix issue: DoS When Depositing Into Strategies Deployed via EigenLayer Factory --- .../contracts/adapters/InceptionEigenAdapter.sol | 16 ---------------- .../adapters/InceptionEigenAdapterWrap.sol | 16 ---------------- 2 files changed, 32 deletions(-) diff --git a/projects/vaults/contracts/adapters/InceptionEigenAdapter.sol b/projects/vaults/contracts/adapters/InceptionEigenAdapter.sol index 162434f3..b22c3fd5 100644 --- a/projects/vaults/contracts/adapters/InceptionEigenAdapter.sol +++ b/projects/vaults/contracts/adapters/InceptionEigenAdapter.sol @@ -63,20 +63,6 @@ contract InceptionEigenAdapter is InceptionBaseAdapter, IInceptionEigenLayerAdap _asset.safeApprove(strategyManager, type(uint256).max); } - /** - * @dev checks whether it's still possible to deposit into the strategy - * @param amount Amount of tokens to delegate/deposit - * @notice Be cautious when using this function, as certain strategies may not enforce TVL limits by inheritance. - */ - function _beforeDepositAssetIntoStrategy(uint256 amount) internal view { - (uint256 maxPerDeposit, uint256 maxTotalDeposits) = _strategy.getTVLLimits(); - - require(amount <= maxPerDeposit, ExceedsMaxPerDeposit(maxPerDeposit, amount)); - - uint256 currentBalance = _asset.balanceOf(address(_strategy)); - require(currentBalance + amount <= maxTotalDeposits, ExceedsMaxTotalDeposited(maxTotalDeposits, currentBalance)); - } - /** * @notice Delegates funds to an operator or deposits into strategy * @dev If operator is zero address and amount > 0, deposits into strategy @@ -92,8 +78,6 @@ contract InceptionEigenAdapter is InceptionBaseAdapter, IInceptionEigenLayerAdap ) external override onlyTrustee whenNotPaused returns (uint256) { // depositIntoStrategy if (amount > 0 && operator == address(0)) { - _beforeDepositAssetIntoStrategy(amount); - // transfer from the vault _asset.safeTransferFrom(msg.sender, address(this), amount); // deposit the asset to the appropriate strategy diff --git a/projects/vaults/contracts/adapters/InceptionEigenAdapterWrap.sol b/projects/vaults/contracts/adapters/InceptionEigenAdapterWrap.sol index 2f7daa17..4e6a0c3f 100644 --- a/projects/vaults/contracts/adapters/InceptionEigenAdapterWrap.sol +++ b/projects/vaults/contracts/adapters/InceptionEigenAdapterWrap.sol @@ -67,20 +67,6 @@ contract InceptionEigenAdapterWrap is InceptionBaseAdapter, IInceptionEigenLayer wrappedAsset().stETH().approve(strategyManager, type(uint256).max); } - /** - * @dev checks whether it's still possible to deposit into the strategy - * @param amount Amount of tokens to delegate/deposit - * @notice Be cautious when using this function, as certain strategies may not enforce TVL limits by inheritance. - */ - function _beforeDepositAssetIntoStrategy(uint256 amount) internal view { - (uint256 maxPerDeposit, uint256 maxTotalDeposits) = _strategy.getTVLLimits(); - - require(amount <= maxPerDeposit, ExceedsMaxPerDeposit(maxPerDeposit, amount)); - - uint256 currentBalance = _asset.balanceOf(address(_strategy)); - require(currentBalance + amount <= maxTotalDeposits, ExceedsMaxTotalDeposited(maxTotalDeposits, currentBalance)); - } - /** * @notice Delegates funds to an operator or deposits into strategy * @dev If operator is zero address and amount > 0, deposits into strategy @@ -96,8 +82,6 @@ contract InceptionEigenAdapterWrap is InceptionBaseAdapter, IInceptionEigenLayer ) external override onlyTrustee whenNotPaused returns (uint256) { // depositIntoStrategy if (amount > 0 && operator == address(0)) { - _beforeDepositAssetIntoStrategy(amount); - // transfer from the vault _asset.safeTransferFrom(msg.sender, address(this), amount); amount = wrappedAsset().unwrap(amount); From 38ba9b51b1c816f3a504f3de5f20fecab4943cef Mon Sep 17 00:00:00 2001 From: Kamil Mukhametzyanov Date: Wed, 18 Jun 2025 16:19:35 +0300 Subject: [PATCH 11/35] Fix issue: The withdrawer field in QueuedWithdrawalParams is now deprecated --- projects/vaults/contracts/adapters/InceptionEigenAdapter.sol | 2 +- .../vaults/contracts/adapters/InceptionEigenAdapterWrap.sol | 2 +- .../eigenlayer-vault/eigen-core/IDelegationManager.sol | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/projects/vaults/contracts/adapters/InceptionEigenAdapter.sol b/projects/vaults/contracts/adapters/InceptionEigenAdapter.sol index b22c3fd5..b8a1d425 100644 --- a/projects/vaults/contracts/adapters/InceptionEigenAdapter.sol +++ b/projects/vaults/contracts/adapters/InceptionEigenAdapter.sol @@ -179,7 +179,7 @@ contract InceptionEigenAdapter is InceptionBaseAdapter, IInceptionEigenLayerAdap withdrawals[0] = IDelegationManager.QueuedWithdrawalParams({ strategies: strategies, shares: sharesToWithdraw, - withdrawer: staker + __deprecated_withdrawer: staker }); // queue from EL diff --git a/projects/vaults/contracts/adapters/InceptionEigenAdapterWrap.sol b/projects/vaults/contracts/adapters/InceptionEigenAdapterWrap.sol index 4e6a0c3f..f34f49a6 100644 --- a/projects/vaults/contracts/adapters/InceptionEigenAdapterWrap.sol +++ b/projects/vaults/contracts/adapters/InceptionEigenAdapterWrap.sol @@ -188,7 +188,7 @@ contract InceptionEigenAdapterWrap is InceptionBaseAdapter, IInceptionEigenLayer withdrawals[0] = IDelegationManager.QueuedWithdrawalParams({ strategies: strategies, shares: sharesToWithdraw, - withdrawer: staker + __deprecated_withdrawer: staker }); // queue withdrawal from EL diff --git a/projects/vaults/contracts/interfaces/eigenlayer-vault/eigen-core/IDelegationManager.sol b/projects/vaults/contracts/interfaces/eigenlayer-vault/eigen-core/IDelegationManager.sol index bc64eaa4..413f7745 100644 --- a/projects/vaults/contracts/interfaces/eigenlayer-vault/eigen-core/IDelegationManager.sol +++ b/projects/vaults/contracts/interfaces/eigenlayer-vault/eigen-core/IDelegationManager.sol @@ -28,7 +28,7 @@ interface IDelegationManager { // Array containing the amount of shares in each Strategy in the `strategies` array uint256[] shares; // The address of the withdrawer - address withdrawer; + address __deprecated_withdrawer; } struct Withdrawal { From a9f27f921abdda0e9b73d67280eee7e393a18031 Mon Sep 17 00:00:00 2001 From: Kamil Mukhametzyanov Date: Wed, 18 Jun 2025 16:20:19 +0300 Subject: [PATCH 12/35] Fix issue: Unnecessary approval to strategyManager --- projects/vaults/contracts/adapters/InceptionEigenAdapterWrap.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/projects/vaults/contracts/adapters/InceptionEigenAdapterWrap.sol b/projects/vaults/contracts/adapters/InceptionEigenAdapterWrap.sol index f34f49a6..a4a06968 100644 --- a/projects/vaults/contracts/adapters/InceptionEigenAdapterWrap.sol +++ b/projects/vaults/contracts/adapters/InceptionEigenAdapterWrap.sol @@ -63,7 +63,6 @@ contract InceptionEigenAdapterWrap is InceptionBaseAdapter, IInceptionEigenLayer _setRewardsCoordinator(rewardCoordinator, claimer); // approve spending by strategyManager - _asset.safeApprove(strategyManager, type(uint256).max); wrappedAsset().stETH().approve(strategyManager, type(uint256).max); } From 4f79f4cfdf8e63c3d1f296d8505b6472d6a9582a Mon Sep 17 00:00:00 2001 From: Kamil Mukhametzyanov Date: Wed, 18 Jun 2025 16:21:32 +0300 Subject: [PATCH 13/35] =?UTF-8?q?Fix=20issue:=20claimableWithdrawalAmount?= =?UTF-8?q?=20can=E2=80=99t=20query=20the=20claimableAssetsOf()=20for=20em?= =?UTF-8?q?ergency=20withdrawals?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../contracts/adapters/InceptionWstETHMellowAdapter.sol | 5 +++-- .../interfaces/adapters/IInceptionMellowAdapter.sol | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/projects/vaults/contracts/adapters/InceptionWstETHMellowAdapter.sol b/projects/vaults/contracts/adapters/InceptionWstETHMellowAdapter.sol index 1423e444..b9d42672 100644 --- a/projects/vaults/contracts/adapters/InceptionWstETHMellowAdapter.sol +++ b/projects/vaults/contracts/adapters/InceptionWstETHMellowAdapter.sol @@ -348,10 +348,11 @@ contract InceptionWstETHMellowAdapter is /** * @notice Returns the total amount available for withdrawal + * @param emergency Emergency flag for claimer * @return total Amount that can be claimed */ - function claimableWithdrawalAmount() public view returns (uint256 total) { - return _claimableWithdrawalAmount(false); + function claimableWithdrawalAmount(bool emergency) public view returns (uint256 total) { + return _claimableWithdrawalAmount(emergency); } /** diff --git a/projects/vaults/contracts/interfaces/adapters/IInceptionMellowAdapter.sol b/projects/vaults/contracts/interfaces/adapters/IInceptionMellowAdapter.sol index 521331c8..b65fba5b 100644 --- a/projects/vaults/contracts/interfaces/adapters/IInceptionMellowAdapter.sol +++ b/projects/vaults/contracts/interfaces/adapters/IInceptionMellowAdapter.sol @@ -31,5 +31,5 @@ interface IInceptionMellowAdapter is IInceptionBaseAdapter { event MellowWithdrawn(uint256 amount, uint256 claimedAmount, address claimer); - function claimableWithdrawalAmount() external view returns (uint256); + function claimableWithdrawalAmount(bool emergency) external view returns (uint256); } From 18ec23244724f5e5c378159bbc15d5be642eb4e1 Mon Sep 17 00:00:00 2001 From: Kamil Mukhametzyanov Date: Wed, 18 Jun 2025 16:22:46 +0300 Subject: [PATCH 14/35] Fix issue: claimAdapterFreeBalance and claimAdapterRewards are missing the whenNotPaused modifier --- projects/vaults/contracts/adapter-handler/AdapterHandler.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/vaults/contracts/adapter-handler/AdapterHandler.sol b/projects/vaults/contracts/adapter-handler/AdapterHandler.sol index b3c57e56..5862afa3 100644 --- a/projects/vaults/contracts/adapter-handler/AdapterHandler.sol +++ b/projects/vaults/contracts/adapter-handler/AdapterHandler.sol @@ -295,7 +295,7 @@ contract AdapterHandler is InceptionAssetsHandler, IAdapterHandler { /** * @notice Claims the free balance from a specified adapter contract. - * @dev Can only be called by an operator, when the contract is not paused, and is non-reentrant. + * @dev Can only be called by an operator and when is non-reentrant. * @param adapter The address of the adapter contract from which to claim the free balance. */ function claimAdapterFreeBalance(address adapter) external onlyOperator nonReentrant { @@ -307,7 +307,7 @@ contract AdapterHandler is InceptionAssetsHandler, IAdapterHandler { * @notice Claim and transfer rewards from the specified adapter to the rewards treasury. * The treasury may optionally swap the received tokens and forward them to the operator * for further distribution to the vault as additional rewards. - * @dev Can only be called by an operator, when the contract is not paused, and is non-reentrant. + * @dev Can only be called by an operator and when is non-reentrant. * @param adapter The address of the adapter contract from which to claim rewards. * @param token Reward token. * @param rewardsData Adapter related bytes of data for rewards. From 41ccbbc741ff88196c8168c8c79916333d1b8098 Mon Sep 17 00:00:00 2001 From: Kamil Mukhametzyanov Date: Wed, 18 Jun 2025 16:23:30 +0300 Subject: [PATCH 15/35] Fix issue: mint() may return more shares than requested --- projects/vaults/contracts/vaults/Symbiotic/InceptionVault_S.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/projects/vaults/contracts/vaults/Symbiotic/InceptionVault_S.sol b/projects/vaults/contracts/vaults/Symbiotic/InceptionVault_S.sol index 8bb57b29..e748a636 100644 --- a/projects/vaults/contracts/vaults/Symbiotic/InceptionVault_S.sol +++ b/projects/vaults/contracts/vaults/Symbiotic/InceptionVault_S.sol @@ -234,6 +234,7 @@ contract InceptionVault_S is AdapterHandler, IInceptionVault_S { /** * @dev Mints shares for assets. See {IERC4626-mint}. + * @notice It can return more assets than expected because of deposit bonus. * @param shares Amount of shares to mint * @param receiver Address to receive the minted shares * @return Amount of assets deposited From e57779c67067ae6208d21d4ddd24cd6715e31869 Mon Sep 17 00:00:00 2001 From: Kamil Mukhametzyanov Date: Wed, 18 Jun 2025 16:26:23 +0300 Subject: [PATCH 16/35] Fix issue: Funds from Mellow Withdrawals Can Be Taken by Other Users --- .../contracts/adapter-handler/AdapterHandler.sol | 2 +- .../assets-handler/InceptionAssetsHandler.sol | 3 ++- .../interfaces/common/IWithdrawalQueue.sol | 6 ++++++ .../contracts/withdrawal-queue/WithdrawalQueue.sol | 13 +++++++++++-- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/projects/vaults/contracts/adapter-handler/AdapterHandler.sol b/projects/vaults/contracts/adapter-handler/AdapterHandler.sol index 5862afa3..7dede375 100644 --- a/projects/vaults/contracts/adapter-handler/AdapterHandler.sol +++ b/projects/vaults/contracts/adapter-handler/AdapterHandler.sol @@ -456,7 +456,7 @@ contract AdapterHandler is InceptionAssetsHandler, IAdapterHandler { */ function getFlashCapacity() public view returns (uint256 total) { uint256 _assets = totalAssets(); - uint256 _sum = redeemReservedAmount() + depositBonusAmount; + uint256 _sum = redeemReservedAmount() + depositBonusAmount + withdrawalQueue.totalPendingRedeemAmount(); return _sum > _assets ? 0 : _assets - _sum; } diff --git a/projects/vaults/contracts/assets-handler/InceptionAssetsHandler.sol b/projects/vaults/contracts/assets-handler/InceptionAssetsHandler.sol index daaf6e59..52235c45 100644 --- a/projects/vaults/contracts/assets-handler/InceptionAssetsHandler.sol +++ b/projects/vaults/contracts/assets-handler/InceptionAssetsHandler.sol @@ -55,7 +55,8 @@ contract InceptionAssetsHandler is uint256 totalDays = rewardsTimeline / 1 days; if (elapsedDays > totalDays) return _asset.balanceOf(address(this)); uint256 reservedRewards = (currentRewards * (totalDays - elapsedDays)) / totalDays; - return (_asset.balanceOf(address(this)) - reservedRewards); + uint256 balance = _asset.balanceOf(address(this)); + return balance > reservedRewards ? balance - reservedRewards : 0; } /** diff --git a/projects/vaults/contracts/interfaces/common/IWithdrawalQueue.sol b/projects/vaults/contracts/interfaces/common/IWithdrawalQueue.sol index 760a7945..446562b6 100644 --- a/projects/vaults/contracts/interfaces/common/IWithdrawalQueue.sol +++ b/projects/vaults/contracts/interfaces/common/IWithdrawalQueue.sol @@ -113,6 +113,12 @@ interface IWithdrawalQueue is IWithdrawalQueueErrors { */ function totalAmountRedeem() external view returns (uint256); + /* + * @notice Returns the total amount reserved for future redeem + * @return The total pending redeem amount + */ + function totalPendingRedeemAmount() external view returns (uint256); + /* * @notice Returns the total pending withdrawal amount for a receiver * @param receiver The address to check diff --git a/projects/vaults/contracts/withdrawal-queue/WithdrawalQueue.sol b/projects/vaults/contracts/withdrawal-queue/WithdrawalQueue.sol index 2222247a..48270b2c 100644 --- a/projects/vaults/contracts/withdrawal-queue/WithdrawalQueue.sol +++ b/projects/vaults/contracts/withdrawal-queue/WithdrawalQueue.sol @@ -37,6 +37,7 @@ contract WithdrawalQueue is uint256 public currentEpoch; uint256 public totalAmountRedeem; uint256 public totalSharesToWithdraw; + uint256 public totalPendingRedeemAmount; modifier onlyVaultOrOwner() { require(msg.sender == inceptionVault || msg.sender == owner(), OnlyVaultOrOwnerAllowed()); @@ -199,6 +200,7 @@ contract WithdrawalQueue is withdrawal.totalUndelegatedAmount += undelegatedAmount; if (claimedAmount > 0) { + totalPendingRedeemAmount += claimedAmount; withdrawal.totalClaimedAmount += claimedAmount; } } @@ -280,6 +282,8 @@ contract WithdrawalQueue is // update withdrawal state withdrawal.totalClaimedAmount += claimedAmount; + // update global state + totalPendingRedeemAmount += claimedAmount; } /** @@ -310,8 +314,8 @@ contract WithdrawalQueue is if ( withdrawal.totalClaimedAmount >= withdrawal.totalUndelegatedAmount ? - withdrawal.totalClaimedAmount - withdrawal.totalUndelegatedAmount <= MAX_CONVERT_THRESHOLD : - withdrawal.totalUndelegatedAmount - withdrawal.totalClaimedAmount <= MAX_CONVERT_THRESHOLD + withdrawal.totalClaimedAmount - withdrawal.totalUndelegatedAmount <= MAX_CONVERT_THRESHOLD : + withdrawal.totalUndelegatedAmount - withdrawal.totalClaimedAmount <= MAX_CONVERT_THRESHOLD ) { if (currentAmount < withdrawal.totalClaimedAmount && withdrawal.totalClaimedAmount - currentAmount > MAX_CONVERT_THRESHOLD) { return true; @@ -334,6 +338,7 @@ contract WithdrawalQueue is withdrawal.ableRedeem = true; totalAmountRedeem += withdrawal.totalClaimedAmount; totalSharesToWithdraw -= withdrawal.totalRequestedShares; + totalPendingRedeemAmount -= withdrawal.totalClaimedAmount; } /** @@ -349,6 +354,8 @@ contract WithdrawalQueue is address[] calldata adapters, address[] calldata vaults ) internal { + totalPendingRedeemAmount -= withdrawal.totalClaimedAmount; + withdrawal.totalClaimedAmount = 0; withdrawal.totalUndelegatedAmount = 0; @@ -373,6 +380,8 @@ contract WithdrawalQueue is // update epoch state withdrawal.totalClaimedAmount = claimedAmount; + // update global state + totalPendingRedeemAmount += claimedAmount; _afterUndelegate(epoch, withdrawal); } From 44940eae39d736bd778b7c7b07dda2fcbbefa43b Mon Sep 17 00:00:00 2001 From: Kamil Mukhametzyanov Date: Wed, 18 Jun 2025 16:26:57 +0300 Subject: [PATCH 17/35] Fix issue: adapterUndelegated is reset on _undelegate() --- projects/vaults/contracts/withdrawal-queue/WithdrawalQueue.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/vaults/contracts/withdrawal-queue/WithdrawalQueue.sol b/projects/vaults/contracts/withdrawal-queue/WithdrawalQueue.sol index 48270b2c..11eb097e 100644 --- a/projects/vaults/contracts/withdrawal-queue/WithdrawalQueue.sol +++ b/projects/vaults/contracts/withdrawal-queue/WithdrawalQueue.sol @@ -196,7 +196,7 @@ contract WithdrawalQueue is ); // update withdrawal data - withdrawal.adapterUndelegated[adapter][vault] = undelegatedAmount; + withdrawal.adapterUndelegated[adapter][vault] += undelegatedAmount; withdrawal.totalUndelegatedAmount += undelegatedAmount; if (claimedAmount > 0) { From 35047332d8e6ec115205ee1337c12dbf5dff7781 Mon Sep 17 00:00:00 2001 From: Kamil Mukhametzyanov Date: Wed, 18 Jun 2025 16:27:31 +0300 Subject: [PATCH 18/35] Fix issue: DoS on delegate() When Depositing More stETH Than Available --- .../vaults/contracts/adapters/InceptionEigenAdapterWrap.sol | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/projects/vaults/contracts/adapters/InceptionEigenAdapterWrap.sol b/projects/vaults/contracts/adapters/InceptionEigenAdapterWrap.sol index a4a06968..ab9be6e8 100644 --- a/projects/vaults/contracts/adapters/InceptionEigenAdapterWrap.sol +++ b/projects/vaults/contracts/adapters/InceptionEigenAdapterWrap.sol @@ -83,7 +83,11 @@ contract InceptionEigenAdapterWrap is InceptionBaseAdapter, IInceptionEigenLayer if (amount > 0 && operator == address(0)) { // transfer from the vault _asset.safeTransferFrom(msg.sender, address(this), amount); - amount = wrappedAsset().unwrap(amount); + + uint256 balanceBefore = wrappedAsset().stETH().balanceOf(address(this)); + wrappedAsset().unwrap(amount); + amount = wrappedAsset().stETH().balanceOf(address(this)) - balanceBefore; + // deposit the asset to the appropriate strategy return wrappedAsset().getWstETHByStETH(_strategy.sharesToUnderlying( _strategyManager.depositIntoStrategy( From d793c9bab466c5a2984389557b02b5087d16d236 Mon Sep 17 00:00:00 2001 From: Kamil Mukhametzyanov Date: Wed, 18 Jun 2025 16:28:15 +0300 Subject: [PATCH 19/35] Fix issue: rewardsTimeline is not initialized --- projects/vaults/contracts/adapter-handler/AdapterHandler.sol | 4 +++- .../contracts/interfaces/common/IInceptionVaultErrors.sol | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/projects/vaults/contracts/adapter-handler/AdapterHandler.sol b/projects/vaults/contracts/adapter-handler/AdapterHandler.sol index 7dede375..b9b1710f 100644 --- a/projects/vaults/contracts/adapter-handler/AdapterHandler.sol +++ b/projects/vaults/contracts/adapter-handler/AdapterHandler.sol @@ -335,7 +335,9 @@ contract AdapterHandler is InceptionAssetsHandler, IAdapterHandler { * @dev The function allows the operator to deposit asset as rewards. * It verifies that the previous rewards timeline is over before accepting new rewards. */ - function addRewards(uint256 amount) external onlyOperator nonReentrant { + function addRewards(uint256 amount) external onlyOperator { + require(rewardsTimeline > 0, RewardsTimelineNotSet()); + /// @dev verify whether the prev timeline is over if (currentRewards > 0) { uint256 totalDays = rewardsTimeline / 1 days; diff --git a/projects/vaults/contracts/interfaces/common/IInceptionVaultErrors.sol b/projects/vaults/contracts/interfaces/common/IInceptionVaultErrors.sol index 80dbe03d..fdafbd73 100644 --- a/projects/vaults/contracts/interfaces/common/IInceptionVaultErrors.sol +++ b/projects/vaults/contracts/interfaces/common/IInceptionVaultErrors.sol @@ -87,4 +87,6 @@ interface IInceptionVaultErrors { error LowerThanMinOut(uint256 minOut); error RewardsTreasuryNotSet(); + + error RewardsTimelineNotSet(); } From a2967e696c9c76f24c513dd98a1216e8c1131696 Mon Sep 17 00:00:00 2001 From: Kamil Mukhametzyanov Date: Wed, 18 Jun 2025 16:29:03 +0300 Subject: [PATCH 20/35] Fix issue: migrateDepositBonus Fails to Update Bonus Tracking on Target Vault --- .../interfaces/symbiotic-vault/IInceptionVault_S.sol | 2 +- .../vaults/contracts/vaults/Symbiotic/InceptionVault_S.sol | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/projects/vaults/contracts/interfaces/symbiotic-vault/IInceptionVault_S.sol b/projects/vaults/contracts/interfaces/symbiotic-vault/IInceptionVault_S.sol index b903eec7..d70cf01a 100644 --- a/projects/vaults/contracts/interfaces/symbiotic-vault/IInceptionVault_S.sol +++ b/projects/vaults/contracts/interfaces/symbiotic-vault/IInceptionVault_S.sol @@ -78,7 +78,7 @@ interface IInceptionVault_S { event WithdrawalQueueChanged(address queue); - event DepositBonusTransferred(address newVault, uint256 amount); + event DepositBonusTransferred(uint256 amount); function inceptionToken() external view returns (IInceptionToken); diff --git a/projects/vaults/contracts/vaults/Symbiotic/InceptionVault_S.sol b/projects/vaults/contracts/vaults/Symbiotic/InceptionVault_S.sol index e748a636..dde7e2d0 100644 --- a/projects/vaults/contracts/vaults/Symbiotic/InceptionVault_S.sol +++ b/projects/vaults/contracts/vaults/Symbiotic/InceptionVault_S.sol @@ -473,17 +473,16 @@ contract InceptionVault_S is AdapterHandler, IInceptionVault_S { * @dev Migrates deposit bonus to a new vault * @param skipEmptyCheck Skip check vault to empty */ - function migrateDepositBonus(address newVault, bool skipEmptyCheck) external onlyOwner { + function migrateDepositBonus(bool skipEmptyCheck) external onlyOwner { require(skipEmptyCheck || getTotalDelegated() == 0, ValueZero()); - require(newVault != address(0), InvalidAddress()); require(depositBonusAmount > 0, NullParams()); uint256 amount = depositBonusAmount; depositBonusAmount = 0; - _asset.safeTransfer(newVault, amount); + _asset.safeTransfer(msg.sender, amount); - emit DepositBonusTransferred(newVault, amount); + emit DepositBonusTransferred(amount); } /*////////////////////////////// From 6f9a1e1e38f199884b7bc710fb4586821f16ad34 Mon Sep 17 00:00:00 2001 From: Kamil Mukhametzyanov Date: Wed, 18 Jun 2025 16:29:39 +0300 Subject: [PATCH 21/35] Fix issue: Slashing will be missed in an edge case --- .../contracts/withdrawal-queue/WithdrawalQueue.sol | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/projects/vaults/contracts/withdrawal-queue/WithdrawalQueue.sol b/projects/vaults/contracts/withdrawal-queue/WithdrawalQueue.sol index 11eb097e..706ead27 100644 --- a/projects/vaults/contracts/withdrawal-queue/WithdrawalQueue.sol +++ b/projects/vaults/contracts/withdrawal-queue/WithdrawalQueue.sol @@ -201,6 +201,7 @@ contract WithdrawalQueue is if (claimedAmount > 0) { totalPendingRedeemAmount += claimedAmount; + withdrawal.totalUndelegatedAmount += claimedAmount; withdrawal.totalClaimedAmount += claimedAmount; } } @@ -211,17 +212,16 @@ contract WithdrawalQueue is */ function _afterUndelegate(uint256 epoch, WithdrawalEpoch storage withdrawal) internal { uint256 requested = IERC4626(inceptionVault).convertToAssets(withdrawal.totalRequestedShares); - uint256 totalUndelegated = withdrawal.totalUndelegatedAmount + withdrawal.totalClaimedAmount; // ensure that the undelegated assets are relevant to the ratio require( - requested >= totalUndelegated ? - requested - totalUndelegated <= MAX_CONVERT_THRESHOLD - : totalUndelegated - requested <= MAX_CONVERT_THRESHOLD, + requested >= withdrawal.totalUndelegatedAmount ? + requested - withdrawal.totalUndelegatedAmount <= MAX_CONVERT_THRESHOLD + : withdrawal.totalUndelegatedAmount - requested <= MAX_CONVERT_THRESHOLD, UndelegateNotCompleted() ); - if (withdrawal.totalClaimedAmount > 0 && withdrawal.totalUndelegatedAmount == 0) { + if (withdrawal.totalClaimedAmount == withdrawal.totalUndelegatedAmount) { _makeRedeemable(withdrawal); } @@ -380,6 +380,7 @@ contract WithdrawalQueue is // update epoch state withdrawal.totalClaimedAmount = claimedAmount; + withdrawal.totalUndelegatedAmount = claimedAmount; // update global state totalPendingRedeemAmount += claimedAmount; From 24d0755bc406c2159b2d413c406f76597243da35 Mon Sep 17 00:00:00 2001 From: Kamil Mukhametzyanov Date: Wed, 18 Jun 2025 16:30:09 +0300 Subject: [PATCH 22/35] Fix issue: IBaseAdapter should add a storage gap --- projects/vaults/contracts/adapters/InceptionBaseAdapter.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/projects/vaults/contracts/adapters/InceptionBaseAdapter.sol b/projects/vaults/contracts/adapters/InceptionBaseAdapter.sol index 96958797..d1d44e59 100644 --- a/projects/vaults/contracts/adapters/InceptionBaseAdapter.sol +++ b/projects/vaults/contracts/adapters/InceptionBaseAdapter.sol @@ -27,6 +27,9 @@ abstract contract InceptionBaseAdapter is address internal _trusteeManager; address internal _inceptionVault; + // @notice This variable must not be used for already deployed vaults — take care during upgrades + uint256[50 - 3] private __gap; + modifier onlyTrustee() { require( msg.sender == _inceptionVault || msg.sender == _trusteeManager, From 9c3e4efa8b9c5e4c914bd56db11227b8dedbf6ab Mon Sep 17 00:00:00 2001 From: Kamil Mukhametzyanov Date: Wed, 18 Jun 2025 16:30:47 +0300 Subject: [PATCH 23/35] =?UTF-8?q?Fix=20issue:=20DoS=20when=20using=20token?= =?UTF-8?q?s=20that=20don=E2=80=99t=20return=20a=20bool=20on=20approval?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../contracts/adapter-claimers/MellowAdapterClaimer.sol | 9 ++++----- .../adapter-claimers/SymbioticAdapterClaimer.sol | 9 ++++----- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/projects/vaults/contracts/adapter-claimers/MellowAdapterClaimer.sol b/projects/vaults/contracts/adapter-claimers/MellowAdapterClaimer.sol index 26785805..b4c1bfe9 100644 --- a/projects/vaults/contracts/adapter-claimers/MellowAdapterClaimer.sol +++ b/projects/vaults/contracts/adapter-claimers/MellowAdapterClaimer.sol @@ -6,7 +6,7 @@ import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ import {ERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol"; import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {IAdapterClaimer} from "../interfaces/adapter-claimer/IAdapterClaimer.sol"; import {IMellowSymbioticVault} from "../interfaces/symbiotic-vault/mellow-core/IMellowSymbioticVault.sol"; @@ -25,6 +25,8 @@ contract MellowAdapterClaimer is OwnableUpgradeable, IAdapterClaimer { + using SafeERC20 for IERC20; + address internal _adapter; /// @custom:oz-upgrades-unsafe-allow constructor @@ -39,10 +41,7 @@ contract MellowAdapterClaimer is __ERC165_init(); _adapter = msg.sender; - require( - IERC20(asset).approve(_adapter, type(uint256).max), - ApprovalFailed() - ); + IERC20(asset).safeIncreaseAllowance(_adapter, type(uint256).max); } /** diff --git a/projects/vaults/contracts/adapter-claimers/SymbioticAdapterClaimer.sol b/projects/vaults/contracts/adapter-claimers/SymbioticAdapterClaimer.sol index a1d96ade..2c013fd2 100644 --- a/projects/vaults/contracts/adapter-claimers/SymbioticAdapterClaimer.sol +++ b/projects/vaults/contracts/adapter-claimers/SymbioticAdapterClaimer.sol @@ -6,7 +6,7 @@ import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ import {ERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol"; import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {IVault} from "../interfaces/symbiotic-vault/symbiotic-core/IVault.sol"; import {IAdapterClaimer} from "../interfaces/adapter-claimer/IAdapterClaimer.sol"; @@ -25,6 +25,8 @@ contract SymbioticAdapterClaimer is OwnableUpgradeable, IAdapterClaimer { + using SafeERC20 for IERC20; + address internal _adapter; /// @custom:oz-upgrades-unsafe-allow constructor @@ -39,10 +41,7 @@ contract SymbioticAdapterClaimer is __ERC165_init(); _adapter = msg.sender; - require( - IERC20(asset).approve(_adapter, type(uint256).max), - ApprovalFailed() - ); + IERC20(asset).safeIncreaseAllowance(_adapter, type(uint256).max); } From 5ef6fef427e2a758a764fef8b169ba9fff3a48b6 Mon Sep 17 00:00:00 2001 From: Kamil Mukhametzyanov Date: Wed, 18 Jun 2025 16:31:25 +0300 Subject: [PATCH 24/35] Fix issue: Insufficient validation when removing a Mellow vault --- .../adapters/InceptionWstETHMellowAdapter.sol | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/projects/vaults/contracts/adapters/InceptionWstETHMellowAdapter.sol b/projects/vaults/contracts/adapters/InceptionWstETHMellowAdapter.sol index b9d42672..1aba50c5 100644 --- a/projects/vaults/contracts/adapters/InceptionWstETHMellowAdapter.sol +++ b/projects/vaults/contracts/adapters/InceptionWstETHMellowAdapter.sol @@ -289,7 +289,9 @@ contract InceptionWstETHMellowAdapter is skipEmptyCheck || ( getDeposited(vault) == 0 && pendingWithdrawalAmount(vault, true) == 0 && - pendingWithdrawalAmount(vault, false) == 0 + pendingWithdrawalAmount(vault, false) == 0 && + _claimableWithdrawalAmount(vault, true) == 0 && + _claimableWithdrawalAmount(vault, false) == 0 ), VaultNotEmpty() ); @@ -356,7 +358,7 @@ contract InceptionWstETHMellowAdapter is } /** - * @notice Internal function to calculate claimable withdrawal amount for an address + * @notice Internal function to calculate claimable withdrawal amount * @param emergency Emergency flag for claimer * @return total Total claimable amount */ @@ -379,6 +381,27 @@ contract InceptionWstETHMellowAdapter is return total; } + /** + * @notice Internal function to calculate claimable withdrawal amount for given vault + * @param mellowVault Mellow vault address + * @param emergency Emergency flag for claimer + * @return total Total claimable amount + */ + function _claimableWithdrawalAmount( + address mellowVault, + bool emergency + ) internal view returns (uint256 total) { + if (emergency) { + return IMellowSymbioticVault(mellowVault).claimableAssetsOf(_emergencyClaimer); + } + + for (uint256 i = 0; i < pendingClaimers.length(); i++) { + total += IMellowSymbioticVault(mellowVault).claimableAssetsOf(pendingClaimers.at(i)); + } + + return total; + } + /** * @notice Returns the total amount of pending withdrawals * @return total Amount of pending withdrawals From d860080140e5fab3e4f7fb90aad5cbd64b4886e3 Mon Sep 17 00:00:00 2001 From: Kamil Mukhametzyanov Date: Wed, 18 Jun 2025 16:32:44 +0300 Subject: [PATCH 25/35] tests: remove ratio feed from tests --- projects/vaults/test/src/init-vault-new.ts | 2 -- projects/vaults/test/src/init-vault.ts | 2 -- .../InceptionVault_S/getters-setters.test.ts | 20 ------------------- 3 files changed, 24 deletions(-) diff --git a/projects/vaults/test/src/init-vault-new.ts b/projects/vaults/test/src/init-vault-new.ts index 5cd625b8..b6a05207 100644 --- a/projects/vaults/test/src/init-vault-new.ts +++ b/projects/vaults/test/src/init-vault-new.ts @@ -145,8 +145,6 @@ export async function initVault( const withdrawalQueue = await upgrades.deployProxy(withdrawalQueueFactory, [iVault.address, [], [], 0]); withdrawalQueue.address = await withdrawalQueue.getAddress(); - await iVault.setRatioFeed(ratioFeed.address); - if (options?.adapters?.includes(adapters.Mellow)) { await iVault.addAdapter(mellowAdapter.address); await mellowAdapter.setInceptionVault(iVault.address); diff --git a/projects/vaults/test/src/init-vault.ts b/projects/vaults/test/src/init-vault.ts index f69adfba..bc2d9a9f 100644 --- a/projects/vaults/test/src/init-vault.ts +++ b/projects/vaults/test/src/init-vault.ts @@ -145,8 +145,6 @@ export async function initVault( const withdrawalQueue = await upgrades.deployProxy(withdrawalQueueFactory, [iVault.address, [], [], 0]); withdrawalQueue.address = await withdrawalQueue.getAddress(); - await iVault.setRatioFeed(ratioFeed.address); - if (options?.adapters?.includes(adapters.Mellow)) { await iVault.addAdapter(mellowAdapter.address); await mellowAdapter.setInceptionVault(iVault.address); diff --git a/projects/vaults/test/tests-unit/InceptionVault_S/getters-setters.test.ts b/projects/vaults/test/tests-unit/InceptionVault_S/getters-setters.test.ts index 377168fd..ae21ce31 100644 --- a/projects/vaults/test/tests-unit/InceptionVault_S/getters-setters.test.ts +++ b/projects/vaults/test/tests-unit/InceptionVault_S/getters-setters.test.ts @@ -114,26 +114,6 @@ describe(`Inception Symbiotic Vault ${assetData.assetName}`, function() { ); }); - it("setRatioFeed(): only owner can", async function() { - const ratioFeed = await iVault.ratioFeed(); - const newRatioFeed = ethers.Wallet.createRandom().address; - await expect(iVault.setRatioFeed(newRatioFeed)) - .to.emit(iVault, "RatioFeedChanged") - .withArgs(ratioFeed, newRatioFeed); - expect(await iVault.ratioFeed()).to.be.eq(newRatioFeed); - }); - - it("setRatioFeed(): reverts when new value is zero address", async function() { - await expect(iVault.setRatioFeed(ethers.ZeroAddress)).to.be.revertedWithCustomError(iVault, "NullParams"); - }); - - it("setRatioFeed(): reverts when caller is not an owner", async function() { - const newRatioFeed = ethers.Wallet.createRandom().address; - await expect(iVault.connect(staker).setRatioFeed(newRatioFeed)).to.be.revertedWith( - "Ownable: caller is not the owner", - ); - }); - it("setWithdrawMinAmount(): only owner can", async function() { const prevValue = await iVault.withdrawMinAmount(); const newMinAmount = randomBI(3); From 15646eb4337d3c8366d3f0c0678408421ae60526 Mon Sep 17 00:00:00 2001 From: Kamil Mukhametzyanov Date: Wed, 18 Jun 2025 16:33:18 +0300 Subject: [PATCH 26/35] tests: remove check to adapters counter --- projects/vaults/test/InceptionVault_S_slashing.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/projects/vaults/test/InceptionVault_S_slashing.ts b/projects/vaults/test/InceptionVault_S_slashing.ts index f9684180..7c72be93 100644 --- a/projects/vaults/test/InceptionVault_S_slashing.ts +++ b/projects/vaults/test/InceptionVault_S_slashing.ts @@ -1970,8 +1970,6 @@ describe("Symbiotic Vault Slashing", function() { expect(withdrawalEpoch[0]).to.be.eq(false); expect(withdrawalEpoch[1]).to.be.eq(epochShares); expect(withdrawalEpoch[2]).to.be.eq(0n); - expect(withdrawalEpoch[3]).to.be.eq(0n); - expect(withdrawalEpoch[4]).to.be.eq(0n); expect(await withdrawalQueue.totalAmountRedeem()).to.be.eq(0); expect(await withdrawalQueue.getPendingWithdrawalOf(staker)).to.be.greaterThan(0); // ---------------- From 98045cfb7daaf91f823f47d0cf0b20ffcd3bd8fa Mon Sep 17 00:00:00 2001 From: Kamil Mukhametzyanov Date: Wed, 18 Jun 2025 16:34:50 +0300 Subject: [PATCH 27/35] tests: fix removeVault and removeAdapter usage --- .../vaults/test/tests-e2e/InceptionVault_S.test.ts | 10 +++++----- .../vaults/test/tests-unit/InceptionVault_S.test.ts | 12 ++++++------ .../test/tests-unit/InceptionVault_S/adapter.test.ts | 8 ++++---- .../test/tests-unit/InceptionVault_S/mellow.test.ts | 10 +++++----- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/projects/vaults/test/tests-e2e/InceptionVault_S.test.ts b/projects/vaults/test/tests-e2e/InceptionVault_S.test.ts index f4c5b230..8089adcb 100644 --- a/projects/vaults/test/tests-e2e/InceptionVault_S.test.ts +++ b/projects/vaults/test/tests-e2e/InceptionVault_S.test.ts @@ -329,11 +329,11 @@ describe(`Inception Symbiotic Vault ${assetData.asset.name} e2e tests`, function }); it("Cannot remove symbioticVault", async function() { - await expect(symbioticAdapter.removeVault(ethers.ZeroAddress)) + await expect(symbioticAdapter.removeVault(ethers.ZeroAddress, false)) .to.be.revertedWithCustomError(symbioticAdapter, "ZeroAddress"); - await expect(symbioticAdapter.removeVault(await iVaultOperator.getAddress())) + await expect(symbioticAdapter.removeVault(await iVaultOperator.getAddress(), false)) .to.be.revertedWithCustomError(symbioticAdapter, "NotContract"); - await expect(symbioticAdapter.removeVault(symbioticVaults[1].vaultAddress)) + await expect(symbioticAdapter.removeVault(symbioticVaults[1].vaultAddress, false)) .to.be.revertedWithCustomError(symbioticAdapter, "VaultNotEmpty"); }); @@ -381,11 +381,11 @@ describe(`Inception Symbiotic Vault ${assetData.asset.name} e2e tests`, function }); it("Remove symbioticVault", async function() { - await expect(symbioticAdapter.removeVault(symbioticVaults[1].vaultAddress)) + await expect(symbioticAdapter.removeVault(symbioticVaults[1].vaultAddress, false)) .to.emit(symbioticAdapter, "VaultRemoved") .withArgs(symbioticVaults[1].vaultAddress); - await expect(symbioticAdapter.removeVault(symbioticVaults[1].vaultAddress)) + await expect(symbioticAdapter.removeVault(symbioticVaults[1].vaultAddress, false)) .to.be.revertedWithCustomError(symbioticAdapter, "NotAdded"); }); diff --git a/projects/vaults/test/tests-unit/InceptionVault_S.test.ts b/projects/vaults/test/tests-unit/InceptionVault_S.test.ts index af8bef45..86eef841 100644 --- a/projects/vaults/test/tests-unit/InceptionVault_S.test.ts +++ b/projects/vaults/test/tests-unit/InceptionVault_S.test.ts @@ -262,7 +262,7 @@ describe(`Inception Symbiotic Vault ${assetData.asset.name}`, function () { // Act const { iVault: iVaultNew } = await initVault(assetData); - await (await iVault.migrateDepositBonus(await iVaultNew.getAddress())).wait(); + await (await iVault.migrateDepositBonus(await iVaultNew.getAddress(), false)).wait(); // Assert: bonus migrated const oldDepositBonus = await iVault.depositBonusAmount(); @@ -273,7 +273,7 @@ describe(`Inception Symbiotic Vault ${assetData.asset.name}`, function () { }); it('should revert if the new vault address is zero', async () => { - await expect(iVault.migrateDepositBonus(ethers.ZeroAddress)).to.be.revertedWithCustomError( + await expect(iVault.migrateDepositBonus(ethers.ZeroAddress, false)).to.be.revertedWithCustomError( iVault, 'InvalidAddress' ); @@ -282,7 +282,7 @@ describe(`Inception Symbiotic Vault ${assetData.asset.name}`, function () { it('should revert if there is no deposit bonus to migrate', async () => { expect(await iVault.depositBonusAmount(), 'Deposit bonus should be 0').to.be.eq(0); - await expect(iVault.migrateDepositBonus(staker.address)).to.be.revertedWithCustomError(iVault, 'NullParams'); + await expect(iVault.migrateDepositBonus(staker.address, false)).to.be.revertedWithCustomError(iVault, 'NullParams'); }); it('should revert if there are delegated funds', async () => { @@ -304,11 +304,11 @@ describe(`Inception Symbiotic Vault ${assetData.asset.name}`, function () { const { iVault: iVaultNew } = await initVault(assetData); // Act/Assert - await expect(iVault.migrateDepositBonus(iVaultNew.address)).to.be.revertedWithCustomError(iVault, 'ValueZero'); + await expect(iVault.migrateDepositBonus(iVaultNew.address, false)).to.be.revertedWithCustomError(iVault, 'ValueZero'); }); it('should only allow the owner to migrate the deposit bonus', async () => { - await expect(iVault.connect(staker).migrateDepositBonus(staker.address)).to.be.revertedWith( + await expect(iVault.connect(staker).migrateDepositBonus(staker.address, false)).to.be.revertedWith( 'Ownable: caller is not the owner' ); }); @@ -327,7 +327,7 @@ describe(`Inception Symbiotic Vault ${assetData.asset.name}`, function () { // Act const { iVault: iVaultNew } = await initVault(assetData); - const migrateTx = await (await iVault.migrateDepositBonus(await iVaultNew.getAddress())).wait(); + const migrateTx = await (await iVault.migrateDepositBonus(await iVaultNew.getAddress(), false)).wait(); // Assert: event emitted await expect(migrateTx) diff --git a/projects/vaults/test/tests-unit/InceptionVault_S/adapter.test.ts b/projects/vaults/test/tests-unit/InceptionVault_S/adapter.test.ts index ab42b620..4d55b2ae 100644 --- a/projects/vaults/test/tests-unit/InceptionVault_S/adapter.test.ts +++ b/projects/vaults/test/tests-unit/InceptionVault_S/adapter.test.ts @@ -141,14 +141,14 @@ describe(`Inception Symbiotic Vault ${assetData.assetName}`, function() { }); it("removeAdapter input args", async function() { - await expect(iVault.removeAdapter(iToken.address)) + await expect(iVault.removeAdapter(iToken.address, false)) .to.be.revertedWithCustomError(iVault, "AdapterNotFound"); await expect(iVault.connect(staker) - .removeAdapter(mellowAdapter.address), + .removeAdapter(mellowAdapter.address, false), ).to.be.revertedWith("Ownable: caller is not the owner"); - await iVault.removeAdapter(mellowAdapter.address); + await iVault.removeAdapter(mellowAdapter.address, false); }); it("emergencyClaim input args", async function() { @@ -207,7 +207,7 @@ describe(`Inception Symbiotic Vault ${assetData.assetName}`, function() { .to.be.revertedWith("Ownable: caller is not the owner"); await expect( - symbioticAdapter.connect(iVaultOperator).removeVault(symbioticVaults[0].vaultAddress), + symbioticAdapter.connect(iVaultOperator).removeVault(symbioticVaults[0].vaultAddress, false), ).to.be.revertedWith("Ownable: caller is not the owner"); }); diff --git a/projects/vaults/test/tests-unit/InceptionVault_S/mellow.test.ts b/projects/vaults/test/tests-unit/InceptionVault_S/mellow.test.ts index e8ff40c8..b407e044 100644 --- a/projects/vaults/test/tests-unit/InceptionVault_S/mellow.test.ts +++ b/projects/vaults/test/tests-unit/InceptionVault_S/mellow.test.ts @@ -82,17 +82,17 @@ describe(`Inception Symbiotic Vault ${assetData.assetName}`, function() { }); it("remove vault: reverts when not an owner", async function() { - await expect(mellowAdapter.connect(staker).removeVault(ZeroAddress)) + await expect(mellowAdapter.connect(staker).removeVault(ZeroAddress, false)) .to.be.revertedWith("Ownable: caller is not the owner"); }); it("remove vault: reverts when unknown vault", async function() { - await expect(mellowAdapter.removeVault(mellowVaults[2].vaultAddress)) + await expect(mellowAdapter.removeVault(mellowVaults[2].vaultAddress, false)) .to.be.revertedWithCustomError(mellowAdapter, "InvalidVault"); }); it("remove vault: reverts when vault is zero address", async function() { - await expect(mellowAdapter.removeVault(ZeroAddress)).to.be.revertedWithCustomError(mellowAdapter, "ZeroAddress"); + await expect(mellowAdapter.removeVault(ZeroAddress, false)).to.be.revertedWithCustomError(mellowAdapter, "ZeroAddress"); }); it("remove vault: reverts when vault is not empty", async function() { @@ -101,12 +101,12 @@ describe(`Inception Symbiotic Vault ${assetData.assetName}`, function() { await iVault.connect(staker).deposit(toWei(10), staker.address); await iVault.connect(iVaultOperator).delegate(mellowAdapter.address, vault, toWei(2), emptyBytes); // try to remove vault - await expect(mellowAdapter.removeVault(vault)).to.be.revertedWithCustomError(mellowAdapter, "VaultNotEmpty"); + await expect(mellowAdapter.removeVault(vault, false)).to.be.revertedWithCustomError(mellowAdapter, "VaultNotEmpty"); }); it("remove vault: success", async function() { const vault = mellowVaults[0].vaultAddress; - await expect(mellowAdapter.removeVault(vault)).to.emit(mellowAdapter, "VaultRemoved"); + await expect(mellowAdapter.removeVault(vault, false)).to.emit(mellowAdapter, "VaultRemoved"); }); // it("addMellowVault wrapper is 0 address", async function () { From 84adb1a83aedeee616dc7c156f6ee3178c11bbbd Mon Sep 17 00:00:00 2001 From: Kamil Mukhametzyanov Date: Wed, 18 Jun 2025 16:36:15 +0300 Subject: [PATCH 28/35] tests: fix according new function arguments --- .../contracts/vaults/EigenLayer/facets/EigenLayerFacet.sol | 2 +- projects/vaults/test/MellowV2.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/projects/vaults/contracts/vaults/EigenLayer/facets/EigenLayerFacet.sol b/projects/vaults/contracts/vaults/EigenLayer/facets/EigenLayerFacet.sol index 61ea6170..91446aad 100644 --- a/projects/vaults/contracts/vaults/EigenLayer/facets/EigenLayerFacet.sol +++ b/projects/vaults/contracts/vaults/EigenLayer/facets/EigenLayerFacet.sol @@ -114,7 +114,7 @@ contract EigenLayerFacet is InceptionVaultStorage_EL { withdrawals[0] = IDelegationManager.QueuedWithdrawalParams({ strategies: strategies, shares: sharesToWithdraw, - withdrawer: address(this) + __deprecated_withdrawer: address(this) }); delegationManager.queueWithdrawals(withdrawals); } diff --git a/projects/vaults/test/MellowV2.ts b/projects/vaults/test/MellowV2.ts index a0ff2ae1..24cef5dd 100644 --- a/projects/vaults/test/MellowV2.ts +++ b/projects/vaults/test/MellowV2.ts @@ -795,7 +795,7 @@ describe("Mellow v2", function () { ); console.log("PendingWithdrawalAmountInMellow : " + (await adapter.pendingWithdrawalAmount())); - console.log("ClaimableWithdrawalAmountInMellow: " + (await adapter.claimableWithdrawalAmount())); + console.log("ClaimableWithdrawalAmountInMellow: " + (await adapter.claimableWithdrawalAmount(false))); console.log("PortionsGivenBackOnWithdrawTX : " + (await adapter.claimableAmount())); console.log("Increasing epoch"); @@ -928,7 +928,7 @@ describe("Mellow v2", function () { ); console.log("PendingWithdrawalAmountInMellow : " + (await adapter.pendingWithdrawalAmount())); - console.log("ClaimableWithdrawalAmountInMellow: " + (await adapter.claimableWithdrawalAmount())); + console.log("ClaimableWithdrawalAmountInMellow: " + (await adapter.claimableWithdrawalAmount(false))); console.log("PortionsGivenBackOnWithdrawTX : " + (await adapter.claimableAmount())); }); }); From 57e5c5f717dfc7f22ff6cfe783e7c5cd5c745fcb Mon Sep 17 00:00:00 2001 From: Kamil Mukhametzyanov Date: Wed, 18 Jun 2025 16:37:12 +0300 Subject: [PATCH 29/35] tests: fix migrateBonusDeposit tests --- .../test/tests-unit/InceptionVault_S.test.ts | 29 +++++++------------ 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/projects/vaults/test/tests-unit/InceptionVault_S.test.ts b/projects/vaults/test/tests-unit/InceptionVault_S.test.ts index 86eef841..f99a18d1 100644 --- a/projects/vaults/test/tests-unit/InceptionVault_S.test.ts +++ b/projects/vaults/test/tests-unit/InceptionVault_S.test.ts @@ -261,28 +261,22 @@ describe(`Inception Symbiotic Vault ${assetData.asset.name}`, function () { expect(depositBonusAmount).to.be.gt(0); // Act - const { iVault: iVaultNew } = await initVault(assetData); - await (await iVault.migrateDepositBonus(await iVaultNew.getAddress(), false)).wait(); + const [owner] = await ethers.getSigners(); + const oldOwnerBalance = await asset.balanceOf(owner); + await (await iVault.connect(owner).migrateDepositBonus(false)).wait(); // Assert: bonus migrated const oldDepositBonus = await iVault.depositBonusAmount(); expect(oldDepositBonus, 'Old vault deposit bonus should equal 0').to.be.eq(0); - const newVaultBalance = await asset.balanceOf(iVaultNew.address); - expect(newVaultBalance, 'New vault balance should equal to transferred deposit bonus').to.be.eq(depositBonusAmount); - }); - - it('should revert if the new vault address is zero', async () => { - await expect(iVault.migrateDepositBonus(ethers.ZeroAddress, false)).to.be.revertedWithCustomError( - iVault, - 'InvalidAddress' - ); + const newOwnerBalance = await asset.balanceOf(owner); + expect(newOwnerBalance, 'New owner balance should equal to transferred deposit bonus').to.be.eq(oldOwnerBalance + depositBonusAmount); }); it('should revert if there is no deposit bonus to migrate', async () => { expect(await iVault.depositBonusAmount(), 'Deposit bonus should be 0').to.be.eq(0); - await expect(iVault.migrateDepositBonus(staker.address, false)).to.be.revertedWithCustomError(iVault, 'NullParams'); + await expect(iVault.migrateDepositBonus(false)).to.be.revertedWithCustomError(iVault, 'NullParams'); }); it('should revert if there are delegated funds', async () => { @@ -301,14 +295,12 @@ describe(`Inception Symbiotic Vault ${assetData.asset.name}`, function () { const depositBonusAmount = await iVault.depositBonusAmount(); expect(depositBonusAmount, 'Deposit bonus should exist').to.be.gt(0); - const { iVault: iVaultNew } = await initVault(assetData); - // Act/Assert - await expect(iVault.migrateDepositBonus(iVaultNew.address, false)).to.be.revertedWithCustomError(iVault, 'ValueZero'); + await expect(iVault.migrateDepositBonus(false)).to.be.revertedWithCustomError(iVault, 'ValueZero'); }); it('should only allow the owner to migrate the deposit bonus', async () => { - await expect(iVault.connect(staker).migrateDepositBonus(staker.address, false)).to.be.revertedWith( + await expect(iVault.connect(staker).migrateDepositBonus(false)).to.be.revertedWith( 'Ownable: caller is not the owner' ); }); @@ -326,13 +318,12 @@ describe(`Inception Symbiotic Vault ${assetData.asset.name}`, function () { const depositBonusAmount = await iVault.depositBonusAmount(); // Act - const { iVault: iVaultNew } = await initVault(assetData); - const migrateTx = await (await iVault.migrateDepositBonus(await iVaultNew.getAddress(), false)).wait(); + const migrateTx = await (await iVault.migrateDepositBonus(false)).wait(); // Assert: event emitted await expect(migrateTx) .to.emit(iVault, 'DepositBonusTransferred') - .withArgs(iVaultNew.address, depositBonusAmount); + .withArgs(depositBonusAmount); }); }); From f13ce3e83e435a0d04ff1973ab1bcc381bc5706d Mon Sep 17 00:00:00 2001 From: Kamil Mukhametzyanov Date: Wed, 18 Jun 2025 16:37:36 +0300 Subject: [PATCH 30/35] tests: test for issue Wrong event emission, the old reward treasury is emitted instead of the new one --- .../vaults/test/tests-unit/InceptionVault_S/rewards.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/projects/vaults/test/tests-unit/InceptionVault_S/rewards.test.ts b/projects/vaults/test/tests-unit/InceptionVault_S/rewards.test.ts index a709c6d3..644fbdbb 100644 --- a/projects/vaults/test/tests-unit/InceptionVault_S/rewards.test.ts +++ b/projects/vaults/test/tests-unit/InceptionVault_S/rewards.test.ts @@ -61,7 +61,9 @@ describe("Farm rewards", function() { it("Set rewards treasury address", async function() { rewardsTreasury = ethers.Wallet.createRandom().address; - await iVault.setRewardsTreasury(rewardsTreasury); + await expect(iVault.setRewardsTreasury(rewardsTreasury)) + .to.emit(iVault, "SetRewardsTreasury") + .withArgs(rewardsTreasury); }); it("Deposit and delegate to symbiotic vault", async function() { From 7f77c5b87a920ed90d860beecfbf5f812c8fa01d Mon Sep 17 00:00:00 2001 From: Kamil Mukhametzyanov Date: Wed, 18 Jun 2025 16:38:08 +0300 Subject: [PATCH 31/35] tests: test for issue rewardsTimeline is not initialized --- .../InceptionVault_S/rewards.test.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/projects/vaults/test/tests-unit/InceptionVault_S/rewards.test.ts b/projects/vaults/test/tests-unit/InceptionVault_S/rewards.test.ts index 644fbdbb..42b61f31 100644 --- a/projects/vaults/test/tests-unit/InceptionVault_S/rewards.test.ts +++ b/projects/vaults/test/tests-unit/InceptionVault_S/rewards.test.ts @@ -155,13 +155,6 @@ describe("Farm rewards", function() { await iVault.setTargetFlashCapacity(1n); }); - it("set rewards timeline", async function () { - const timeline = 86400; - - await iVault.setRewardsTimeline(timeline); - expect(await iVault.rewardsTimeline()).to.be.eq(timeline); - }); - it("set rewards timeline: invalid data", async function() { await expect(iVault.setRewardsTimeline(3600n)) .to.be.revertedWithCustomError(iVault, "InconsistentData"); @@ -180,6 +173,18 @@ describe("Farm rewards", function() { .to.be.revertedWith("Ownable: caller is not the owner"); }); + it("failed to add rewards when timeline empty", async function() { + await expect(iVault.connect(iVaultOperator).addRewards(1n)) + .to.be.revertedWithCustomError(iVault, "RewardsTimelineNotSet"); + }); + + it("set rewards timeline", async function () { + const timeline = 86400; + + await iVault.setRewardsTimeline(timeline); + expect(await iVault.rewardsTimeline()).to.be.eq(timeline); + }); + it("add rewards for the first time", async function() { const rewardsAmount = toWei(10); From 662aae085457d637a1b273128622773304b55c32 Mon Sep 17 00:00:00 2001 From: Kamil Mukhametzyanov Date: Wed, 18 Jun 2025 16:38:36 +0300 Subject: [PATCH 32/35] tests: test for issue Funds from Mellow Withdrawals Can Be Taken by Other Users --- .../vaults/test/InceptionVault_S_slashing.ts | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/projects/vaults/test/InceptionVault_S_slashing.ts b/projects/vaults/test/InceptionVault_S_slashing.ts index 7c72be93..38db38b5 100644 --- a/projects/vaults/test/InceptionVault_S_slashing.ts +++ b/projects/vaults/test/InceptionVault_S_slashing.ts @@ -1076,13 +1076,11 @@ describe("Symbiotic Vault Slashing", function() { .map(log => mellowAdapter.interface.parseLog(log)); let claimer1 = adapterEvents[0].args["claimer"]; + expect(await withdrawalQueue.totalPendingRedeemAmount()).to.be.eq(events[0].args["claimedAmount"]); + expect(await iVault.getFlashCapacity()).to.be.eq(0n); expect(await iVault.ratio()).to.be.closeTo(toWei(1), ratioErr); // ---------------- - console.log("before", await symbioticVaults[0].vault.totalStake()); - console.log("before totalDelegated", await iVault.getTotalDelegated()); - console.log("pending withdrawals", await iVault.getTotalPendingWithdrawals()); - // apply slash let totalStake = await symbioticVaults[0].vault.totalStake(); await assetDataNew.applySymbioticSlash(symbioticVaults[0].vault, totalStake * 10n / 100n); @@ -1109,6 +1107,8 @@ describe("Symbiotic Vault Slashing", function() { ); await tx.wait(); + expect(await withdrawalQueue.totalPendingRedeemAmount()).to.be.eq(0n); + expect(await iVault.getFlashCapacity()).to.be.eq(0n); expect(await iVault.ratio()).to.be.closeTo(1053370378591850307n, ratioErr); // ---------------- @@ -1163,6 +1163,8 @@ describe("Symbiotic Vault Slashing", function() { expect(events[0].args["actualAmounts"]).to.be.eq(813088477205661249n); expect(await iVault.ratio()).to.be.closeTo(999822420543056026n, ratioErr); + expect(await withdrawalQueue.totalPendingRedeemAmount()).to.be.eq(events[0].args["claimedAmount"]); + expect(await iVault.getFlashCapacity()).to.be.eq(0n); // ---------------- // claim @@ -1175,6 +1177,8 @@ describe("Symbiotic Vault Slashing", function() { expect(await withdrawalQueue.totalAmountRedeem()).to.be.closeTo(undelegateAmount, transactErr); expect(await withdrawalQueue.totalSharesToWithdraw()).to.be.eq(0n); expect(await iVault.ratio()).to.be.closeTo(999822420543056026n, ratioErr); + expect(await withdrawalQueue.totalPendingRedeemAmount()).to.be.eq(0n); + expect(await iVault.getFlashCapacity()).to.be.eq(0n); // ---------------- // redeem @@ -1258,6 +1262,7 @@ describe("Symbiotic Vault Slashing", function() { tx = await iVault.connect(iVaultOperator).undelegate(await withdrawalQueue.currentEpoch(), []); expect(await iVault.ratio()).to.be.closeTo(1112752741401218766n, ratioErr); + expect(await withdrawalQueue.totalPendingRedeemAmount()).to.be.eq(0n); // ---------------- // redeem @@ -1661,6 +1666,7 @@ describe("Symbiotic Vault Slashing", function() { expect(await asset.balanceOf(iVault.address)).to.be.eq(toWei(5)); expect(await withdrawalQueue.totalAmountRedeem()).to.be.eq(toWei(2)); expect(await iVault.ratio()).to.be.closeTo(toWei(1), ratioErr); + expect(await withdrawalQueue.totalPendingRedeemAmount()).to.be.eq(0n); // ---------------- // redeem @@ -1730,6 +1736,7 @@ describe("Symbiotic Vault Slashing", function() { expect(await asset.balanceOf(iVault.address)).to.be.eq(toWei(5)); expect(await withdrawalQueue.totalAmountRedeem()).to.be.eq(toWei(2)); expect(await iVault.ratio()).to.be.closeTo(toWei(1), ratioErr); + expect(await withdrawalQueue.totalPendingRedeemAmount()).to.be.eq(0n); // ---------------- // redeem @@ -1866,13 +1873,17 @@ describe("Symbiotic Vault Slashing", function() { // ---------------- // undelegate #2 + let flashCapBefore = await iVault.getFlashCapacity(); tx = await iVault.connect(iVaultOperator) .emergencyUndelegate([[mellowAdapter.address, mellowVaults[0].vaultAddress, toWei(5), []]]); receipt = await tx.wait(); + events = receipt.logs?.filter(e => e.eventName === "UndelegatedFrom"); adapterEvents = receipt.logs?.filter(log => log.address === mellowAdapter.address) .map(log => mellowAdapter.interface.parseLog(log)); claimer = adapterEvents[0].args["claimer"]; + expect(await withdrawalQueue.totalPendingRedeemAmount()).to.be.eq(events[0].args["claimedAmount"]); + expect(await iVault.getFlashCapacity()).to.be.eq(flashCapBefore); expect(await iVault.ratio()).to.be.eq(1852573758880544819n); // claim #2 @@ -1882,6 +1893,8 @@ describe("Symbiotic Vault Slashing", function() { ) // ---------------- + expect(await withdrawalQueue.totalPendingRedeemAmount()).to.be.eq(0n); + expect(await iVault.getFlashCapacity()).to.be.greaterThan(flashCapBefore); expect(await iVault.ratio()).to.be.closeTo(1852573758880544819n, 10n); @@ -1889,6 +1902,8 @@ describe("Symbiotic Vault Slashing", function() { const redeemReservedBefore = await iVault.redeemReservedAmount(); await iVault.connect(iVaultOperator).undelegate(1, []); const redeemReservedAfter = await iVault.redeemReservedAmount(); + + expect(await withdrawalQueue.totalPendingRedeemAmount()).to.be.eq(0n); // ---------------- // redeem @@ -1934,6 +1949,7 @@ describe("Symbiotic Vault Slashing", function() { await tx.wait(); // undelegate + let flashCapBefore = await iVault.getFlashCapacity(); let epochShares = await withdrawalQueue.getRequestedShares(await withdrawalQueue.currentEpoch()); expect(epochShares).to.be.eq(toWei(5)); tx = await iVault.connect(iVaultOperator) @@ -1948,6 +1964,8 @@ describe("Symbiotic Vault Slashing", function() { expect(events[0].args["epoch"]).to.be.eq(1); expect(events[0].args["adapter"]).to.be.eq(mellowAdapter.address); expect(events[0].args["actualAmounts"]).to.be.eq(toWei(5)); + expect(await withdrawalQueue.totalPendingRedeemAmount()).to.be.eq(events[0].args["claimedAmount"]); + expect(await iVault.getFlashCapacity()).to.be.eq(flashCapBefore); expect(await iVault.ratio()).to.be.eq(toWei(1)); // ---------------- @@ -1972,12 +1990,16 @@ describe("Symbiotic Vault Slashing", function() { expect(withdrawalEpoch[2]).to.be.eq(0n); expect(await withdrawalQueue.totalAmountRedeem()).to.be.eq(0); expect(await withdrawalQueue.getPendingWithdrawalOf(staker)).to.be.greaterThan(0); + expect(await withdrawalQueue.totalPendingRedeemAmount()).to.be.eq(0n); + expect(await iVault.getFlashCapacity()).to.be.greaterThan(flashCapBefore); // ---------------- // force undelegate and claim const redeemReservedBefore = await iVault.redeemReservedAmount(); await iVault.connect(iVaultOperator).undelegate(1, []); const redeemReservedAfter = await iVault.redeemReservedAmount(); + + expect(await withdrawalQueue.totalPendingRedeemAmount()).to.be.eq(0n); // ---------------- // redeem From 90133f24c52e23a73529da4d683874ad42c87b85 Mon Sep 17 00:00:00 2001 From: Kamil Mukhametzyanov Date: Thu, 19 Jun 2025 10:26:30 +0300 Subject: [PATCH 33/35] Fix issue: SymbioticAdapterClaimer and MellowAdapterClaimer can never be paused --- .../adapter-claimers/MellowAdapterClaimer.sol | 18 ++++++++++++++++++ .../SymbioticAdapterClaimer.sol | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/projects/vaults/contracts/adapter-claimers/MellowAdapterClaimer.sol b/projects/vaults/contracts/adapter-claimers/MellowAdapterClaimer.sol index b4c1bfe9..af4e1bc3 100644 --- a/projects/vaults/contracts/adapter-claimers/MellowAdapterClaimer.sol +++ b/projects/vaults/contracts/adapter-claimers/MellowAdapterClaimer.sol @@ -65,4 +65,22 @@ contract MellowAdapterClaimer is amount ); } + + /*/////////////////////////////// + ////// Pausable functions ////// + /////////////////////////////*/ + + /** + * @dev Pauses the contract + */ + function pause() external onlyOwner { + _pause(); + } + + /** + * @dev Unpauses the contract + */ + function unpause() external onlyOwner { + _unpause(); + } } \ No newline at end of file diff --git a/projects/vaults/contracts/adapter-claimers/SymbioticAdapterClaimer.sol b/projects/vaults/contracts/adapter-claimers/SymbioticAdapterClaimer.sol index 2c013fd2..0d72e03d 100644 --- a/projects/vaults/contracts/adapter-claimers/SymbioticAdapterClaimer.sol +++ b/projects/vaults/contracts/adapter-claimers/SymbioticAdapterClaimer.sol @@ -61,4 +61,22 @@ contract SymbioticAdapterClaimer is require(msg.sender == _adapter, OnlyAdapter()); return IVault(vault).claim(recipient, epoch); } + + /*/////////////////////////////// + ////// Pausable functions ////// + /////////////////////////////*/ + + /** + * @dev Pauses the contract + */ + function pause() external onlyOwner { + _pause(); + } + + /** + * @dev Unpauses the contract + */ + function unpause() external onlyOwner { + _unpause(); + } } \ No newline at end of file From d3af390e0733218e40964ff070dda470bc6a3e87 Mon Sep 17 00:00:00 2001 From: Kamil Mukhametzyanov Date: Thu, 19 Jun 2025 10:46:27 +0300 Subject: [PATCH 34/35] tests: test for issue SymbioticAdapterClaimer and MellowAdapterClaimer can never be paused --- .../InceptionVault_S/getters-setters.test.ts | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/projects/vaults/test/tests-unit/InceptionVault_S/getters-setters.test.ts b/projects/vaults/test/tests-unit/InceptionVault_S/getters-setters.test.ts index ae21ce31..044a0f73 100644 --- a/projects/vaults/test/tests-unit/InceptionVault_S/getters-setters.test.ts +++ b/projects/vaults/test/tests-unit/InceptionVault_S/getters-setters.test.ts @@ -14,9 +14,11 @@ import { import { adapters, emptyBytes } from "../../src/constants"; import { initVault, MAX_TARGET_PERCENT } from "../../src/init-vault"; import { ZeroAddress } from "ethers"; +import { mellow } from "../../../typechain-types/contracts/tests"; const { ethers, network } = hardhat; const mellowVaults = vaults.mellow; +const symbioticVaults = vaults.symbiotic; const assetData = stETH; describe(`Inception Symbiotic Vault ${assetData.assetName}`, function() { @@ -377,4 +379,72 @@ describe(`Inception Symbiotic Vault ${assetData.assetName}`, function() { await expect(mellowAdapter.connect(staker).unpause()).to.be.revertedWith("Ownable: caller is not the owner"); }); }); + + describe("Adapter claimers setters", function() { + let owner, symbioticClaimer, mellowClaimer; + + before(async function() { + await snapshot.restore(); + await iVault.setTargetFlashCapacity(1n); + + await iVault.connect(staker).deposit(toWei(10), staker.address); + + await iVault.connect(iVaultOperator) + .delegate(symbioticAdapter.address, symbioticVaults[0].vaultAddress, toWei(4), emptyBytes); + await iVault.connect(iVaultOperator) + .delegate(mellowAdapter.address, mellowVaults[0].vaultAddress, toWei(4), emptyBytes); + + await iVault.connect(staker).withdraw(toWei(2), staker.address); + + const tx = await iVault.connect(iVaultOperator) + .undelegate(await withdrawalQueue.currentEpoch(), [ + [symbioticAdapter.address, symbioticVaults[0].vaultAddress, toWei(1), emptyBytes], + [mellowAdapter.address, mellowVaults[0].vaultAddress, toWei(1), emptyBytes] + ]); + const receipt = await tx.wait(); + + let adapterEvents = receipt.logs?.filter(log => log.address === symbioticAdapter.address) + .map(log => symbioticAdapter.interface.parseLog(log)); + const symbioticClaimerAddr = adapterEvents[0].args["claimer"]; + + adapterEvents = receipt.logs?.filter(log => log.address === mellowAdapter.address) + .map(log => mellowAdapter.interface.parseLog(log)); + const mellowClaimerAddr = adapterEvents[0].args["claimer"]; + + [owner] = await ethers.getSigners(); + + mellowClaimer = await ethers.getContractAt("MellowAdapterClaimer", mellowClaimerAddr); + symbioticClaimer = await ethers.getContractAt("SymbioticAdapterClaimer", symbioticClaimerAddr); + }); + + it("pause mellow claimer", async function() { + expect(await mellowClaimer.paused()).equal(false); + await expect(mellowClaimer.connect(staker).pause()).to.be.revertedWith("Ownable: caller is not the owner"); + await mellowClaimer.connect(owner).pause(); + await expect(mellowClaimer.connect(owner).pause()).to.be.revertedWith("Pausable: paused"); + expect(await mellowClaimer.paused()).equal(true); + }); + + it("unpause mellow claimer", async function() { + await expect(mellowClaimer.connect(staker).unpause()).to.be.revertedWith("Ownable: caller is not the owner"); + await mellowClaimer.connect(owner).unpause(); + await expect(mellowClaimer.connect(owner).unpause()).to.be.revertedWith("Pausable: not paused"); + expect(await mellowClaimer.paused()).equal(false); + }); + + it("pause symbiotic claimer", async function() { + expect(await symbioticClaimer.paused()).equal(false); + await expect(symbioticClaimer.connect(staker).pause()).to.be.revertedWith("Ownable: caller is not the owner"); + await symbioticClaimer.connect(owner).pause(); + await expect(symbioticClaimer.connect(owner).pause()).to.be.revertedWith("Pausable: paused"); + expect(await symbioticClaimer.paused()).equal(true); + }); + + it("unpause symbiotic claimer", async function() { + await expect(symbioticClaimer.connect(staker).unpause()).to.be.revertedWith("Ownable: caller is not the owner"); + await symbioticClaimer.connect(owner).unpause(); + await expect(symbioticClaimer.connect(owner).unpause()).to.be.revertedWith("Pausable: not paused"); + expect(await symbioticClaimer.paused()).equal(false); + }); + }); }); From bbec68845ca632366fe0da2d76ed81fba10a1e69 Mon Sep 17 00:00:00 2001 From: Kamil Mukhametzyanov Date: Thu, 19 Jun 2025 15:15:48 +0300 Subject: [PATCH 35/35] tests: skip upgradable test --- projects/vaults/test/MellowV2.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/vaults/test/MellowV2.ts b/projects/vaults/test/MellowV2.ts index 24cef5dd..213c65ab 100644 --- a/projects/vaults/test/MellowV2.ts +++ b/projects/vaults/test/MellowV2.ts @@ -386,7 +386,7 @@ describe("Mellow v2", function () { // console.log("PendingWithdraw: " + await vault.getPendingWithdrawalAmountFromMellow()); }); }); - describe("test #3", function () { + describe.skip("test #3", function () { before(async function () { // FORKING await network.provider.request({