# Operate an Allocation Strategy \[Configure queues, move and dispatch capital, rebalance, and unstick operations]

:::info
In Railnet smart contracts, an Allocation Strategy is implemented as a **MultiVehicle**.
:::

You need a deployed strategy with at least one authorized yield source ([Create an Allocation Strategy](/strategies/allocation/create)) and the operational roles on your EAC ([Guardrails and mandates](/strategies/allocation/guardrails)). If a platform deployed the strategy and invited you to run it, the source list, the caps and the fees are theirs; everything here is yours.

The MultiVehicle exposes only its manager. Reach every engine through it:

```solidity [Solidity]
IVehicleManager manager = MultiVehicle(multiVehicle).manager();

ISectorAccountingEngine accounting = manager.accountingEngine();
IQueueStrategyEngine strategy = manager.queueStrategyEngine();
ISubQueryEngine subQueryEngine = manager.subQueryEngine();
IQueryRedeemQueue redeemQueue = manager.redeemQueue();
```

## Sectors

Every asset and share sits in a sector. `AVAILABLE` holds idle base assets and is what `withdrawable()` reports. `RESERVED` holds base assets you park aside: it counts toward `totalAssets()` but neither auto-fulfill nor the queue engine can consume it, so getting liquidity in or out of it always takes an explicit `move`. Each authorized source has its own staging sector, `SectorLib.toSector(vehicle)`, and `ALLOCATION` holds the source shares from settled deposits. `ENTRY` and `EXIT` are virtual and untracked. See [Sector accounting](/developers/contracts/accounting) for the full model.

The operating loop is **move** base assets from `AVAILABLE` into a source's sector, then **dispatch** to execute the deposit.

## Configure allocation queues

The Queue Strategy Engine distributes capital across authorized sources in queue order. In the deposit queue `target.value` is a **ceiling**: each entry is filled up to its target before the queue moves on, and `type(uint256).max` means no limit. In the redeem queue it is a **floor**: each entry is drained down to its target, and `0` means it can be emptied. `target.threshold` is a tolerance band that skips an entry already within range, so small top-ups do not burn gas.

Queues do not enforce ongoing ratios. If yields diverge, allocations drift. A common pattern is to set the redeem queue in reverse order of the deposit queue, so the last source filled is the first drained.

**Requires:** `MULTI_VEHICLE_SET_QUEUES` scoped to the Queue Strategy Engine.

```solidity [Solidity]
// Deposit: Aave up to 80,000 shares, then Morpho without limit
IQueueStrategyEngine.QueueEntry[] memory depositQueue = new IQueueStrategyEngine.QueueEntry[](2);
depositQueue[0] = IQueueStrategyEngine.QueueEntry({
    vehicle: IVehicle(aaveVehicle),
    target: Target({value: 80_000e18, threshold: 0})
});
depositQueue[1] = IQueueStrategyEngine.QueueEntry({
    vehicle: IVehicle(morphoVehicle),
    target: Target({value: type(uint256).max, threshold: 0})
});

// Redeem: drain Morpho first, then Aave
IQueueStrategyEngine.QueueEntry[] memory redeemQueue = new IQueueStrategyEngine.QueueEntry[](2);
redeemQueue[0] = IQueueStrategyEngine.QueueEntry({
    vehicle: IVehicle(morphoVehicle),
    target: Target({value: 0, threshold: 0})
});
redeemQueue[1] = IQueueStrategyEngine.QueueEntry({
    vehicle: IVehicle(aaveVehicle),
    target: Target({value: 0, threshold: 0})
});

// Both queues are replaced atomically; every vehicle must still be authorized and ready
strategy.setQueues(depositQueue, redeemQueue);
```

Call `setQueues` again at any time to change the allocation plan.

## Read the current state

```solidity [Solidity]
uint256 totalAssets = accounting.totalAssets();
uint256 withdrawable = accounting.withdrawable();   // AVAILABLE only

uint256 idleUsdc = accounting.getSectorBalance(SectorLib.AVAILABLE, IERC20(usdc));
uint256 stagedUsdc = accounting.getSectorBalance(SectorLib.toSector(IVehicle(aaveVehicle)), IERC20(usdc));

(
    uint256 sharesAfterUnlocks,         // settled shares in ALLOCATION
    uint256 sharesBeforeCreates,        // shares staged for new redeem queries
    uint256 expectedSharesAfterUnlocks, // ephemeral shares from in-flight deposits
    uint256 expectedAssetsAfterUnlocks, // projected assets once in-flight redeems unlock
    uint256 assetsBeforeCreates         // assets staged for new deposit queries
) = accounting.vehicleHoldings(IVehicle(aaveVehicle));

// Headroom before the vehicle's cap or its own limits bind
uint256 depositable = accounting.maxDepositable(IVehicle(aaveVehicle));
uint256 redeemable = accounting.maxRedeemable(IVehicle(aaveVehicle));
```

