/// @notice A struct describing the status of an underlying vault/// @dev Contains data about a vault's chain ID, share price, oracle, and morestructVaultData {/// @dev The ID of the chain where the vault is deployeduint64 chainId;/// @dev The superform ID of the vault in the Superform protocoluint256 superformId;/// @dev The oracle that provides the share price for the vault ISharePriceOracle oracle;/// @dev The number of decimals used in the ERC4626 sharesuint8 decimals;/// @dev The total assets invested in the vaultuint128 totalDebt;/// @dev The address of the vaultaddress vaultAddress;}
/// @notice Data structure for cross-chain requests
/// @param controller Address controlling the request
/// @param superformIds Array of Superform IDs involved in request
/// @param requestedAssetsPerVault Array of requested assets per vault
/// @param requestedAssets Total requested assets
/// @param receiverAddress Address to receive assets
struct RequestData {
address controller;
uint256[] superformIds;
uint256[] requestedAssetsPerVault;
uint256 requestedAssets;
address receiverAddress;
}
// Request tracking
mapping(address => ERC7540_Request) private _pendingDepositRequest;
mapping(address => ERC7540_FilledRequest) private _claimableDepositRequest;
mapping(address => ERC7540_Request) private _pendingRedeemRequest;
mapping(address => ERC7540_FilledRequest) private _claimableRedeemRequest;
// State tracking
mapping(bytes32 => RequestData) public requests;
mapping(address => uint256) public nonces;
mapping(address => uint256) public lastRedeem;
/// @dev Internal cache struct to allocate in memory
struct ProcessRedeemRequestCache {
// List of vauts to withdraw from on each chain
uint256[WITHDRAWAL_QUEUE_SIZE][N_CHAINS] dstVaults;
// List of shares to redeem on each vault in each chain
uint256[WITHDRAWAL_QUEUE_SIZE][N_CHAINS] sharesPerVault;
// List of assets to withdraw on each vault in each chain
uint256[WITHDRAWAL_QUEUE_SIZE][N_CHAINS] assetsPerVault;
// Cache length of list of each chain
uint256[N_CHAINS] lens;
// Assets to divest from other vaults
uint256 amountToWithdraw;
// Shares actually used
uint256 sharesFulfilled;
// Save assets that were withdrawn instantly
uint256 totalClaimableWithdraw;
// Cache totalAssets
uint256 totalAssets;
// Cache totalIdle
uint256 totalIdle;
// Cache totalDebt
uint256 totalDebt;
// Convert shares to assets at current price
uint256 assets;
// Wether is a single chain or multichain withdrawal
bool isSingleChain;
bool isMultiChain;
// Wether is a single or multivault withdrawal
bool isMultiVault;
}
/// @param shares to redeem and burn
/// @param controller controller that created the request
/// @param receiver address of the assets receiver in case its a
struct ProcessRedeemRequestConfig {
uint256 shares;
address controller;
SingleXChainSingleVaultWithdraw sXsV;
SingleXChainMultiVaultWithdraw sXmV;
MultiXChainSingleVaultWithdraw mXsV;
MultiXChainMultiVaultWithdraw mXmV;
}
/// @notice Transfers assets from sender into the Vault and submits a Request for asynchronous deposit.
/// @param assets the amount of deposit assets to transfer from owner
/// @param controller the controller of the request who will be able to operate the request
/// @param owner the source of the deposit assets
/// @return requestId
function requestDeposit(
uint256 assets,
address controller,
address owner
)
public
override
noEmergencyShutdown
returns (uint256 requestId)
{
requestId = super.requestDeposit(assets, controller, owner);
// fulfill the request directly
_fulfillDepositRequest(controller, assets, convertToShares(assets));
}
/// @notice Executes the redeem request for a controller
function _processRedeemRequest(ProcessRedeemRequestConfig memory config) private {
// Use struct to avoid stack too deep
ProcessRedeemRequestCache memory cache;
cache.totalIdle = _totalIdle;
cache.totalDebt = _totalDebt;
cache.assets = convertToAssets(config.shares);
cache.totalAssets = totalAssets();
// Cannot process more assets than the
if (cache.assets > cache.totalAssets - gateway.totalpendingXChainInvests()) {
revert InsufficientAvailableAssets();
}
// If totalIdle can covers the amount fulfill directly
if (cache.totalIdle >= cache.assets) {
cache.sharesFulfilled = config.shares;
cache.totalClaimableWithdraw = cache.assets;
}
// Otherwise perform Superform withdrawals
else {
// Cache amount to withdraw before reducing totalIdle
cache.amountToWithdraw = cache.assets - cache.totalIdle;
// Use totalIdle to fulfill the request
if (cache.totalIdle > 0) {
cache.totalClaimableWithdraw = cache.totalIdle;
cache.sharesFulfilled = _convertToShares(cache.totalIdle, cache.totalAssets);
}
///////////////////////////////// PREVIOUS CALCULATIONS ////////////////////////////////
_prepareWithdrawalRoute(cache);
//////////////////////////////// WITHDRAW FROM THIS CHAIN ////////////////////////////////
// Cache chain index
uint256 chainIndex = chainIndexes[THIS_CHAIN_ID];
if (cache.lens[chainIndex] > 0) {
if (cache.lens[chainIndex] == 1) {
// shares to redeem
uint256 sharesAmount = cache.sharesPerVault[chainIndex][0];
// assets to withdraw
uint256 assetsAmount = cache.assetsPerVault[chainIndex][0];
// superformId(take first element fo the array)
uint256 superformId = cache.dstVaults[chainIndex][0];
// get actual withdrawn amount
uint256 withdrawn = _liquidateSingleDirectSingleVault(
vaults[superformId].vaultAddress, sharesAmount, 0, address(this)
);
// cache shares to burn
cache.sharesFulfilled += _convertToShares(assetsAmount, cache.totalAssets);
// reduce vault debt
vaults[superformId].totalDebt = _sub0(vaults[superformId].totalDebt, assetsAmount).toUint128();
// cache instant total withdraw
cache.totalClaimableWithdraw += withdrawn;
// Increase idle funds
cache.totalIdle += withdrawn;
} else {
uint256 len = cache.lens[chainIndex];
// Prepare arguments for request using dynamic arrays
address[] memory vaultAddresses = new address[](len);
uint256[] memory amounts = new uint256[](len);
// Calculate requested amount
uint256 requestedAssets;
// Cast fixed arrays to dynamic ones
for (uint256 i = 0; i != len; i++) {
vaultAddresses[i] = vaults[cache.dstVaults[chainIndex][i]].vaultAddress;
amounts[i] = cache.sharesPerVault[chainIndex][i];
// Reduce vault debt individually
uint256 superformId = cache.dstVaults[chainIndex][i];
// Increase total assets requested
requestedAssets += cache.assetsPerVault[chainIndex][i];
// Reduce vault debt
vaults[superformId].totalDebt =
_sub0(vaults[superformId].totalDebt, cache.assetsPerVault[chainIndex][i]).toUint128();
}
// Withdraw from the vault synchronously
uint256 withdrawn = _liquidateSingleDirectMultiVault(
vaultAddresses, amounts, _getEmptyuintArray(amounts.length), address(this)
);
// Increase claimable assets and fulfilled shares by the amount withdran synchronously
cache.totalClaimableWithdraw += withdrawn;
cache.sharesFulfilled += _convertToShares(requestedAssets, cache.totalAssets);
// Increase total idle
cache.totalIdle += withdrawn;
}
}
//////////////////////////////// WITHDRAW FROM EXTERNAL CHAINS ////////////////////////////////
// If its not multichain
if (!cache.isMultiChain) {
// If its multivault
if (!cache.isMultiVault) {
uint256 superformId;
uint256 amount;
uint64 chainId;
uint256 chainIndex;
for (uint256 i = 0; i < N_CHAINS; ++i) {
if (DST_CHAINS[i] == THIS_CHAIN_ID) continue;
// The vaults list length should be 1(single-vault)
if (cache.lens[i] > 0) {
chainId = DST_CHAINS[i];
chainIndex = chainIndexes[chainId];
superformId = cache.dstVaults[i][0];
amount = cache.sharesPerVault[i][0];
// Withdraw from one vault asynchronously(crosschain)
_liquidateSingleXChainSingleVault(
chainId, superformId, amount, config.controller, config.sXsV, cache.assetsPerVault[i][0]
);
// reduce vault debt
vaults[superformId].totalDebt =
_sub0(vaults[superformId].totalDebt, cache.assetsPerVault[chainIndex][0]).toUint128();
break;
}
}
} else {
uint256[] memory superformIds;
uint256[] memory amounts;
uint64 chainId;
for (uint256 i = 0; i < N_CHAINS; ++i) {
if (DST_CHAINS[i] == THIS_CHAIN_ID) continue;
if (cache.lens[i] > 0) {
chainId = DST_CHAINS[i];
superformIds = _toDynamicUint256Array(cache.dstVaults[i], cache.lens[i]);
amounts = _toDynamicUint256Array(cache.sharesPerVault[i], cache.lens[i]);
uint256 totalDebtReduction;
// reduce vault debt
for (uint256 j = 0; j < superformIds.length;) {
vaults[superformIds[j]].totalDebt =
_sub0(vaults[superformIds[j]].totalDebt, cache.assetsPerVault[i][j]).toUint128();
totalDebtReduction += cache.assetsPerVault[i][j];
unchecked {
++j;
}
}
// Withdraw from multiple vaults asynchronously(crosschain)
_liquidateSingleXChainMultiVault(
chainId,
superformIds,
amounts,
config.controller,
config.sXmV,
totalDebtReduction,
_toDynamicUint256Array(cache.assetsPerVault[i], cache.lens[i])
);
break;
}
}
}
}
// If its multichain
else {
// If its single vault
if (!cache.isMultiVault) {
uint256 chainsLen;
for (uint256 i = 0; i < cache.lens.length; i++) {
if (cache.lens[i] > 0) chainsLen++;
}
uint8[][] memory ambIds = new uint8[][](chainsLen);
uint64[] memory dstChainIds = new uint64[](chainsLen);
SingleVaultSFData[] memory singleVaultDatas = new SingleVaultSFData[](chainsLen);
uint256 lastChainsIndex;
for (uint256 i = 0; i < N_CHAINS; i++) {
if (cache.lens[i] > 0) {
dstChainIds[lastChainsIndex] = DST_CHAINS[i];
++lastChainsIndex;
}
}
uint256[] memory totalDebtReductions = new uint256[](chainsLen);
for (uint256 i = 0; i < N_CHAINS; i++) {
totalDebtReductions[i] = cache.assetsPerVault[i][0];
singleVaultDatas[i] = SingleVaultSFData({
superformId: cache.dstVaults[i][0],
amount: cache.sharesPerVault[i][0],
outputAmount: config.mXsV.outputAmounts[i],
maxSlippage: config.mXsV.maxSlippages[i],
liqRequest: config.mXsV.liqRequests[i],
permit2data: "",
hasDstSwap: config.mXsV.hasDstSwaps[i],
retain4626: false,
receiverAddress: config.controller,
receiverAddressSP: address(0),
extraFormData: ""
});
ambIds[i] = config.mXsV.ambIds[i];
vaults[cache.dstVaults[i][0]].totalDebt =
_sub0(vaults[cache.dstVaults[i][0]].totalDebt, cache.assetsPerVault[i][0]).toUint128();
}
_liquidateMultiDstSingleVault(
ambIds, dstChainIds, singleVaultDatas, config.mXsV.value, totalDebtReductions
);
}
// If its multi-vault
else {
// Cache the number of chains we will withdraw from
uint256 chainsLen;
for (uint256 i = 0; i < cache.lens.length; i++) {
if (cache.lens[i] > 0) chainsLen++;
}
uint8[][] memory ambIds = new uint8[][](chainsLen);
// Cacche destination chains
uint64[] memory dstChainIds = new uint64[](chainsLen);
// Cache multivault calls for each chain
MultiVaultSFData[] memory multiVaultDatas = new MultiVaultSFData[](chainsLen);
uint256 lastChainsIndex;
for (uint256 i = 0; i < N_CHAINS; i++) {
if (cache.lens[i] > 0) {
dstChainIds[lastChainsIndex] = DST_CHAINS[i];
++lastChainsIndex;
}
}
uint256[] memory totalDebtReductions = new uint256[](chainsLen);
uint256[][] memory debtReductionsPerVault = new uint256[][](chainsLen);
for (uint256 i = 0; i < N_CHAINS; i++) {
bool[] memory emptyBoolArray = _getEmptyBoolArray(cache.lens[i]);
uint256[] memory superformIds = _toDynamicUint256Array(cache.dstVaults[i], cache.lens[i]);
multiVaultDatas[i] = MultiVaultSFData({
superformIds: superformIds,
amounts: _toDynamicUint256Array(cache.sharesPerVault[i], cache.lens[i]),
outputAmounts: config.mXmV.outputAmounts[i],
maxSlippages: config.mXmV.maxSlippages[i],
liqRequests: config.mXmV.liqRequests[i],
permit2data: "",
hasDstSwaps: config.mXmV.hasDstSwaps[i],
retain4626s: emptyBoolArray,
receiverAddress: config.controller,
receiverAddressSP: address(0),
extraFormData: ""
});
ambIds[i] = config.mXmV.ambIds[i];
debtReductionsPerVault[i] = _toDynamicUint256Array(cache.sharesPerVault[i], cache.lens[i]);
for (uint256 j = 0; j < superformIds.length;) {
vaults[superformIds[j]].totalDebt =
_sub0(vaults[superformIds[j]].totalDebt, cache.assetsPerVault[i][j]).toUint128();
totalDebtReductions[i] += cache.assetsPerVault[i][j];
unchecked {
++j;
}
}
}
// Withdraw from multiple vaults and chains asynchronously
_liquidateMultiDstMultiVault(
ambIds,
dstChainIds,
multiVaultDatas,
config.mXmV.value,
totalDebtReductions,
debtReductionsPerVault
);
}
}
}
// Optimistically deduct all assets to withdraw from the total
_totalIdle = cache.totalIdle.toUint128();
_totalIdle -= cache.totalClaimableWithdraw.toUint128();
_totalDebt = cache.totalDebt.toUint128();
emit ProcessRedeemRequest(config.controller, config.shares);
// Burn all shares from this contract(they already have been transferred)
_burn(address(this), config.shares);
// Fulfill request with instant withdrawals only
_fulfillRedeemRequest(cache.sharesFulfilled, cache.totalClaimableWithdraw, config.controller);
}
/// @dev Hook that is called when processing a redeem request and make it claimable.
/// @dev It assumes user transferred its shares to the contract when requesting a redeem
function _fulfillRedeemRequest(
uint256 sharesFulfilled,
uint256 assetsWithdrawn,
address controller
)
internal
virtual
{
_pendingRedeemRequest[controller] = _pendingRedeemRequest[controller].sub(sharesFulfilled);
_claimableRedeemRequest[controller].assets += assetsWithdrawn;
_claimableRedeemRequest[controller].shares += sharesFulfilled;
}
// Pending investment tracking
mapping(uint256 => uint256) public pendingXChainInvests;
uint256 public totalpendingXChainInvests;
// Receiver contract tracking
mapping(bytes32 => address) public receivers;
// Queue tracking
EnumerableSetLib.Bytes32Set internal _requestsQueue;
uint256 public totalPendingXChainDivests;
// Lock states
mapping(address => bool) public redeemLocked;
mapping(address => uint256) internal _depositLockCheckPoint;
function investSingleDirectSingleVault(
address vaultAddress,
uint256 assets,
uint256 minSharesOut
)
public
onlyRoles(MANAGER_ROLE)
returns (uint256 shares)
{
// Ensure the target vault is in the approved list
if (!isVaultListed(vaultAddress)) revert VaultNotListed();
// Record the balance before deposit to calculate received shares
uint256 balanceBefore = vaultAddress.balanceOf(address(this));
// Deposit assets into the target vault
ERC4626(vaultAddress).deposit(assets, address(this));
// Calculate the number of shares received
shares = vaultAddress.balanceOf(address(this)) - balanceBefore;
// Ensure the received shares meet the minimum expected assets
if (shares < minSharesOut) {
revert InsufficientAssets();
}
// Update the vault's internal accounting
uint128 amountUint128 = assets.toUint128();
_totalIdle -= amountUint128;
_totalDebt += amountUint128;
vaults[_vaultToSuperformId[vaultAddress]].totalDebt += amountUint128;
emit Invest(assets);
return shares;
}