# Core Operations

### Error Types

```solidity
error InsufficientAssets();
error VaultNotListed();
error RequestNotFound();
error InvalidController();
error MinimumBalanceNotMet();
error InvalidSuperformId();
error InvalidRecoveryAddress();
error InvalidAmount();
error TotalAmountMismatch();
error SharesLocked();
error VaultShutdown();
error Unauthorized();
error ExceedsMaxDeposit();
error ExceedsMaxMint();
```

### Events

```solidity
event Invest(uint256 amount);
event Divest(uint256 amount);
event SettleXChainInvest(uint256 indexed superformId, uint256 assets);
event SettleXChainDivest(uint256 assets);
event ProcessRedeemRequest(address indexed controller, uint256 shares);
event FulfillRedeemRequest(address indexed controller, uint256 shares, uint256 assets);
event RequestSettled(bytes32 indexed key, address indexed controller, uint256 settledAmount);
```

### Constants

```solidity
uint256 public constant WITHDRAWAL_QUEUE_SIZE = 30;
uint256 public constant SECS_PER_YEAR = 31_556_952;
uint256 public constant MAX_BPS = 10_000;
```

### Deposit Operations

#### Atomic Deposit Flow

```solidity
// Step 1: Request Deposit
requestId = vault.requestDeposit(
    1000e6,     // Amount
    msg.sender, // Controller
    msg.sender  // Owner
);

// Step 2: Execute Deposit (same transaction)
shares = vault.deposit(
    1000e6,     // Amount
    recipient,  // Share recipient
    msg.sender  // Controller
);
```

```solidity
// Deposit validation
function _deposit(
    uint256 assets,
    uint256 shares,
    address receiver,
    address controller
)
    internal
    override
    returns (uint256 assetsReturn, uint256 sharesReturn)
{
    _updatePosition(controller, shares);
    if (lastRedeem[controller] == 0) lastRedeem[controller] = block.timestamp;
    return super._deposit(assets, shares, receiver, controller);
}

function _afterDeposit(uint256 assets, uint256 /*uint shares*/ ) internal override {
    uint128 assetsUint128 = assets.toUint128();
    _totalIdle += assetsUint128;
}
```

Key Features:

* Atomic execution via `multicall`
* Immediate request fulfillment
* Direct share minting
* No asynchronous operations

Security Considerations:

* Emergency shutdown check
* Share lock enforcement
* Balance verification

#### Alternative Deposit Methods

```solidity
// Direct minting flow
function mint(
    uint256 shares,
    address to,
    address controller
) public override noEmergencyShutdown returns (uint256 assets) {
    uint256 sharesBalance = balanceOf(to);
    assets = super.mint(shares, to, controller);
    _lockShares(to, sharesBalance, shares);
    _afterDeposit(assets, shares);
}

// Share locking mechanism
function _lockShares(
    address to, 
    uint256 sharesBalance, 
    uint256 newShares
) private {
    uint256 newBalance = sharesBalance + newShares;
    if (sharesBalance == 0) {
        _depositLockCheckPoint[to] = block.timestamp;
    } else {
        _depositLockCheckPoint[to] = 
            ((_depositLockCheckPoint[to] * sharesBalance) / newBalance) +
            ((block.timestamp * newShares) / newBalance);
    }
}
```

### Investment Operations

#### Direct Investment (Same Chain)

```solidity
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;
}
```

#### Cross-Chain Investment

```solidity
function investSingleXChainSingleVault(SingleXChainSingleVaultStateReq calldata req)
    external
    payable
    onlyRoles(MANAGER_ROLE)
{
        gateway.investSingleXChainSingleVault{ value: msg.value }(req);

        // Update the vault's internal accounting
        uint256 amount = req.superformData.amount;
        uint128 amountUint128 = amount.toUint128();
        _totalIdle -= amountUint128;

        emit Invest(amount);
}
```

#### Multi-Vault Investment

