# State Management & Operations

## State Management & Operations

### Core State Structures

#### Vault State Structure

```solidity
/// @notice A struct describing the status of an underlying vault
/// @dev Contains data about a vault's chain ID, share price, oracle, and more
struct VaultData {
    /// @dev The ID of the chain where the vault is deployed
    uint64 chainId;
    /// @dev The superform ID of the vault in the Superform protocol
    uint256 superformId;
    /// @dev The oracle that provides the share price for the vault
    ISharePriceOracle oracle;
    /// @dev The number of decimals used in the ERC4626 shares
    uint8 decimals;
    /// @dev The total assets invested in the vault
    uint128 totalDebt;
    /// @dev The address of the vault
    address vaultAddress;
}
```

#### Request States

```solidity
/// @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;
```

#### Withdrawal Queue State

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

### Request State Lifecycle

#### Request Configuration

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

<figure><img src="/files/HdhSEzymXjjNlZJdeLao" alt=""><figcaption></figcaption></figure>

#### Initial State → PendingRequest

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

#### PendingRequest → ClaimableRequest

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

#### ClaimableRequest → Completed

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

### Cross-Chain State Management

<figure><img src="/files/eTNpb4YMWYbIpv1iJpnN" alt=""><figcaption></figcaption></figure>

#### Investment State Tracking

```solidity
// Pending investment tracking
mapping(uint256 => uint256) public pendingXChainInvests;
uint256 public totalpendingXChainInvests;

// Receiver contract tracking  
mapping(bytes32 => address) public receivers;
```

#### Withdrawal State Tracking

```solidity
// Queue tracking
EnumerableSetLib.Bytes32Set internal _requestsQueue;
uint256 public totalPendingXChainDivests;

// Lock states
mapping(address => bool) public redeemLocked;
mapping(address => uint256) internal _depositLockCheckPoint;
```

### Direct Investment Flow

<figure><img src="/files/i6YtldDVCO59StYeqEIa" alt=""><figcaption></figcaption></figure>

#### State Updates

<pre class="language-solidity"><code class="lang-solidity"><strong>    function investSingleDirectSingleVault(
</strong>        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 &#x3C; 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;
    }
</code></pre>

### Security Considerations

#### State Locks

```solidity
    // Reentrancy protection
    modifier nonReentrant() {
        require(_locked != 2, "ReentrancyGuard: reentrant call");
        _locked = 2;
        _;
        _locked = 1;
    }
    
    // Emergency shutdown
    modifier noEmergencyShutdown() {
        if (emergencyShutdown) {
            revert VaultShutdown();
        }
        _;
    }
```

#### State Validations

```solidity
    /// @dev Reverts if deposited shares are locked
    /// @param controller shares controller
    function _checkSharesLocked(address controller) private view {
        if (block.timestamp < _depositLockCheckPoint[controller] + sharesLockTime) revert SharesLocked();
    }
```

#### Critical Invariants

1. Total Assets = Idle + Debt + Pending Investments
2. Share Price Watermark ≤ Current Share Price
3. Request Queue Ordering Must Be Maintained
4. Receiver Contracts Must Be Unique Per Request

#### State Recovery

```solidity
    /// @notice Settles a cross-chain liquidation by processing received assets
    /// @dev Pulls assets from the receiver contract and fulfills the settlement in the vault.
    /// Only callable by addresses with RELAYER_ROLE. The key for lookup is generated based on
    /// whether it's a single vault (superformId) or multiple vaults (array of superformIds).
    /// @param key identifier of the receiver contract
    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);
    }

    function notifyRefund(uint256 superformId, uint256 value) external {
        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];
        if (req.controller == address(vault)) {
            totalPendingXChainDivests -= vaultRequestedAssets;
        }
        requests[key].requestedAssets -= vaultRequestedAssets;
        ERC20Receiver(msg.sender).setMinExpectedBalance(_sub0(currentExpectedBalance, vaultRequestedAssets));
        superPositions.safeTransferFrom(msg.sender, address(this), superformId, value, "");
        superPositions.safeTransferFrom(
            address(this), address(vault), superformId, value, abi.encode(vaultRequestedAssets)
        );
    }
```


---

# 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/state-management-and-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.
