Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/collector/CollectorV4.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
import "../../src/interfaces/queues/IEigenLayerWithdrawalQueue.sol";
import "../../src/interfaces/queues/ISymbioticWithdrawalQueue.sol";
import "../../src/interfaces/tokens/IWSTETH.sol";
import "../../src/strategies/RatiosStrategy.sol";
import "../../src/vaults/MultiVault.sol";
import "./Oracle.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "./modules/SymbioticModule.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/utils/math/Math.sol";
contract CollectorV4 is Ownable {
struct Withdrawal {
uint256 subvaultIndex;
uint256 assets;
bool isTimestamp; // if false -> block.number
uint256 claimingTime; // if 0 - assets == claimable assets, otherwise = pending assets
uint256 withdrawalIndex;
uint256 withdrawalRequestType; // 0 - withdrawals, 1 - transferedWithdrawals
}
struct Response {
address vault;
address asset;
uint8 assetDecimals;
uint256 assetPriceX96;
uint256 totalLP;
uint256 totalUSD;
uint256 totalETH;
uint256 totalUnderlying;
uint256 limitLP;
uint256 limitUSD;
uint256 limitETH;
uint256 limitUnderlying;
uint256 userLP;
uint256 userETH;
uint256 userUSD;
uint256 userUnderlying;
uint256 lpPriceUSD;
uint256 lpPriceETH;
uint256 lpPriceUnderlying;
Withdrawal[] withdrawals;
uint256 blockNumber;
uint256 timestamp;
}
struct FetchDepositAmountsResponse {
bool isDepositPossible;
bool isDepositorWhitelisted;
uint256[] ratiosD18; // multiplied by 1e18 for weis of underlying tokens
address[] tokens;
uint256 expectedLpAmount; // in lp weis 1e18
uint256 expectedLpAmountUSDC; // in USDC weis 1e8 (due to chainlink decimals)
uint256[] expectedAmounts; // in underlying tokens weis
uint256[] expectedAmountsUSDC; // in USDC weis 1e8 (due to chainlink decimals)
}
struct CollectEigenLayerWithdrawalsStack {
uint256[] withdrawals;
uint256[] transferredWithdrawals;
uint256 withdrawalDelay;
IStrategy strategy;
}
struct GetVaultAssetsELStack {
uint256[] withdrawals;
uint256 withdrawalDelay;
IStrategy strategy;
uint256 length;
IDelegationManager.Withdrawal data;
bool isClaimed;
uint256 shares;
uint256 vaultShares;
}
address public constant eth = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
address public immutable usd = address(bytes20(keccak256("usd-token-address")));
address public immutable wsteth;
address public immutable weth;
address public immutable steth;
Oracle public oracle;
constructor(address wsteth_, address weth_, address owner_) Ownable(owner_) {
wsteth = wsteth_;
steth = address(IWSTETH(wsteth_).stETH());
weth = weth_;
}
// Mutable functions
function setOracle(address oracle_) external onlyOwner {
oracle = Oracle(oracle_);
}
// View functions
function collect(address user, IERC4626 vault_) public view returns (Response memory r) {
MultiVault vault = MultiVault(address(vault_));
r.vault = address(vault_);
r.asset = vault_.asset();
r.assetDecimals = IERC20Metadata(r.asset).decimals();
r.assetPriceX96 = oracle.priceX96(r.asset);
r.totalLP = vault_.totalSupply();
r.totalUnderlying = vault_.totalAssets();
r.totalETH = oracle.getValue(r.asset, r.totalUnderlying);
r.totalUSD = oracle.getValue(r.asset, usd, r.totalUnderlying);
r.limitUnderlying = vault.limit();
r.limitLP = vault.limit() > type(uint224).max
? type(uint256).max
: vault.previewWithdraw(r.limitUnderlying);
r.limitETH = oracle.getValue(r.asset, r.limitUnderlying);
r.limitUSD = oracle.getValue(r.asset, usd, r.limitUnderlying);
r.userLP = vault.balanceOf(user);
r.userUnderlying = vault.convertToAssets(r.userLP);
r.userETH = oracle.getValue(r.asset, r.userUnderlying);
r.userUSD = oracle.getValue(r.asset, usd, r.userUnderlying);
r.lpPriceUnderlying = vault.previewRedeem(1 ether);
r.lpPriceETH = oracle.getValue(r.asset, r.lpPriceUnderlying);
r.lpPriceUSD = oracle.getValue(r.asset, usd, r.lpPriceUnderlying);
r.blockNumber = block.number;
r.timestamp = block.timestamp;
if (address(vault) == 0x5E362eb2c0706Bd1d134689eC75176018385430B) {
return r;
}
Withdrawal[] memory withdrawals = new Withdrawal[](vault.subvaultsCount() * 50);
uint256 iterator = 0;
uint256 subvaultsCount = vault.subvaultsCount();
IMultiVaultStorage.Subvault memory subvault;
for (uint256 subvaultIndex = 0; subvaultIndex < subvaultsCount; subvaultIndex++) {
subvault = vault.subvaultAt(subvaultIndex);
if (subvault.withdrawalQueue == address(0)) {
continue;
}
IWithdrawalQueue queue = IWithdrawalQueue(subvault.withdrawalQueue);
uint256 claimable = queue.claimableAssetsOf(user);
if (claimable != 0) {
withdrawals[iterator++] = Withdrawal({
subvaultIndex: subvaultIndex,
assets: claimable,
isTimestamp: true,
claimingTime: 0,
withdrawalIndex: 0,
withdrawalRequestType: 0
});
}
if (queue.pendingAssetsOf(user) == 0) {
continue;
}
Withdrawal[] memory w = subvault.protocol == IMultiVaultStorage.Protocol.SYMBIOTIC
? collectSymbioticWithdrawals(
user, subvaultIndex, ISymbioticWithdrawalQueue(subvault.withdrawalQueue)
)
: subvault.protocol == IMultiVaultStorage.Protocol.EIGEN_LAYER
? collectEigenLayerWithdrawals(
user, subvaultIndex, IEigenLayerWithdrawalQueue(subvault.withdrawalQueue)
)
: new Withdrawal[](0);
for (uint256 i = 0; i < w.length; i++) {
withdrawals[iterator++] = w[i];
}
}
assembly {
mstore(withdrawals, iterator)
}
r.withdrawals = withdrawals;
}
function collectSymbioticWithdrawals(
address user,
uint256 subvaultIndex,
ISymbioticWithdrawalQueue q
) public view returns (Withdrawal[] memory withdrawals_) {
withdrawals_ = new Withdrawal[](2);
uint256 iterator = 0;
(uint256 sharesToClaimPrev, uint256 sharesToClaim,, uint256 claimEpoch) =
q.getAccountData(user);
ISymbioticVault symbioticVault = q.symbioticVault();
uint256 currentEpoch = q.getCurrentEpoch();
if (claimEpoch == currentEpoch + 1) {
if (sharesToClaimPrev != 0) {
ISymbioticWithdrawalQueue.EpochData memory epochData = q.getEpochData(currentEpoch);
uint256 assets = Math.mulDiv(
symbioticVault.withdrawalsOf(currentEpoch, address(q)),
sharesToClaimPrev,
epochData.sharesToClaim
);
if (assets != 0) {
withdrawals_[iterator++] = Withdrawal({
subvaultIndex: subvaultIndex,
assets: assets,
isTimestamp: true,
claimingTime: symbioticVault.currentEpochStart()
+ symbioticVault.epochDuration(),
withdrawalIndex: 0,
withdrawalRequestType: 0
});
}
}
if (sharesToClaim != 0) {
ISymbioticWithdrawalQueue.EpochData memory epochData =
q.getEpochData(currentEpoch + 1);
uint256 assets = Math.mulDiv(
symbioticVault.withdrawalsOf(currentEpoch + 1, address(q)),
sharesToClaim,
epochData.sharesToClaim
);
if (assets != 0) {
withdrawals_[iterator++] = Withdrawal({
subvaultIndex: subvaultIndex,
assets: assets,
isTimestamp: true,
claimingTime: symbioticVault.currentEpochStart()
+ 2 * symbioticVault.epochDuration(),
withdrawalIndex: 0,
withdrawalRequestType: 0
});
}
}
} else if (claimEpoch == currentEpoch) {
if (sharesToClaim != 0) {
ISymbioticWithdrawalQueue.EpochData memory epochData = q.getEpochData(currentEpoch);
uint256 assets = Math.mulDiv(
symbioticVault.withdrawalsOf(currentEpoch, address(q)),
sharesToClaim,
epochData.sharesToClaim
);
if (assets != 0) {
withdrawals_[iterator++] = Withdrawal({
subvaultIndex: subvaultIndex,
assets: assets,
isTimestamp: true,
claimingTime: symbioticVault.currentEpochStart()
+ symbioticVault.epochDuration(),
withdrawalIndex: 0,
withdrawalRequestType: 0
});
}
}
}
assembly {
mstore(withdrawals_, iterator)
}
}
function collectEigenLayerWithdrawals(
address user,
uint256 subvaultIndex,
IEigenLayerWithdrawalQueue queue
) public view returns (Withdrawal[] memory withdrawals_) {
CollectEigenLayerWithdrawalsStack memory s;
(, s.withdrawals, s.transferredWithdrawals) =
queue.getAccountData(user, type(uint256).max, 0, type(uint256).max, 0);
s.withdrawalDelay = IDelegationManager(queue.delegation()).minWithdrawalDelayBlocks() + 1;
uint256 currentBlock = block.number;
withdrawals_ = new Withdrawal[](s.withdrawals.length + s.transferredWithdrawals.length);
uint256 iterator = 0;
for (uint256 i = 0; i < s.withdrawals.length; i++) {
(
IDelegationManager.Withdrawal memory data,
bool isClaimed,
,
uint256 shares,
uint256 accountShares
) = queue.getWithdrawalRequest(s.withdrawals[i], user);
if (isClaimed || data.startBlock + s.withdrawalDelay <= currentBlock) {
continue;
}
uint256 pendingShares = queue.convertScaledSharesToShares(data, accountShares, shares);
uint256 pendingAssets = pendingShares == 0
? 0
: IIsolatedEigenLayerVault(queue.isolatedVault()).sharesToUnderlyingView(
queue.strategy(), pendingShares
);
withdrawals_[iterator++] = Withdrawal({
subvaultIndex: subvaultIndex,
assets: pendingAssets,
isTimestamp: false,
claimingTime: data.startBlock + s.withdrawalDelay,
withdrawalIndex: s.withdrawals[i],
withdrawalRequestType: 0
});
}
for (uint256 i = 0; i < s.transferredWithdrawals.length; i++) {
(
IDelegationManager.Withdrawal memory data,
bool isClaimed,
uint256 assets,
uint256 shares,
uint256 accountShares
) = queue.getWithdrawalRequest(s.withdrawals[i], user);
withdrawals_[iterator] = Withdrawal({
subvaultIndex: subvaultIndex,
assets: 0,
isTimestamp: false,
claimingTime: data.startBlock + s.withdrawalDelay,
withdrawalIndex: s.withdrawals[i],
withdrawalRequestType: 1
});
if (isClaimed) {
uint256 claimalbleAssets =
shares == accountShares ? assets : Math.mulDiv(assets, accountShares, shares);
withdrawals_[iterator++].assets = claimalbleAssets;
} else {
uint256 pendingShares =
queue.convertScaledSharesToShares(data, accountShares, shares);
uint256 pendingAssets = pendingShares == 0
? 0
: IIsolatedEigenLayerVault(queue.isolatedVault()).sharesToUnderlyingView(
queue.strategy(), pendingShares
);
withdrawals_[iterator++].assets = pendingAssets;
}
}
assembly {
mstore(withdrawals_, iterator)
}
}
function collect(address user, address[] memory vaults)
public
view
returns (Response[] memory responses)
{
responses = new Response[](vaults.length);
for (uint256 i = 0; i < vaults.length; i++) {
responses[i] = collect(user, IERC4626(vaults[i]));
}
}
function multiCollect(address[] calldata users, address[] calldata vaults)
external
view
returns (Response[][] memory responses)
{
responses = new Response[][](users.length);
for (uint256 i = 0; i < users.length; i++) {
responses[i] = collect(users[i], vaults);
}
}
function fetchWithdrawalAmounts(uint256 lpAmount, address vault)
external
view
returns (uint256[] memory expectedAmounts, uint256[] memory expectedAmountsUSDC)
{
expectedAmounts = new uint256[](1);
expectedAmountsUSDC = new uint256[](1);
expectedAmounts[0] = IERC4626(vault).previewRedeem(lpAmount);
expectedAmountsUSDC[0] = oracle.getValue(IERC4626(vault).asset(), usd, expectedAmounts[0]);
}
function fetchDepositWrapperParams(address vault, address user, address token, uint256 amount)
external
view
returns (
bool isDepositPossible,
bool isDepositorWhitelisted,
bool isWhitelistedToken,
uint256 lpAmount,
uint256 depositValueUSDC
)
{
if (MultiVault(vault).depositPause()) {
return (false, false, false, 0, 0);
}
isDepositPossible = true;
if (MultiVault(vault).depositWhitelist() && !MultiVault(vault).isDepositorWhitelisted(user))
{
return (isDepositPossible, false, false, 0, 0);
}
isDepositorWhitelisted = true;
if (MultiVault(vault).asset() != wsteth) {
return (isDepositPossible, isDepositorWhitelisted, false, 0, 0);
}
if (token == weth || token == steth || token == wsteth || token == eth) {
isWhitelistedToken = true;
} else {
return (isDepositPossible, isDepositorWhitelisted, false, 0, 0);
}
if (token != wsteth) {
amount = IWSTETH(wsteth).getWstETHByStETH(amount);
}
lpAmount = MultiVault(vault).previewDeposit(amount);
depositValueUSDC = oracle.getValue(wsteth, usd, amount);
}
function fetchDepositAmounts(uint256[] memory amounts, address vault, address user)
external
view
returns (FetchDepositAmountsResponse memory r)
{
if (MultiVault(vault).depositPause()) {
return r;
}
r.isDepositPossible = true;
if (MultiVault(vault).depositWhitelist() && !MultiVault(vault).isDepositorWhitelisted(user))
{
return r;
}
r.isDepositorWhitelisted = true;
r.ratiosD18 = new uint256[](1);
r.ratiosD18[0] = 1 ether;
r.tokens = new address[](1);
r.tokens[0] = MultiVault(vault).asset();
r.expectedLpAmount = MultiVault(vault).previewDeposit(amounts[0]);
r.expectedLpAmountUSDC = oracle.getValue(MultiVault(vault).asset(), usd, amounts[0]);
r.expectedAmounts = new uint256[](1);
r.expectedAmounts[0] = amounts[0];
r.expectedAmountsUSDC = new uint256[](1);
r.expectedAmountsUSDC[0] = r.expectedLpAmountUSDC;
}
function getVaultAssets(MultiVault v, address user, uint256 shares)
public
view
returns (
uint256 accountAssets,
uint256 accountInstantAssets,
Withdrawal[] memory withdrawals
)
{
if (address(v) == 0x5E362eb2c0706Bd1d134689eC75176018385430B) {
// DVstETH
accountAssets = v.previewRedeem(shares == 0 ? v.balanceOf(user) : shares);
accountInstantAssets = accountAssets;
return (accountAssets, accountInstantAssets, new Withdrawal[](0));
}
IWithdrawalStrategy.WithdrawalData[] memory data;
{
IRatiosStrategy s = IRatiosStrategy(address(v.depositStrategy()));
(, uint256 liquid) = s.calculateState(address(v), false, 0);
accountAssets = v.previewRedeem(shares == 0 ? v.balanceOf(user) : shares);
accountInstantAssets = Math.min(liquid, accountAssets);
data = s.calculateWithdrawalAmounts(address(v), accountAssets);
}
withdrawals = new Withdrawal[](data.length * 128);
uint256 n = 0;
for (uint256 i = 0; i < data.length; i++) {
accountInstantAssets += data[i].claimable;
IMultiVaultStorage.Subvault memory subvault = v.subvaultAt(data[i].subvaultIndex);
if (subvault.withdrawalQueue == address(0)) {
continue;
}
if (data[i].staked != 0) {
// regular unstaking
if (subvault.protocol == IMultiVaultStorage.Protocol.SYMBIOTIC) {
ISymbioticVault symbioticVault = ISymbioticVault(subvault.vault);
withdrawals[n++] = Withdrawal({
subvaultIndex: data[i].subvaultIndex,
assets: data[i].staked,
isTimestamp: true,
claimingTime: symbioticVault.currentEpochStart()
+ 2 * symbioticVault.epochDuration(),
withdrawalIndex: 0,
withdrawalRequestType: 0
});
} else if (subvault.protocol == IMultiVaultStorage.Protocol.EIGEN_LAYER) {
IEigenLayerWithdrawalQueue queue =
IEigenLayerWithdrawalQueue(subvault.withdrawalQueue);
withdrawals[n++] = Withdrawal({
subvaultIndex: data[i].subvaultIndex,
assets: data[i].staked,
isTimestamp: false,
claimingTime: block.number
+ IDelegationManager(queue.delegation()).minWithdrawalDelayBlocks() + 1,
withdrawalIndex: queue.withdrawalRequests(),
withdrawalRequestType: 0
});
} else {
revert("Unsupported protocol");
}
}
if (data[i].pending != 0) {
if (subvault.protocol == IMultiVaultStorage.Protocol.SYMBIOTIC) {
ISymbioticWithdrawalQueue queue =
ISymbioticWithdrawalQueue(subvault.withdrawalQueue);
(, uint256 sharesToClaim,, uint256 claimEpoch) =
queue.getAccountData(address(v));
ISymbioticVault symbioticVault = queue.symbioticVault();
uint256 currentEpoch = symbioticVault.currentEpoch();
if (claimEpoch == currentEpoch + 1) {
if (sharesToClaim != 0) {
ISymbioticWithdrawalQueue.EpochData memory epochData =
queue.getEpochData(currentEpoch + 1);
uint256 assets = Math.mulDiv(
symbioticVault.withdrawalsOf(currentEpoch + 1, address(queue)),
sharesToClaim,
epochData.sharesToClaim
);
if (assets != 0) {
assets = Math.min(assets, data[i].pending);
data[i].pending -= assets;
withdrawals[n++] = Withdrawal({
subvaultIndex: data[i].subvaultIndex,
assets: assets,
isTimestamp: true,
claimingTime: symbioticVault.currentEpochStart()
+ 2 * symbioticVault.epochDuration(),
withdrawalIndex: 0,
withdrawalRequestType: 0
});
}
}
if (data[i].pending != 0) {
withdrawals[n++] = Withdrawal({
subvaultIndex: data[i].subvaultIndex,
assets: data[i].pending,
isTimestamp: true,
claimingTime: symbioticVault.currentEpochStart()
+ symbioticVault.epochDuration(),
withdrawalIndex: 0,
withdrawalRequestType: 0
});
}
} else if (claimEpoch == currentEpoch) {
withdrawals[n++] = Withdrawal({
subvaultIndex: data[i].subvaultIndex,
assets: data[i].pending,
isTimestamp: true,
claimingTime: symbioticVault.currentEpochStart()
+ symbioticVault.epochDuration(),
withdrawalIndex: 0,
withdrawalRequestType: 0
});
} else {
revert("Invalid state!");
}
} else if (subvault.protocol == IMultiVaultStorage.Protocol.EIGEN_LAYER) {
GetVaultAssetsELStack memory s;
IEigenLayerWithdrawalQueue queue =
IEigenLayerWithdrawalQueue(subvault.withdrawalQueue);
(, s.withdrawals,) =
queue.getAccountData(address(v), type(uint256).max, 0, 0, 0);
s.withdrawalDelay =
IDelegationManager(queue.delegation()).minWithdrawalDelayBlocks() + 1;
s.strategy = IStrategy(queue.strategy());
s.length = s.withdrawals.length;
// Filter out claimed and claimable withdrawals
for (uint256 index = 0; index < s.length;) {
(s.data, s.isClaimed,,,) =
queue.getWithdrawalRequest(s.withdrawals[index], address(v));
if (s.isClaimed || s.data.startBlock + s.withdrawalDelay <= block.number) {
s.withdrawals[index] = s.withdrawals[--s.length];
} else {
index++;
}
}
for (uint256 index = 0; index < s.length; index++) {
(s.data,,, s.shares, s.vaultShares) =
queue.getWithdrawalRequest(s.withdrawals[index], address(v));
uint256 transferrableAssets = IIsolatedEigenLayerVault(
queue.isolatedVault()
).sharesToUnderlyingView(
queue.strategy(),
queue.convertScaledSharesToShares(s.data, s.vaultShares, s.shares)
);
if (transferrableAssets == 0) {
continue;
}
if (transferrableAssets >= data[i].pending) {
withdrawals[n++] = Withdrawal({
subvaultIndex: data[i].subvaultIndex,
assets: data[i].pending,
isTimestamp: false,
claimingTime: s.data.startBlock + s.withdrawalDelay,
withdrawalIndex: s.withdrawals[index],
withdrawalRequestType: 1
});
data[i].pending = 0;
break;
} else {
withdrawals[n++] = Withdrawal({
subvaultIndex: data[i].subvaultIndex,
assets: transferrableAssets,
isTimestamp: false,
claimingTime: s.data.startBlock + s.withdrawalDelay,
withdrawalIndex: s.withdrawals[index],
withdrawalRequestType: 1
});
data[i].pending -= transferrableAssets;
}
}
if (data[i].pending != 0) {
withdrawals[n++] = Withdrawal({
subvaultIndex: data[i].subvaultIndex,
assets: data[i].pending,
isTimestamp: false,
claimingTime: 0,
withdrawalIndex: 0,
withdrawalRequestType: 2 /* roundings, et cetera */
});
}
} else {
revert("Unsupported protocol");
}
}
}
assembly {
mstore(withdrawals, n)
}
}
}
"
},
"src/interfaces/queues/IEigenLayerWithdrawalQueue.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;
import {IIsolatedEigenLayerVault} from "../adapters/IIsolatedEigenLayerVault.sol";
import {IWithdrawalQueue} from "./IWithdrawalQueue.sol";
import {IDelegationManager} from "@eigenlayer-interfaces/IDelegationManager.sol";
import {IStrategy} from "@eigenlayer-interfaces/IStrategy.sol";
import {IDelegationManagerExtended} from "../utils/IDelegationManagerExtended.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
interface IEigenLayerWithdrawalQueue is IWithdrawalQueue {
struct WithdrawalData {
IDelegationManager.Withdrawal data;
bool isClaimed;
uint256 assets;
uint256 shares;
mapping(address account => uint256) sharesOf;
}
struct AccountData {
uint256 claimableAssets;
EnumerableSet.UintSet withdrawals;
EnumerableSet.UintSet transferredWithdrawals;
}
function MAX_WITHDRAWALS() external view returns (uint256);
function isolatedVault() external view returns (address);
function claimer() external view returns (address);
function delegation() external view returns (address);
function strategy() external view returns (address);
function operator() external view returns (address);
function isShutdown() external view returns (bool);
function latestWithdrawableBlock() external view returns (uint256);
function convertScaledSharesToShares(
IDelegationManager.Withdrawal memory withdrawal,
uint256 scaledShares,
uint256 totalScaledShares
) external view returns (uint256);
function getAccountData(
address account,
uint256 withdrawalsLimit,
uint256 withdrawalsOffset,
uint256 transferredWithdrawalsLimit,
uint256 transferredWithdrawalsOffset
)
external
view
returns (
uint256 claimableAssets,
uint256[] memory withdrawals,
uint256[] memory transferredWithdrawals
);
function getWithdrawalRequest(uint256 index, address account)
external
view
returns (
IDelegationManager.Withdrawal memory data,
bool isClaimed,
uint256 assets,
uint256 shares,
uint256 accountShares
);
function withdrawalRequests() external view returns (uint256);
function initialize(address isolatedVault_, address strategy_, address operator_) external;
function request(address account, uint256 assets, bool isSelfRequested) external;
function handleWithdrawals(address account) external;
function acceptPendingAssets(address account, uint256[] calldata withdrawals_) external;
// permissionless function
function shutdown(uint32 blockNumber, uint256 shares) external;
event Transfer(
address indexed from, address indexed to, uint256 indexed withdrawalIndex, uint256 assets
);
event Pull(uint256 indexed withdrawalIndex, uint256 assets);
event Handled(address indexed account, uint256 indexed withdrawalIndex, uint256 assets);
event Request(
address indexed account,
uint256 indexed withdrawalIndex,
uint256 assets,
bool isSelfRequested
);
event Claimed(address indexed account, address indexed to, uint256 assets);
event Accepted(address indexed account, uint256 indexed withdrawalIndex);
event Shutdown(address indexed sender, uint32 indexed blockNumber, uint256 indexed shares);
}
"
},
"src/interfaces/queues/ISymbioticWithdrawalQueue.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;
import {IWithdrawalQueue} from "./IWithdrawalQueue.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {IVault as ISymbioticVault} from "@symbiotic/core/interfaces/vault/IVault.sol";
/**
* @title ISymbioticWithdrawalQueue
* @notice Interface to handle the withdrawal process from the Symbiotic Vault.
* @dev This interface is an extension of IWithdrawalQueue for interacting specifically with the Symbiotic Vault.
*/
interface ISymbioticWithdrawalQueue is IWithdrawalQueue {
/**
* @notice Struct to hold epoch-related data.
* @param isClaimed Indicates whether the epoch has been claimed.
* @param sharesToClaim The amount of shares to be claimed.
* @param claimableAssets The amount of assets that can be claimed.
*/
struct EpochData {
bool isClaimed;
uint256 sharesToClaim;
uint256 claimableAssets;
}
/**
* @notice Struct to store account-related data for withdrawals.
* @param sharesToClaim A mapping of epochs to shares to be claimed for a given epoch.
* @param claimableAssets The total amount of assets that can be claimed.
* @param claimEpoch The most recent epoch requested for withdrawal.
*/
struct AccountData {
mapping(uint256 => uint256) sharesToClaim;
uint256 claimableAssets;
uint256 claimEpoch;
}
/**
* @notice Returns the address of the associated MellowSymbioticVault.
* @return vault The address of the MellowSymbioticVault.
*/
function vault() external view returns (address);
/**
* @notice Returns the address of the associated Symbiotic Vault.
* @return symbioticVault The address of the Symbiotic Vault.
*/
function symbioticVault() external view returns (ISymbioticVault);
/**
* @notice Returns the address of the collateral token used by the vault.
* @dev The collateral token is the same as the vault's asset.
* @return collateralAddress The address of the collateral token.
*/
function collateral() external view returns (address);
/**
* @notice Returns the current epoch of the Symbiotic Vault.
* @return currentEpoch The current epoch of the Symbiotic Vault.
*/
function getCurrentEpoch() external view returns (uint256);
/**
* @notice Returns the data for a specific account.
* @param account The address of the account to retrieve data for.
* @return sharesToClaimPrev The amount of shares to claim for the epoch before the last requested epoch.
* @return sharesToClaim The amount of shares to claim for the last requested epoch.
* @return claimableAssets The total amount of assets that can be claimed.
* @return claimEpoch The most recent epoch requested for withdrawal.
*/
function getAccountData(address account)
external
view
returns (
uint256 sharesToClaimPrev,
uint256 sharesToClaim,
uint256 claimableAssets,
uint256 claimEpoch
);
/**
* @notice Returns the data for a specific epoch.
* @param epoch The epoch number to retrieve data for.
* @return epochData The data for the specified epoch.
*/
function getEpochData(uint256 epoch) external view returns (EpochData memory);
/**
* @notice Returns the total amount of pending assets awaiting withdrawal.
* @dev This amount may decrease due to slashing events in the Symbiotic Vault.
* @return assets The total amount of assets pending withdrawal.
*/
function pendingAssets() external view returns (uint256);
/**
* @notice Returns the amount of assets in the withdrawal queue for a specific account that cannot be claimed yet.
* @param account The address of the account.
* @return assets The amount of pending assets in the withdrawal queue for the account.
*/
function pendingAssetsOf(address account) external view returns (uint256 assets);
/**
* @notice Returns the amount of assets that can be claimed by an account.
* @param account The address of the account.
* @return assets The amount of assets claimable by the account.
*/
function claimableAssetsOf(address account) external view returns (uint256 assets);
/**
* @notice Requests the withdrawal of a specified amount of collateral for a given account.
* @param account The address of the account requesting the withdrawal.
* @param amount The amount of collateral to withdraw.
*
* @custom:requirements
* - `msg.sender` MUST be the vault.
* - `amount` MUST be greater than zero.
*
* @custom:effects
* - Emits a `WithdrawalRequested` event.
*/
function request(address account, uint256 amount) external;
/**
* @notice Claims assets from the Symbiotic Vault for a specified epoch to the Withdrawal Queue address.
* @param epoch The epoch number.
* @dev Emits an EpochClaimed event.
*/
function pull(uint256 epoch) external;
/**
* @notice Finalizes the withdrawal process for a specific account and transfers assets to the recipient.
* @param account The address of the account requesting the withdrawal.
* @param recipient The address of the recipient receiving the withdrawn assets.
* @param maxAmount The maximum amount of assets to withdraw.
* @return amount The actual amount of assets withdrawn.
*/
function claim(address account, address recipient, uint256 maxAmount)
external
returns (uint256 amount);
/**
* @notice Handles the pending epochs for a specific account and makes assets claimable for recent epochs.
* @param account The address of the account.
* @dev Emits an EpochClaimed event.
*/
function handlePendingEpochs(address account) external;
/// @notice Emitted when a withdrawal request is created.
event WithdrawalRequested(address indexed account, uint256 indexed epoch, uint256 amount);
/// @notice Emitted when assets are successfully claimed for a specific epoch.
event EpochClaimed(uint256 indexed epoch, uint256 claimedAssets);
/// @notice Emitted when assets are successfully withdrawn and transferred to a recipient.
event Claimed(address indexed account, address indexed recipient, uint256 amount);
/// @notice Emitted when pending assets are successfully transferred from one account to another.
event Transfer(address indexed from, address indexed to, uint256 indexed epoch, uint256 amount);
}
"
},
"src/interfaces/tokens/IWSTETH.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;
import "./ISTETH.sol";
interface IWSTETH is IERC20 {
function wrap(uint256 _stETHAmount) external returns (uint256 wstETHAmount);
function unwrap(uint256 _wstETHAmount) external returns (uint256 stETHAmount);
function getWstETHByStETH(uint256 _stETHAmount) external view returns (uint256);
function getStETHByWstETH(uint256 _wstETHAmount) external view returns (uint256);
function stETH() external view returns (ISTETH);
}
"
},
"src/strategies/RatiosStrategy.sol": {
"content": "// // SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;
import "../interfaces/strategies/IRatiosStrategy.sol";
contract RatiosStrategy is IRatiosStrategy {
/// @inheritdoc IRatiosStrategy
uint256 public constant D18 = 1e18;
/// @inheritdoc IRatiosStrategy
bytes32 public constant RATIOS_STRATEGY_SET_RATIOS_ROLE =
keccak256("RATIOS_STRATEGY_SET_RATIOS_ROLE");
/// @inheritdoc IRatiosStrategy
mapping(address vault => mapping(address subvault => Ratio)) public ratios;
/// @inheritdoc IRatiosStrategy
function setRatios(address vault, address[] calldata subvaults, Ratio[] calldata ratios_)
external
{
require(
IAccessControl(vault).hasRole(RATIOS_STRATEGY_SET_RATIOS_ROLE, msg.sender),
"RatiosStrategy: forbidden"
);
require(
subvaults.length == ratios_.length,
"RatiosStrategy: subvaults and ratios length mismatch"
);
IMultiVault multiVault = IMultiVault(vault);
uint256 n = subvaults.length;
for (uint256 i = 0; i < n; i++) {
if (multiVault.indexOfSubvault(subvaults[i]) != 0) {
require(
ratios_[i].minRatioD18 <= ratios_[i].maxRatioD18
&& ratios_[i].maxRatioD18 <= D18,
"RatiosStrategy: invalid ratios"
);
} else {
require(
ratios_[i].minRatioD18 == 0 && ratios_[i].maxRatioD18 == 0,
"RatiosStrategy: invalid subvault"
);
}
}
mapping(address => Ratio) storage vaultRatios_ = ratios[vault];
for (uint256 i = 0; i < n; i++) {
vaultRatios_[subvaults[i]] = ratios_[i];
}
emit RatiosSet(vault, subvaults, ratios_);
}
/// @inheritdoc IRatiosStrategy
function calculateState(address vault, bool isDeposit, uint256 increment)
public
view
returns (Amounts[] memory state, uint256 liquid)
{
IMultiVault multiVault = IMultiVault(vault);
uint256 n = multiVault.subvaultsCount();
state = new Amounts[](n);
liquid = IERC20(IERC4626(vault).asset()).balanceOf(vault);
IDefaultCollateral collateral = multiVault.defaultCollateral();
if (address(collateral) != address(0)) {
liquid += collateral.balanceOf(vault);
}
uint256 totalAssets = liquid;
IMultiVaultStorage.Subvault memory subvault;
for (uint256 i = 0; i < n; i++) {
subvault = multiVault.subvaultAt(i);
IProtocolAdapter adapter = multiVault.adapterOf(subvault.protocol);
state[i].staked = adapter.stakedAt(subvault.vault);
if (!isDeposit && adapter.areWithdrawalsPaused(subvault.vault, vault)) {
revert("RatiosStrategy: withdrawals paused");
}
if (subvault.withdrawalQueue != address(0)) {
state[i].claimable =
IWithdrawalQueue(subvault.withdrawalQueue).claimableAssetsOf(vault);
state[i].pending = IWithdrawalQueue(subvault.withdrawalQueue).pendingAssetsOf(vault);
totalAssets += state[i].staked + state[i].pending + state[i].claimable;
} else {
totalAssets += state[i].staked;
}
uint256 maxDeposit = adapter.maxDeposit(subvault.vault);
if (type(uint256).max - state[i].staked > maxDeposit) {
state[i].max = maxDeposit + state[i].staked;
} else {
state[i].max = type(uint256).max;
}
}
totalAssets = isDeposit ? totalAssets + increment : totalAssets - increment;
mapping(address => Ratio) storage vaultRatios_ = ratios[vault];
for (uint256 i = 0; i < n; i++) {
Ratio memory ratio = vaultRatios_[multiVault.subvaultAt(i).vault];
if (ratio.maxRatioD18 == 0) {
state[i].max = 0;
state[i].min = 0;
} else {
state[i].max = Math.min(state[i].max, (totalAssets * ratio.maxRatioD18) / D18);
state[i].min = Math.min(state[i].max, (totalAssets * ratio.minRatioD18) / D18);
}
}
}
/// @inheritdoc IDepositStrategy
function calculateDepositAmounts(address vault, uint256 amount)
external
view
override
returns (DepositData[] memory data)
{
(Amounts[] memory state,) = calculateState(vault, true, amount);
uint256 n = state.length;
for (uint256 i = 0; i < n; i++) {
uint256 assets_ = state[i].staked;
if (state[i].min > assets_) {
state[i].min -= assets_;
state[i].max -= assets_;
} else if (state[i].max > assets_) {
state[i].min = 0;
state[i].max -= assets_;
} else {
state[i].min = 0;
state[i].max = 0;
}
}
data = new DepositData[](n);
for (uint256 i = 0; i < n && amount != 0; i++) {
data[i].subvaultIndex = i;
if (state[i].min == 0) {
continue;
}
uint256 assets_ = Math.min(state[i].min, amount);
state[i].max -= assets_;
amount -= assets_;
data[i].deposit = assets_;
}
for (uint256 i = 0; i < n && amount != 0; i++) {
if (state[i].max == 0) {
continue;
}
uint256 assets_ = Math.min(state[i].max, amount);
amount -= assets_;
data[i].deposit += assets_;
}
uint256 count = 0;
for (uint256 i = 0; i < n; i++) {
if (data[i].deposit != 0) {
if (count != i) {
data[count] = data[i];
}
count++;
}
}
assembly {
mstore(data, count)
}
}
/// @inheritdoc IWithdrawalStrategy
function calculateWithdrawalAmounts(address vault, uint256 amount)
external
view
override
returns (WithdrawalData[] memory data)
{
(Amounts[] memory state, uint256 liquid) = calculateState(vault, false, amount);
if (amount <= liquid) {
return data;
}
amount -= liquid;
uint256 n = state.length;
data = new WithdrawalData[](n);
for (uint256 i = 0; i < n && amount != 0; i++) {
data[i].subvaultIndex = i;
if (state[i].staked > state[i].max) {
uint256 extra = state[i].staked - state[i].max;
if (extra > amount) {
data[i].staked = amount;
amount = 0;
} else {
data[i].staked = extra;
amount -= extra;
}
state[i].staked -= data[i].staked;
}
}
for (uint256 i = 0; i < n && amount != 0; i++) {
if (state[i].staked > state[i].min) {
uint256 allowed = state[i].staked - state[i].min;
if (allowed > amount) {
data[i].staked += amount;
amount = 0;
} else {
data[i].staked += allowed;
amount -= allowed;
state[i].staked -= allowed;
}
}
}
for (uint256 i = 0; i < n && amount != 0; i++) {
if (state[i].pending > 0) {
if (state[i].pending > amount) {
data[i].pending += amount;
amount = 0;
} else {
data[i].pending += state[i].pending;
amount -= state[i].pending;
}
}
}
for (uint256 i = 0; i < n && amount != 0; i++) {
if (state[i].claimable > 0) {
if (state[i].claimable > amount) {
data[i].claimable += amount;
amount = 0;
} else {
data[i].claimable += state[i].claimable;
amount -= state[i].claimable;
}
}
}
for (uint256 i = 0; i < n && amount != 0; i++) {
uint256 staked = state[i].staked;
if (staked > 0) {
if (staked > amount) {
data[i].staked += amount;
amount = 0;
} else {
data[i].staked += staked;
amount -= staked;
}
}
}
uint256 count = 0;
for (uint256 i = 0; i < n; i++) {
if (data[i].staked + data[i].pending + data[i].claimable != 0) {
if (count != i) {
data[count] = data[i];
}
count++;
}
}
assembly {
mstore(data, count)
}
}
/// @inheritdoc IRebalanceStrategy
function calculateRebalanceAmounts(address vault)
external
view
override
returns (RebalanceData[] memory data)
{
(Amounts[] memory state, uint256 liquid) = calculateState(vault, false, 0);
uint256 n = state.length;
data = new RebalanceData[](n);
uint256 totalRequired = 0;
uint256 pending = 0;
for (uint256 i = 0; i < n; i++) {
data[i].subvaultIndex = i;
data[i].claimable = state[i].claimable;
liquid += state[i].claimable;
pending += state[i].pending;
if (state[i].staked > state[i].max) {
data[i].staked = state[i].staked - state[i].max;
pending += data[i].staked;
state[i].staked = state[i].max;
}
if (state[i].min > state[i].staked) {
totalRequired += state[i].min - state[i].staked;
}
}
if (totalRequired > liquid + pending) {
uint256 unstake = totalRequired - liquid - pending;
for (uint256 i = 0; i < n && unstake > 0; i++) {
if (state[i].staked > state[i].min) {
uint256 allowed = state[i].staked - state[i].min;
if (allowed > unstake) {
data[i].staked += unstake;
unstake = 0;
} else {
data[i].staked += allowed;
unstake -= allowed;
}
}
}
}
for (uint256 i = 0; i < n && liquid > 0; i++) {
if (state[i].staked < state[i].min) {
uint256 required = state[i].min - state[i].staked;
if (required > liquid) {
data[i].deposit = liquid;
liquid = 0;
} else {
data[i].deposit = required;
liquid -= required;
state[i].max -= data[i].deposit;
}
}
}
for (uint256 i = 0; i < n && liquid > 0; i++) {
if (state[i].staked < state[i].max) {
uint256 allowed = state[i].max - state[i].staked;
if (allowed > liquid) {
data[i].deposit += liquid;
liquid = 0;
} else {
data[i].deposit += allowed;
liquid -= allowed;
}
}
}
}
}
"
},
"src/vaults/MultiVault.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;
import "../interfaces/vaults/IMultiVault.sol";
import {ERC4626Vault} from "./ERC4626Vault.sol";
import {MultiVaultStorage} from "./MultiVaultStorage.sol";
import {VaultControlStorage} from "./VaultControlStorage.sol";
contract MultiVault is IMultiVault, ERC4626Vault, MultiVaultStorage {
using SafeERC20 for IERC20;
using Math for uint256;
uint256 private constant D6 = 1e6;
bytes32 public constant ADD_SUBVAULT_ROLE = keccak256("ADD_SUBVAULT_ROLE");
bytes32 public constant REMOVE_SUBVAULT_ROLE = keccak256("REMOVE_SUBVAULT_ROLE");
bytes32 public constant SET_STRATEGY_ROLE = keccak256("SET_STRATEGY_ROLE");
bytes32 public constant SET_FARM_ROLE = keccak256("SET_FARM_ROLE");
bytes32 public constant REBALANCE_ROLE = keccak256("REBALANCE_ROLE");
bytes32 public constant SET_DEFAULT_COLLATERAL_ROLE = keccak256("SET_DEFAULT_COLLATERAL_ROLE");
bytes32 public constant SET_ADAPTER_ROLE = keccak256("SET_ADAPTER_ROLE");
constructor(bytes32 name_, uint256 version_)
VaultControlStorage(name_, version_)
MultiVaultStorage(name_, version_)
{
_disableInitializers();
}
// ------------------------------- EXTERNAL VIEW FUNCTIONS -------------------------------
/// @inheritdoc IERC4626
function totalAssets()
public
view
virtual
override(IERC4626, ERC4626Upgradeable)
returns (uint256 assets_)
{
address this_ = address(this);
assets_ = IERC20(asset()).balanceOf(this_);
IDefaultCollateral collateral = defaultCollateral();
if (address(collateral) != address(0)) {
assets_ += collateral.balanceOf(this_);
}
uint256 length = subvaultsCount();
Subvault memory subvault;
for (uint256 i = 0; i < length; i++) {
subvault = subvaultAt(i);
assets_ += adapterOf(subvault.protocol).stakedAt(subvault.vault);
if (subvault.withdrawalQueue != address(0)) {
assets_ += IWithdrawalQueue(subvault.withdrawalQueue).claimableAssetsOf(this_)
+ IWithdrawalQueue(subvault.withdrawalQueue).pendingAssetsOf(this_);
}
}
}
// ------------------------------- EXTERNAL MUTATIVE FUNCTIONS -------------------------------
/// @inheritdoc IMultiVault
function initialize(InitParams calldata initParams) public virtual reinitializer(2) {
__initializeERC4626(
initParams.admin,
initParams.limit,
initParams.depositPause,
initParams.withdrawalPause,
initParams.depositWhitelist,
initParams.asset,
initParams.name,
initParams.symbol
);
__initializeMultiVaultStorage(
initParams.depositStrategy,
initParams.withdrawalStrategy,
initParams.rebalanceStrategy,
initParams.defaultCollateral,
initParams.symbioticAdapter,
initParams.eigenLayerAdapter,
initParams.erc4626Adapter
);
require(
initParams.defaultCollateral == address(0)
|| IDefaultCollateral(initParams.defaultCollateral).asset() == initParams.asset,
"MultiVault: default collateral asset does not match the vault asset"
);
}
/// @inheritdoc IMultiVault
function addSubvault(address vault, Protocol protocol) external onlyRole(ADD_SUBVAULT_ROLE) {
IProtocolAdapter adapter = adapterOf(protocol);
require(
adapter.assetOf(vault) == asset(),
"MultiVault: subvault asset does not match the vault asset"
);
_addSubvault(vault, adapter.handleVault(vault), protocol);
}
/// @inheritdoc IMultiVault
function removeSubvault(address subvault) external onlyRole(REMOVE_SUBVAULT_ROLE) {
_removeSubvault(subvault);
}
/// @inheritdoc IMultiVault
function setDepositStrategy(address newDepositStrategy) external onlyRole(SET_STRATEGY_ROLE) {
require(
newDepositStrategy != address(0), "MultiVault: deposit strategy cannot be zero address"
);
_setDepositStrategy(newDepositStrategy);
}
/// @inheritdoc IMultiVault
function setWithdrawalStrategy(address newWithdrawalStrategy)
external
onlyRole(SET_STRATEGY_ROLE)
{
require(
newWithdrawalStrategy != address(0),
"MultiVault: withdrawal strategy cannot be zero address"
);
_setWithdrawalStrategy(newWithdrawalStrategy);
}
/// @inheritdoc IMultiVault
function setRebalanceStrategy(address newRebalanceStrategy)
external
onlyRole(SET_STRATEGY_ROLE)
{
require(
newRebalanceStrategy != address(0),
"MultiVault: rebalance strategy cannot be zero address"
);
_setRebalanceStrategy(newRebalanceStrategy);
}
/// @inheritdoc IMultiVault
function setDefaultCollateral(address defaultCollateral_)
external
onlyRole(SET_DEFAULT_COLLATERAL_ROLE)
{
require(
address(defaultCollateral()) == address(0) && defaultCollateral_ != address(0),
"MultiVault: default collateral already set or cannot be zero address"
);
require(
IDefaultCollateral(defaultCollateral_).asset() == asset(),
"MultiVault: default collateral asset does not match the vault asset"
);
_setDefaultCollateral(defaultCollateral_);
}
/// @inheritdoc IMultiVault
function setSymbioticAdapter(address adapter_) external onlyRole(SET_ADAPTER_ROLE) {
require(adapter_ != address(0), "MultiVault: adapter cannot be zero address");
_setSymbioticAdapter(adapter_);
}
/// @inheritdoc IMultiVault
function setEigenLayerAdapter(address adapter_) external onlyRole(SET_ADAPTER_ROLE) {
require(adapter_ != address(0), "MultiVault: adapter cannot be zero address");
_setEigenLayerAdapter(adapter_);
}
/// @inheritdoc IMultiVault
function setERC4626Adapter(address adapter_) external onlyRole(SET_ADAPTER_ROLE) {
require(adapter_ != address(0), "MultiVault: adapter cannot be zero address");
_setERC4626Adapter(adapter_);
}
/// @inheritdoc IMultiVault
function setRewardsData(uint256 farmId, RewardData calldata rewardData)
external
onlyRole(SET_FARM_ROLE)
{
if (rewardData.token != address(0)) {
require(
rewardData.token != asset() && rewardData.token != address(defaultCollateral()),
"MultiVault: reward token cannot be the same as the asset or default collateral"
);
require(rewardData.curatorFeeD6 <= D6, "MultiVault: curator fee exceeds 100%");
require(
rewardData.distributionFarm != address(0),
"MultiVault: distribution farm address cannot be zero"
);
if (rewardData.curatorFeeD6 != 0) {
require(
rewardData.curatorTreasury != address(0),
"MultiVault: curator treasury address cannot be zero when fee is set"
);
}
adapterOf(rewardData.protocol).validateRewardData(rewardData.data);
}
_setRewardData(farmId, rewardData);
}
/// @inheritdoc IMultiVault
function rebalance() external onlyRole(REBALANCE_ROLE) {
address this_ = address(this);
IRebalanceStrategy.RebalanceData[] memory data =
rebalanceStrategy().calculateRebalanceAmounts(this_);
for (uint256 i = 0; i < data.length; i++) {
_withdraw(data[i].subvaultIndex, data[i].staked, 0, data[i].claimable, this_, this_);
}
IDefaultCollateral collateral = defaultCollateral();
if (address(collateral) != address(0)) {
uint256 balance = collateral.balanceOf(this_);
if (balance != 0) {
collateral.withdraw(this_, balance);
}
}
for (uint256 i = 0; i < data.length; i++) {
_deposit(data[i].subvaultIndex, data[i].deposit);
}
_depositIntoCollateral();
emit Rebalance(data, block.timestamp);
}
/// @inheritdoc IMultiVault
function pushRewards(uint256 farmId, bytes calldata farmData) external nonReentrant {
require(farmIdsContains(farmId), "MultiVault: farm not found");
IMultiVaultStorage.RewardData memory data = rewardData(farmId);
IERC20 rewardToken = IERC20(data.token);
address this_ = address(this);
uint256 rewardAmount = rewardToken.balanceOf(this_);
Address.functionDelegateCall(
address(adapterOf(data.protocol)),
abi.encodeCall(
IProtocolAdapter.pushRewards, (address(rewardToken), farmData, data.data)
)
);
rewardAmount = rewardToken.balanceOf(this_) - rewardAmount;
if (rewardAmount == 0) {
return;
}
uint256 curatorFee = rewardAmount.mulDiv(data.curatorFeeD6, D6);
if (curatorFee != 0) {
rewardToken.safeTransfer(data.curatorTreasury, curatorFee);
}
rewardAmount = rewardAmount - curatorFee;
if (rewardAmount != 0) {
rewardToken.safeTransfer(data.distributionFarm, rewardAmount);
}
emit RewardsPushed(farmId, rewardAmount, curatorFee, block.timestamp);
}
// ------------------------------- INTERNAL MUTATIVE FUNCTIONS -------------------------------
/// @dev Deposits assets into the specified subvault
function _deposit(uint256 subvaultIndex, uint256 assets) private {
if (assets == 0) {
return;
}
Subvault memory subvault = subvaultAt(subvaultIndex);
Address.functionDelegateCall(
address(adapterOf(subvault.protocol)),
abi.encodeCall(IProtocolAdapter.deposit, (subvault.vault, assets))
);
}
/// @dev Withdraws assets from the specified subvault
function _withdraw(
uint256 subvaultIndex,
uint256 request,
uint256 pending,
uint256 claimable,
address owner,
address receiver
) private {
Subvault memory subvault = subvaultAt(subvaultIndex);
address this_ = address(this);
if (claimable != 0) {
IWithdrawalQueue(subvault.withdrawalQueue).claim(this_, receiver, claimable);
}
if (pending != 0) {
IWithdrawalQueue(subvault.withdrawalQueue).transferPendingAssets(receiver, pending);
}
if (request != 0) {
Address.functionDelegateCall(
address(adapterOf(subvault.protocol)),
abi.encodeCall(
IProtocolAdapter.withdraw,
(subvault.vault, subvault.withdrawalQueue, receiver, request, owner)
)
);
}
}
/// @dev Deposits assets into the default collateral
function _depositIntoCollateral() private {
IDefaultCollateral collateral = defaultCollateral();
if (address(collateral) == address(0)) {
return;
}
uint256 limit_ = collateral.limit();
uint256 supply_ = collateral.totalSupply();
if (supply_ >= limit_) {
return;
}
address this_ = address(this);
IERC20 asset_ = IERC20(asset());
uint256 assets = asset_.balanceOf(this_).min(limit_ - supply_);
if (assets == 0) {
return;
}
asset_.safeIncreaseAllowance(address(collateral), assets);
collateral.deposit(this_, assets);
emit DepositIntoCollateral(assets);
}
function _deposit(address caller, address receiver, uint256 assets, uint256 shares)
internal
virtual
override
{
address this_ = address(this);
IDepositStrategy.DepositData[] memory data =
depositStrategy().calculateDepositAmounts(this_, assets);
super._deposit(caller, receiver, assets, shares);
for (uint256 i = 0; i < data.length; i++) {
if (data[i].deposit != 0) {
_deposit(data[i].subvaultIndex, data[i].deposit);
assets -= data[i].deposit;
}
}
_depositIntoCollateral();
}
function _withdraw(
address caller,
address receiver,
address owner,
uint256 assets,
uint256 shares
) internal virtual override {
address this_ = address(this);
IWithdrawalStrategy.WithdrawalData[] memory data =
withdrawalStrategy().calculateWithdrawalAmounts(this_, assets);
if (caller != owner) {
_spendAllowance(owner, caller, shares);
}
_burn(owner, shares);
uint256 liquid = assets;
IWithdrawalStrategy.WithdrawalData memory d;
for (uint256 i = 0; i < data.length; i++) {
d = data[i];
_withdraw(d.subvaultIndex, d.staked, d.pending, d.claimable, owner, receiver);
liquid -= d.staked + d.pending + d.claimable;
}
if (liquid != 0) {
IERC20 asset_ = IERC20(asset());
uint256 balance = asset_.balanceOf(this_);
if (balance < liquid) {
if (balance != 0) {
asset_.safeTransfer(receiver, balance);
liquid -= balance;
}
defaultCollateral().withdraw(receiver, liquid);
} else
Submitted on: 2025-10-24 20:15:38
Comments
Log in to comment.
No comments yet.