```solidity
function investSingleDirectMultiVault(
    address[] calldata vaultAddresses,
    uint256[] calldata assets,
    uint256[] calldata minSharesOuts
) external returns (uint256[] memory shares) {
    shares = new uint256[](vaultAddresses.length);
    for (uint256 i = 0; i < vaultAddresses.length; ++i) {
        shares[i] = investSingleDirectSingleVault(
            vaultAddresses[i], 
            assets[i], 
            minSharesOuts[i]
        );
    }
}
```

#### Gas Management

```solidity
modifier refundGas() {
        uint256 balanceBefore;
        assembly {
            balanceBefore := sub(selfbalance(), callvalue())
        }
        _;
        assembly {
            let balanceAfter := selfbalance()
            switch lt(balanceAfter, balanceBefore)
            case true {
                mstore(0x00, 0x1c26714c) // `InsufficientGas()`.
                revert(0x1c, 0x04)
            }
            case false {
                // Transfer all the ETH to sender and check if it succeeded or not.
                if iszero(call(gas(), origin(), balanceAfter, codesize(), 0x00, codesize(), 0x00)) {
                    mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
                    revert(0x1c, 0x04)
                }
            }
        }
    }
```

### Withdrawal Operations

#### Request Phase

```solidity
function requestRedeem(
        uint256 shares,
        address controller,
        address owner
    )
        public
        override
        noEmergencyShutdown
        returns (uint256 requestId)
    {
        // Require deposited shares arent locked
        _checkSharesLocked(controller);
        requestId = super.requestRedeem(shares, controller, owner);
    }
```

#### Processing Phase

```solidity
function processRedeemRequest(ProcessRedeemRequestParams calldata params)
    external
    payable
    onlyRoles(RELAYER_ROLE)
    nonReentrant
{
    // Retrieve the pending redeem request for the specified controller
    // This request may involve cross-chain withdrawals from various ERC4626 vaults

    // Process the redemption request asynchronously
    // Parameters:
    // 1. pendingRedeemRequest(controller): Fetches the pending shares
    // 2. controller: The address initiating the redemption (used as both 'from' and 'to')
    _processRedeemRequest(
        ProcessRedeemRequestConfig(
            params.shares == 0 ? pendingRedeemRequest(params.controller) : params.shares,
            params.controller,
            params.sXsV,
            params.sXmV,
            params.mXsV,
            params.mXmV
        )
    );
    // Note: After processing, the redeemed assets are held by this contract
    // The user can later claim these assets using `redeem` or `withdraw`
}
```

