diff --git a/projects/vaults/contracts/adapter-claimers/MellowAdapterClaimer.sol b/projects/vaults/contracts/adapter-claimers/MellowAdapterClaimer.sol index 26785805..af4e1bc3 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); } /** @@ -66,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 a1d96ade..0d72e03d 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); } @@ -62,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 diff --git a/projects/vaults/contracts/adapter-handler/AdapterHandler.sol b/projects/vaults/contracts/adapter-handler/AdapterHandler.sol index cd3ff2b8..b9b1710f 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()); @@ -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. @@ -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; @@ -456,7 +458,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; } @@ -516,10 +518,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/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, diff --git a/projects/vaults/contracts/adapters/InceptionEigenAdapter.sol b/projects/vaults/contracts/adapters/InceptionEigenAdapter.sol index 9c3b3f8a..b8a1d425 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 @@ -173,13 +157,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; @@ -189,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 4fce3943..ab9be6e8 100644 --- a/projects/vaults/contracts/adapters/InceptionEigenAdapterWrap.sol +++ b/projects/vaults/contracts/adapters/InceptionEigenAdapterWrap.sol @@ -63,24 +63,9 @@ 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); } - /** - * @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,11 +81,13 @@ 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); + + 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( @@ -180,15 +167,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; @@ -198,7 +191,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/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 e5951e0f..1aba50c5 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); @@ -281,13 +281,18 @@ 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 && + _claimableWithdrawalAmount(vault, true) == 0 && + _claimableWithdrawalAmount(vault, false) == 0 + ), VaultNotEmpty() ); @@ -345,14 +350,15 @@ 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); } /** - * @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 */ @@ -375,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 diff --git a/projects/vaults/contracts/assets-handler/InceptionAssetsHandler.sol b/projects/vaults/contracts/assets-handler/InceptionAssetsHandler.sol index 170bce97..52235c45 100644 --- a/projects/vaults/contracts/assets-handler/InceptionAssetsHandler.sol +++ b/projects/vaults/contracts/assets-handler/InceptionAssetsHandler.sol @@ -54,8 +54,9 @@ 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); - return (_asset.balanceOf(address(this)) - reservedRewards); + uint256 reservedRewards = (currentRewards * (totalDays - elapsedDays)) / totalDays; + uint256 balance = _asset.balanceOf(address(this)); + return balance > reservedRewards ? balance - reservedRewards : 0; } /** @@ -65,7 +66,7 @@ contract InceptionAssetsHandler is function setRewardsTreasury(address treasury) external onlyOwner { require(treasury != address(0), NullParams()); - emit SetRewardsTreasury(rewardsTreasury); + emit SetRewardsTreasury(treasury); rewardsTreasury = treasury; } 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); } 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(); } diff --git a/projects/vaults/contracts/interfaces/common/IWithdrawalQueue.sol b/projects/vaults/contracts/interfaces/common/IWithdrawalQueue.sol index 9aff046e..446562b6 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; } /* @@ -116,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/interfaces/eigenlayer-vault/eigen-core/IDelegationManager.sol b/projects/vaults/contracts/interfaces/eigenlayer-vault/eigen-core/IDelegationManager.sol index 3cae9262..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 { @@ -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); } 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/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/contracts/vaults/Symbiotic/InceptionVault_S.sol b/projects/vaults/contracts/vaults/Symbiotic/InceptionVault_S.sol index 606ca221..dde7e2d0 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; @@ -235,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 @@ -471,19 +471,18 @@ 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()); - require(newVault != address(0), InvalidAddress()); + function migrateDepositBonus(bool skipEmptyCheck) external onlyOwner { + require(skipEmptyCheck || getTotalDelegated() == 0, ValueZero()); 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); } /*////////////////////////////// @@ -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 diff --git a/projects/vaults/contracts/withdrawal-queue/WithdrawalQueue.sol b/projects/vaults/contracts/withdrawal-queue/WithdrawalQueue.sol index 76c74714..706ead27 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()); @@ -195,17 +196,14 @@ contract WithdrawalQueue is ); // update withdrawal data - withdrawal.adapterUndelegated[adapter][vault] = undelegatedAmount; + withdrawal.adapterUndelegated[adapter][vault] += undelegatedAmount; withdrawal.totalUndelegatedAmount += undelegatedAmount; - withdrawal.adaptersUndelegatedCounter++; if (claimedAmount > 0) { + totalPendingRedeemAmount += claimedAmount; + withdrawal.totalUndelegatedAmount += claimedAmount; withdrawal.totalClaimedAmount += claimedAmount; } - - if (claimedAmount > 0 && undelegatedAmount == 0) { - withdrawal.adaptersClaimedCounter++; - } } /** @@ -214,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); } @@ -285,7 +282,8 @@ contract WithdrawalQueue is // update withdrawal state withdrawal.totalClaimedAmount += claimedAmount; - withdrawal.adaptersClaimedCounter++; + // update global state + totalPendingRedeemAmount += claimedAmount; } /** @@ -300,8 +298,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); @@ -316,7 +312,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; } @@ -331,19 +331,19 @@ 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 { withdrawal.ableRedeem = true; totalAmountRedeem += withdrawal.totalClaimedAmount; totalSharesToWithdraw -= withdrawal.totalRequestedShares; + totalPendingRedeemAmount -= withdrawal.totalClaimedAmount; } /** * @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 @@ -354,10 +354,10 @@ contract WithdrawalQueue is address[] calldata adapters, address[] calldata vaults ) internal { + totalPendingRedeemAmount -= withdrawal.totalClaimedAmount; + 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]]; @@ -380,6 +380,9 @@ contract WithdrawalQueue is // update epoch state withdrawal.totalClaimedAmount = claimedAmount; + withdrawal.totalUndelegatedAmount = claimedAmount; + // update global state + totalPendingRedeemAmount += claimedAmount; _afterUndelegate(epoch, withdrawal); } diff --git a/projects/vaults/test/InceptionVault_S_slashing.ts b/projects/vaults/test/InceptionVault_S_slashing.ts index f9684180..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)); // ---------------- @@ -1970,16 +1988,18 @@ 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); + 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 diff --git a/projects/vaults/test/MellowV2.ts b/projects/vaults/test/MellowV2.ts index a0ff2ae1..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({ @@ -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())); }); }); 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-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..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())).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)).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)).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)).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)).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())).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); }); }); 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/getters-setters.test.ts b/projects/vaults/test/tests-unit/InceptionVault_S/getters-setters.test.ts index 377168fd..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() { @@ -114,26 +116,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); @@ -397,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); + }); + }); }); 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 () { 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..42b61f31 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() { @@ -153,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"); @@ -178,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);