Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
8dd948d
Fix issue: Storage collision due to increasing the storage gap
grkamil Jun 18, 2025
babaa6f
Fix issue: Emergency withdrawals that the MellowVault processes insta…
grkamil Jun 18, 2025
262bc5a
Fix issue: The ratioFeed shall be legacy
grkamil Jun 18, 2025
4005e01
Fix issue: Withdrawal Epoch Can Get Permanently Stuck if a Symbiotic …
grkamil Jun 18, 2025
95116ed
Fix issue: All Epochs Incorrectly Marked as "Slashed" Due to Yield an…
grkamil Jun 18, 2025
5eea8d4
Fix issue: Some functions can be griefed via a tiny donation
grkamil Jun 18, 2025
4113093
Fix issue: withdraw() will queue less tokens than expected
grkamil Jun 18, 2025
4c3b3ea
Fix issue: Wrong event emission, the old reward treasury is emitted i…
grkamil Jun 18, 2025
ec657f9
Fix issue: Precision loss due to division before multiplication
grkamil Jun 18, 2025
2593dfe
Fix issue: DoS When Depositing Into Strategies Deployed via EigenLaye…
grkamil Jun 18, 2025
38ba9b5
Fix issue: The withdrawer field in QueuedWithdrawalParams is now depr…
grkamil Jun 18, 2025
a9f27f9
Fix issue: Unnecessary approval to strategyManager
grkamil Jun 18, 2025
4f79f4c
Fix issue: claimableWithdrawalAmount can’t query the claimableAssetsO…
grkamil Jun 18, 2025
18ec232
Fix issue: claimAdapterFreeBalance and claimAdapterRewards are missin…
grkamil Jun 18, 2025
41ccbbc
Fix issue: mint() may return more shares than requested
grkamil Jun 18, 2025
e57779c
Fix issue: Funds from Mellow Withdrawals Can Be Taken by Other Users
grkamil Jun 18, 2025
44940ea
Fix issue: adapterUndelegated is reset on _undelegate()
grkamil Jun 18, 2025
3504733
Fix issue: DoS on delegate() When Depositing More stETH Than Available
grkamil Jun 18, 2025
d793c9b
Fix issue: rewardsTimeline is not initialized
grkamil Jun 18, 2025
a2967e6
Fix issue: migrateDepositBonus Fails to Update Bonus Tracking on Targ…
grkamil Jun 18, 2025
6f9a1e1
Fix issue: Slashing will be missed in an edge case
grkamil Jun 18, 2025
24d0755
Fix issue: IBaseAdapter should add a storage gap
grkamil Jun 18, 2025
9c3e4ef
Fix issue: DoS when using tokens that don’t return a bool on approval
grkamil Jun 18, 2025
5ef6fef
Fix issue: Insufficient validation when removing a Mellow vault
grkamil Jun 18, 2025
d860080
tests: remove ratio feed from tests
grkamil Jun 18, 2025
15646eb
tests: remove check to adapters counter
grkamil Jun 18, 2025
98045cf
tests: fix removeVault and removeAdapter usage
grkamil Jun 18, 2025
84adb1a
tests: fix according new function arguments
grkamil Jun 18, 2025
57e5c5f
tests: fix migrateBonusDeposit tests
grkamil Jun 18, 2025
f13ce3e
tests: test for issue Wrong event emission, the old reward treasury i…
grkamil Jun 18, 2025
7f77c5b
tests: test for issue rewardsTimeline is not initialized
grkamil Jun 18, 2025
662aae0
tests: test for issue Funds from Mellow Withdrawals Can Be Taken by O…
grkamil Jun 18, 2025
90133f2
Fix issue: SymbioticAdapterClaimer and MellowAdapterClaimer can never…
grkamil Jun 19, 2025
d3af390
tests: test for issue SymbioticAdapterClaimer and MellowAdapterClaime…
grkamil Jun 19, 2025
bbec688
tests: skip upgradable test
grkamil Jun 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -25,6 +25,8 @@ contract MellowAdapterClaimer is
OwnableUpgradeable,
IAdapterClaimer
{
using SafeERC20 for IERC20;

address internal _adapter;

/// @custom:oz-upgrades-unsafe-allow constructor
Expand All @@ -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);
}

/**
Expand All @@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -25,6 +25,8 @@ contract SymbioticAdapterClaimer is
OwnableUpgradeable,
IAdapterClaimer
{
using SafeERC20 for IERC20;

address internal _adapter;

/// @custom:oz-upgrades-unsafe-allow constructor
Expand All @@ -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);
}


Expand All @@ -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();
}
}
17 changes: 10 additions & 7 deletions projects/vaults/contracts/adapter-handler/AdapterHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -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 {
Expand All @@ -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.
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions projects/vaults/contracts/adapters/InceptionBaseAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
30 changes: 10 additions & 20 deletions projects/vaults/contracts/adapters/InceptionEigenAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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;

Expand All @@ -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
Expand Down
37 changes: 15 additions & 22 deletions projects/vaults/contracts/adapters/InceptionEigenAdapterWrap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
Expand Down Expand Up @@ -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;

Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Loading