```solidity
// Withdrawal Queue Processing
function _exhaustWithdrawalQueue(
        ProcessRedeemRequestCache memory cache,
        uint256[WITHDRAWAL_QUEUE_SIZE] memory queue,
        bool resetValues
    )
        private
        view
    {
        // Cache how many chains we need and how many vaults in each chain
        for (uint256 i = 0; i != WITHDRAWAL_QUEUE_SIZE; i++) {
            // If we exhausted the queue stop
            if (queue[i] == 0) {
                if (resetValues) {
                    // reset values
                    cache.amountToWithdraw = cache.assets - cache.totalIdle;
                }
                break;
            }
            if (resetValues) {
                // If its fulfilled stop
                if (cache.amountToWithdraw == 0) {
                    break;
                }
            }
            // Cache next vault from the withdrawal queue
            VaultData memory vault = vaults[queue[i]];
            // Calcualate the maxWithdraw of the vault
            uint256 maxWithdraw = vault.convertToAssets(_sharesBalance(vault), true);

            // Dont withdraw more than max
            uint256 withdrawAssets = Math.min(maxWithdraw, cache.amountToWithdraw);
            if (withdrawAssets == 0) continue;
            // Cache chain index
            uint256 chainIndex = chainIndexes[vault.chainId];
            // Cache chain length
            uint256 len = cache.lens[chainIndex];
            // Push the superformId to the last index of the array
            cache.dstVaults[chainIndex][len] = vault.superformId;

            uint256 shares;
            if (cache.amountToWithdraw >= maxWithdraw) {
                uint256 balance = _sharesBalance(vault);
                shares = balance;
            } else {
                shares = vault.convertToShares(withdrawAssets, true);
            }

            if (shares == 0) continue;
            // Push the shares to redeeem of that vault
            cache.sharesPerVault[chainIndex][len] = shares;
            // Push the assetse to withdraw of that vault
            cache.assetsPerVault[chainIndex][len] = withdrawAssets;
            // Reduce the total debt by no more than the debt of this vault
            uint256 debtReduction = Math.min(vault.totalDebt, withdrawAssets);
            // Reduce totalDebt
            cache.totalDebt -= debtReduction;
            // Reduce needed assets
            cache.amountToWithdraw -= withdrawAssets;

            // Cache wether is single chain or multichain
            if (vault.chainId != THIS_CHAIN_ID) {
                if (!cache.isSingleChain && !cache.isMultiChain) {
                    // First external chain encountered
                    cache.isSingleChain = true;
                } else if (cache.isSingleChain) {
                    // Find the first external chain ID
                    uint256 firstChainId;
                    for (uint256 j = 0; j < N_CHAINS; j++) {
                        if (cache.lens[j] > 0 && j != chainIndexes[THIS_CHAIN_ID]) {
                            firstChainId = j;
                            break;
                        }
                    }
                    // If this vault is from a different chain than the first one, it's multi-chain
                    if (chainIndex != firstChainId) {
                        cache.isSingleChain = false;
                        cache.isMultiChain = true;
                    }
                }
            
                // Check if there are multiple vaults in this chain
                if (cache.lens[chainIndex] >= 1) {
                    cache.isMultiVault = true;
                }
            }

            // Increase index for iteration
            unchecked {
                cache.lens[chainIndex]++;
            }
        }
    }
```

#### Settlement Phase

```solidity
function settleLiquidation(bytes32 key, bool force) external onlyRoles(RELAYER_ROLE) {
    if (!_requestsQueue.contains(key)) revert RequestNotFound();

    RequestData memory data = requests[key];
    if (data.controller == address(0)) revert InvalidController();

    ERC20Receiver receiverContract = ERC20Receiver(getReceiver(key));
    uint256 settledAssets = receiverContract.balance();

    _requestsQueue.remove(key);

    if (!force) {
        if (receiverContract.balance() < receiverContract.minExpectedBalance()) {
            revert MinimumBalanceNotMet();
        }
    }

    receiverContract.pull(settledAssets);
    asset.safeTransfer(address(vault), settledAssets);
    vault.fulfillSettledRequest(data.controller, data.requestedAssets, settledAssets);
    emit RequestSettled(key, data.controller, settledAssets);
}
```

#### Balance Verification

```solidity
function previewWithdrawalRoute(
    address controller,
    uint256 shares,
    bool despiseDust
)
    public
    view
    returns (ProcessRedeemRequestCache memory cachedRoute)
{
    if (shares == 0) {
        shares = pendingRedeemRequest(controller);
    }

    cachedRoute.shares = shares;
    cachedRoute.assets = convertToAssets(shares);
    cachedRoute.totalIdle = _totalIdle;
    cachedRoute.totalDebt = _totalDebt;
    cachedRoute.totalAssets = totalAssets();

    // Cannot process more assets than the available
    if (cachedRoute.assets > totalWithdrawableAssets()) {
        revert InsufficientAvailableAssets();
    }

    // If totalIdle can covers the amount fulfill directly
    if (cachedRoute.totalIdle >= cachedRoute.assets) {
        cachedRoute.sharesFulfilled = shares;
        cachedRoute.totalClaimableWithdraw = cachedRoute.assets;
    }
    // Otherwise perform Superform withdrawals
    else {
        // Cache amount to withdraw before reducing totalIdle
        cachedRoute.amountToWithdraw = cachedRoute.assets - cachedRoute.totalIdle;
        // Use totalIdle to fulfill the request
        if (cachedRoute.totalIdle > 0) {
            cachedRoute.totalClaimableWithdraw = cachedRoute.totalIdle;
            cachedRoute.sharesFulfilled = _convertToShares(cachedRoute.totalIdle, cachedRoute.totalAssets);
        }
        ///////////////////////////////// PREVIOUS CALCULATIONS ////////////////////////////////
        _prepareWithdrawalRoute(cachedRoute, despiseDust);
    }
    return cachedRoute;
}
```