## Move and dispatch

`move` stages assets; it does not deposit them. **Requires:** `MULTI_VEHICLE_MOVE` scoped to the Sector Accounting Engine.

```solidity [Solidity]
accounting.move(
    ISectorAccountingEngine.MoveParams({
        from: SectorLib.AVAILABLE,
        to: SectorLib.toSector(IVehicle(targetVehicle)),
        asset: usdc,
        amount: 10_000e6,
        operationId: keccak256("top-up-aave-2026-08")
    })
);
```

`dispatch` creates the deposit or redeem query on the source. **Requires:** `MULTI_VEHICLE_DISPATCH` scoped to the Sector Accounting Engine.

```solidity [Solidity]
(Query memory query, State dispatchState) = accounting.dispatch(
    ISectorAccountingEngine.DispatchParams({
        vehicle: IVehicle(targetVehicle),
        mode: Mode.DEPOSIT,
        amount: 10_000e6,
        settledDestination: SectorLib.ALLOCATION,   // shares land here on success
        rejectedDestination: SectorLib.AVAILABLE,   // assets return here on failure
        minOutput: 9_950e18,                        // slippage floor; 0 accepts any output
        data: "",
        operationId: keccak256("top-up-aave-2026-08")
    })
);
```

`dispatchState` is the state the sub-query reached in this call: `SETTLED` for a sync source, `PROCESSING` for an async one. `operationId` is a free-form tag echoed into the event, so reuse one value across the steps of a multi-step operation.

:::warning
`amount: type(uint256).max` dispatches the whole sector balance and auto-limits to the vehicle's cap instead of reverting, but it requires `minOutput: 0`. A slippage bound may only bind to an explicit amount, never to the execution-time sector balance.
:::

## Rebalance

There is no `rebalance` call. Compose one from the primitives, reusing a single `operationId` so the steps group off-chain: dispatch a `REDEEM` from the source vehicle with `settledDestination: SectorLib.AVAILABLE`, `move` the freed assets into the destination vehicle's sector with `amount: type(uint256).max`, then `dispatch` a `DEPOSIT` into it with `minOutput: 0`.

If the source redeems asynchronously, step one returns `PROCESSING` and the assets are not in `AVAILABLE` yet. Wait for the sub-query to settle before running the other two.

## Keepers

Keepers handle the repeated calls: feeding the redemption queue, retrieving settled assets from it, redeeming fee shares, progressing async sub-queries. Allocation decisions stay manual. See [Sync vs async](/developers/contracts/sync-vs-async) for how keepers work, and query `Job` on the [API](/developers/api) to confirm automation is running.

## When something is stuck

:::details[Redemptions are not progressing]
Call `manager.feedQueryRedeemQueue()` if withdrawable assets exist (`MULTI_VEHICLE_FEED_QUERY_REDEEM_QUEUE` on the Vehicle Manager), or `subQueryEngine.progressQuery(subQuery, query)` for a sub-query stuck in a non-terminal state (`MULTI_VEHICLE_PROGRESS_QUERY` on the Sub Query Engine). If assets are sitting in a source's sector with no active query, dispatch them with `amount: type(uint256).max` and `minOutput: 0`.
:::

:::details[A deposit is stuck in PROCESSING]
The source needs manual progression, which is normal for async protocols like Ethena. Call `subQueryEngine.progressQuery(subQuery, query)`.
:::

:::details[dispatch reverts]
Check in order: the source's sector has a non-zero balance (`getSectorBalance`); the source is still authorized (`manager.isAuthorized(vehicle)`); you hold `MULTI_VEHICLE_DISPATCH` scoped to the accounting engine; `minOutput` is `0` when `amount` is the max sentinel; `maxDepositable(vehicle)` is non-zero, which it is not when the vehicle is at its cap or paused; the underlying protocol is not rejecting `create`.
:::

:::details[Capital is not allocating]
Deposit targets are ceilings, not ratios. When every source has reached its ceiling, new deposits stay idle in `AVAILABLE`. Redeem targets are floors, so a source is never drained below its target.
:::

## Next steps

<Cards>
  <Card title="Risk management" description="Caps, emergency controls, and the audit trail." icon="shield-check" to="/strategies/risk-management" />

  <Card title="Sector accounting" description="The full sector model and double-entry rules." icon="calculator" to="/developers/contracts/accounting" />

  <Card title="Fee Manager" description="Fee formulas, caps, and recipient splits." icon="percent" to="/developers/contracts/fee-manager" />
</Cards>