### Failure Mode Analysis

1. **Bridge Failures**
   * Timeout scenarios
   * Asset recovery process
   * State reconciliation
2. **Oracle Failures**
   * Price feed delays
   * Stale price handling
   * Fallback mechanisms
3. **Gas-Related Failures**
   * Cross-chain operation out of gas
   * Batch operation partial success
   * Recovery procedures

### Cross-Chain Settlement

#### Investment Settlement

```solidity
function settleXChainInvest(uint256 superformId, uint256 bridgedAssets) public {
        if (msg.sender != address(gateway)) revert Unauthorized();
        _totalDebt += bridgedAssets.toUint128();
        vaults[superformId].totalDebt += bridgedAssets.toUint128();
        emit SettleXChainInvest(superformId, bridgedAssets);
    }
```

#### Divestment Settlement

```solidity
function settleDivest(bytes32 key, bool force) external onlyRoles(RELAYER_ROLE) {
        if (!_requestsQueue.contains(key)) revert();
        RequestData memory data = requests[key];
        _requestsQueue.remove(key);
        ERC20Receiver receiverContract = ERC20Receiver(getReceiver(key));
        if (data.controller != address(vault)) revert();
        if (!force) {
            if (receiverContract.balance() < receiverContract.minExpectedBalance()) revert();
        }
        uint256 settledAssets = receiverContract.balance();
        uint256 requestedAssets = data.requestedAssets;
        receiverContract.pull(settledAssets);
        totalPendingXChainDivests -= settledAssets;
        asset.safeTransfer(address(vault), settledAssets);
        vault.settleXChainDivest(requestedAssets);
    }
```

#### Refund Handling

```solidity
function notifyRefund(uint256 superformId, uint256 value) external {
        // Prevent bugs from superform
        if(value == 0) return;
        bytes32 key = ERC20Receiver(msg.sender).key();
        if (requests[key].receiverAddress != msg.sender) revert();
        RequestData memory req = requests[key];
        uint256 currentExpectedBalance = ERC20Receiver(msg.sender).minExpectedBalance();

        uint256 vaultIndex;
        for (uint256 i = 0; i < req.superformIds.length; ++i) {
            if (req.superformIds[i] == superformId) {
                vaultIndex = i;
                break;
            }
        }
        uint256 vaultRequestedAssets = req.requestedAssetsPerVault[vaultIndex];

        _handleRefund(key, superformId, value, vaultRequestedAssets);

        _requestsQueue.remove(key); // We can only remove the request if it's a single vault otherwise we need to
            // confirm both succeeded
        ERC20Receiver(msg.sender).setMinExpectedBalance(_sub0(currentExpectedBalance, vaultRequestedAssets));
    }
```

#### NFT Position Safety

```solidity
function onERC1155Received(
        address operator,
        address from,
        uint256 superformId,
        uint256 value,
        bytes memory data
    )
        public
        returns (bytes4)
    {
        // Silence compiler warnings
        operator;
        value;
        data;
        if (msg.sender != address(_superPositions)) revert();
        if (from != address(0)) revert();
        ISuperformGateway(_deployer).notifyRefund(superformId, value);
        return this.onERC1155Received.selector;
    }
    
function onERC1155BatchReceived(
        address operator,
        address from,
        uint256[] memory superformIds,
        uint256[] memory values,
        bytes memory data
    )
        public
        returns (bytes4)
    {
        // Silence compiler warnings
        operator;
        from;
        values;
        data;
        if (msg.sender != address(_superPositions)) revert();
        if (from != address(0)) revert();
        ISuperformGateway(_deployer).notifyBatchRefund(superformIds, values);
        return this.onERC1155BatchReceived.selector;
    }
```

### Security Considerations

#### Critical Invariants

1. Total assets = idle assets + allocated assets
2. Pending requests must be processed in order
3. Settlement amounts must meet minimum requirements

#### Fee Management

```solidity
function chargeGlobalFees() external updateGlobalWatermark onlyRoles(MANAGER_ROLE) returns (uint256) {
        uint256 currentSharePrice = sharePrice();
        uint256 lastSharePrice = sharePriceWaterMark;
        uint256 duration = block.timestamp - lastFeesCharged;
        uint256 currentTotalAssets = totalAssets();
        uint256 lastTotalAssets = totalSupply().fullMulDiv(lastSharePrice, 10 ** decimals());

        // Calculate time-based fees (management & oracle)
        // These are charged on total assets, prorated for the time period
        uint256 managementFees = (currentTotalAssets * duration).fullMulDiv(managementFee, SECS_PER_YEAR) / MAX_BPS;
        uint256 oracleFees = (currentTotalAssets * duration).fullMulDiv(oracleFee, SECS_PER_YEAR) / MAX_BPS;
        uint256 totalFees = managementFees + oracleFees;
        uint256 performanceFees;

        currentTotalAssets += managementFees + oracleFees;

        lastFeesCharged = block.timestamp;

        // Calculate the asset's value change since entry
        // This gives us the raw profit/loss in asset terms
        int256 assetsDelta = int256(currentTotalAssets) - int256(lastTotalAssets);

        // Only calculate fees if there's a profit
        if (assetsDelta > 0) {
            uint256 excessReturn;

            // Calculate returns relative to hurdle rate
            uint256 hurdleReturn = (lastTotalAssets * hurdleRate()).fullMulDiv(duration, SECS_PER_YEAR) / MAX_BPS;
            uint256 totalReturn = uint256(assetsDelta);

            // Only charge performance fees if:
            // 1. Current share price is not below
            // 2. Returns exceed hurdle rate
            if (currentSharePrice > sharePriceWaterMark && totalReturn > hurdleReturn) {
                // Only charge performance fees on returns above hurdle rate
                excessReturn = totalReturn - hurdleReturn;

                performanceFees = excessReturn * performanceFee / MAX_BPS;
            }

            // Calculate total fees
            totalFees += performanceFees;
        }
        // Transfer fees to treasury if any were charged
        if (totalFees > 0) {
            _mint(treasury, convertToShares(totalFees));
            _afterDeposit(totalFees, 0);
        }
        assembly {
            let m := shr(96, not(0))

            // Emit the {AssessFees} event
            mstore(0x00, managementFees)
            mstore(0x20, performanceFees)
            mstore(0x40, oracleFees)
            log2(0x00, 0x60, 0xa443e1db11cb46c65620e8e21d4830a6b9b444fa4c350f0dd0024b8a5a6b6ef5, and(m, address()))
        }
        return totalFees;
    }
```

#### Access Control

```solidity
modifier onlyRoles(uint256 role) {
    if (!hasRole(role, msg.sender)) revert Unauthorized();
    _;
}
```

#### State Protection

```solidity
modifier nonReentrant() {
    require(_locked != 2, "ReentrancyGuard: reentrant call");
    _locked = 2;
    _;
    _locked = 1;
}
```

#### Emergency Controls

```solidity
modifier noEmergencyShutdown() {
    if (emergencyShutdown) {
        revert VaultShutdown();
    }
    _;
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://devs.maxapy.io/maxapy-erc7540/metavault/core-operations.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
