Accounting and Flow of Funds
MultiVehicle implements rigorous double-entry accounting principles to track all asset movements through the system. Understanding the sector accounting model and asset flow is essential for operators managing multi-vehicle deployments.
Sector Accounting Model
All asset movements follow double-entry bookkeeping, where every transfer is recorded as a movement between sectors.
Core Sectors
| Sector | Purpose | Counted in |
|---|---|---|
| ENTRY | Assets entering the system. Carries negative balances, so it is a flow marker rather than a holding. | Neither (virtual) |
| AVAILABLE | Idle base assets awaiting allocation. | totalAssets() and withdrawable() |
| RESERVED | Base assets the operator has earmarked. Asset-only — share moves into it are rejected on-chain. | totalAssets() only |
| ALLOCATION | Sub-vehicle shares from settled deposits (deployed capital). | totalAssets() |
| EXIT | Assets leaving the system. | Neither (virtual) |
RESERVED is not a rename of an old “REDEEM” sector: it holds operator-earmarked
base assets that still count toward totalAssets() but are deliberately excluded
from withdrawable().
Dynamic Sectors
| Sector | Format | Purpose |
|---|---|---|
| Vehicle (staging) Sector | 0x01 + 11 zero bytes + the vehicle address | Per-vehicle staging area, where value waits for a query to be created for it |
| Query Sector | 0x02-prefixed | Ephemeral, used only while recovering a failed sub-query |
Static sector ids are the sector name as right-aligned ASCII in a 32-byte word —
so AVAILABLE is 0x0000…617661696c61626c65. There are no “pending” sectors: value
in flight sits in the relevant vehicle’s staging sector.
Deposit Flow
Assets flowing from users into sub-vehicles follow this path:
User Wallet
│
├─ User initiates deposit
│
▼
┌─────────────────┐
│ MultiVehicle │
│ (Contract) │
└────────┬────────┘
│
├─ 1. Transfer assets from user
│
▼
┌─────────┐
│ ENTRY │ ← Virtual sector (accounting marker)
└────┬────┘
│
├─ 2. Record inflow
│
▼
┌───────────┐
│ AVAILABLE │ ← Idle assets, awaiting allocation
└─────┬─────┘
│
├─ 3. QueueStrategyEngine determines allocation
│
▼
┌──────────────────┐
│ Vehicle Sector │ ← Staging: 0x01 + 11 zero bytes + vehicle address
│ (e.g., Aave) │ Value waits here for a query, and stays here
└────────┬─────────┘ while that query is in flight
│
├─ 4. Dispatch creates a STEAM query
├─ 5. Query progresses: PROCESSING → UNLOCKING → SETTLED
│
▼
┌────────────┐
│ ALLOCATION │ ← Sub-vehicle shares (deployed capital)
└────────────┘ Deposit Flow Details
Step 1-2: Asset Reception
// A deposit pulls the assets and books them into AVAILABLE, crossing the
// virtual ENTRY marker on the way in. Pass allocate=true to run the strategy
// engine and dispatch the resulting allocations in the same call.
accountingEngine.deposit(amount, false) Step 3: Allocation Decision
// QueueStrategyEngine processes deposit queue
depositQueue = [{aave, 60k}, {morpho, 30k}]
// Determines: allocate X to Aave vehicle Step 4-5: Query Dispatch
// AVAILABLE → the vehicle's staging sector.
accountingEngine.move(ISectorAccountingEngine.MoveParams({
from: SectorLib.AVAILABLE,
to: vehicle.toSector(),
asset: address(baseAsset),
amount: amount,
operationId: operationId
}));
// Create the STEAM query. The value stays in the vehicle's staging sector
// while the query is in flight; settled shares land in settledDestination.
accountingEngine.dispatch(ISectorAccountingEngine.DispatchParams({
vehicle: vehicle,
mode: Mode.DEPOSIT,
amount: type(uint256).max, // everything staged, cap-limited
settledDestination: SectorLib.ALLOCATION,
rejectedDestination: SectorLib.AVAILABLE,
minOutput: 0, // required when amount is the max sentinel
data: "",
operationId: operationId // same id, so the steps correlate
})); Redeem Flow
Assets flowing from sub-vehicles back to users follow this path:
┌────────────┐
│ ALLOCATION │ ← Sub-vehicle shares
└─────┬──────┘
│
├─ 1. QueueStrategyEngine determines unallocation
│
▼
┌──────────────────┐
│ Vehicle Sector │ ← Staging for redeem; the in-flight query's value
└────────┬─────────┘ stays here until it settles
│
├─ 2. Dispatch creates the redeem query
├─ 3. Query progresses and settles
│
▼
┌───────────┐
│ AVAILABLE │ ← Base assets, now withdrawable
└─────┬─────┘
│
├─ 4. Record outflow
│
▼
┌─────────┐
│ EXIT │ ← Virtual sector (accounting marker)
└────┬────┘
│
├─ 5. Transfer to user
│
▼
┌─────────────────┐
│ MultiVehicle │
│ (Contract) │
└────────┬────────┘
│
▼
User Wallet Redeem Flow Details
Step 1: Unallocation Decision
// A redemption is a STEAM query in REDEEM mode, not a redeem() method.
multiVehicle.create(query) // query.mode = Mode.REDEEM
// QueueStrategyEngine processes redeem queue
redeemQueue = [{morpho, 10k}, {aave, 20k}]
// Determines: unallocate from Morpho first, then Aave if needed Step 2-3: Query Dispatch
// ALLOCATION → the vehicle's staging sector. The asset is the vehicle
// itself, because what moves here is that vehicle's shares.
accountingEngine.move(ISectorAccountingEngine.MoveParams({
from: SectorLib.ALLOCATION,
to: vehicle.toSector(),
asset: address(vehicle),
amount: shares,
operationId: operationId
}));
// Dispatch the redeem. Settled base assets land in settledDestination;
// a rejected redeem returns the shares to ALLOCATION.
accountingEngine.dispatch(ISectorAccountingEngine.DispatchParams({
vehicle: vehicle,
mode: Mode.REDEEM,
amount: type(uint256).max,
settledDestination: SectorLib.AVAILABLE,
rejectedDestination: SectorLib.ALLOCATION,
minOutput: 0,
data: "",
operationId: operationId
})); Step 4-5: Asset Return
// Unlocking the query pays the receiver, booking the outflow across the
// virtual EXIT marker.
multiVehicle.unlock(query) // returns (newState, paidAsset) Asynchronous Redemptions
When immediate liquidity is insufficient, the QueryRedeemQueue handles asynchronous fulfillment:
User Redeem Request
│
├─ Insufficient immediate liquidity
│
▼
┌──────────────────┐
│ QueryRedeemQueue │
│ │
│ Demand created │ ← User's pending redemption
└────────┬─────────┘
│
│ ... waiting for liquidity ...
│
├─ Keeper/Operator calls VehicleManager.feedQueryRedeemQueue()
│
▼
┌──────────────────┐
│ Fulfillment │ ← Liquidity provision
└────────┬─────────┘
│
├─ Binary search matching
│
▼
User receives assets (full or partial) Exchange Rate & Share Price
MultiVehicle uses the ERC4626 standard for pricing:
shares = assets × totalSupply / totalAssets() Total Assets Calculation
The totalAssets() function aggregates value across all sectors:
totalAssets =
AVAILABLE sector (idle base assets)
+ RESERVED sector (operator-earmarked base assets)
+ ALLOCATION sector (sub-vehicle shares, converted to base asset)
+ ephemeral accounting (expected outputs from in-flight queries) Why Ephemeral Accounting Matters
Without ephemeral accounting, in-flight queries would create pricing anomalies:
Without Ephemeral Accounting (❌ Incorrect):
Time T0: totalAssets = 100k
User deposits 10k → query dispatched
Time T1: totalAssets = 100k (10k staged in the vehicle sector, not counted)
Share price drops incorrectly! With Ephemeral Accounting (✅ Correct):
Time T0: totalAssets = 100k
User deposits 10k → query dispatched
Time T1: totalAssets = 110k (10k expected output tracked)
Share price remains accurate! Query Lifecycle States
Sub-vehicle queries progress through STEAM protocol states:
Standard Flow (Synchronous)
EMPTY → UNLOCKING → SETTLED Asynchronous Flow
EMPTY → PROCESSING → (PAUSED →) UNLOCKING → SETTLED Error Recovery
PROCESSING → RECOVERING → REJECTED State Responsibilities
| State | Accounting Impact | Operator Action |
|---|---|---|
| EMPTY | No query exists | - |
| PROCESSING | Assets received, vehicle performing operations. Ephemeral accounting active | Wait for vehicle |
| PAUSED | Paused for external condition (cooldown, oracle) | Call resume() |
| UNLOCKING | Processing complete, output ready for claim | Call unlock |
| SETTLED | Query complete, assets in final sector | - |
| RECOVERING | Error state, assets frozen | Investigate and recover |
| REJECTED | Query failed, assets returned | Handle failure |
Sector Transfer Examples
Example 1: Simple Deposit
// User deposits 1000 USDC
// Current state: AVAILABLE=50k, ALLOCATION=100k
1. ENTRY ← 1000 (virtual inflow)
2. AVAILABLE ← 1000 (from ENTRY)
// Now: AVAILABLE=51k
3. vehicleSector ← 1000 (move, from AVAILABLE)
// Now: AVAILABLE=50k
4. dispatch(DEPOSIT) creates the query
// Value stays in vehicleSector while in flight
// Ephemeral: +1000 expected shares
5. ALLOCATION ← 1000 shares (query settles)
// Now: ALLOCATION=101k shares
// Ephemeral cleared Example 2: Redeem with Queue
// User redeems shares worth 2000 USDC
// Immediate liquidity: 500 USDC
// Need: 1500 USDC more
1. ALLOCATION → vehicleSector (move, asset = the vehicle's shares)
2. dispatch(REDEEM) creates the query
3. AVAILABLE ← 500 USDC (query settles, immediate portion)
4. QueryRedeemQueue.createDemand(1500 USDC)
// User partially fulfilled: 500 USDC
// Demand: 1500 USDC pending
// Later: Operator provides liquidity. feedQueryRedeemQueue lives on the
// VehicleManager now, and takes NO arguments — it feeds whatever the queue
// needs from what is available. To pull assets back out of the queue, use
// VehicleManager.retrieveQueryRedeemQueueAssets(amount).
5. vehicleManager.feedQueryRedeemQueue()
6. AVAILABLE → EXIT → user receives the remaining 1500 USDC Monitoring & Observability
Operators should monitor these key metrics:
Sector Balances
DEPOSIT: Amount of idle capital
ALLOCATION: Total deployed capital
REDEEM: Liquidity ready for withdrawal
Pending: Assets in flight Queue Health
QueryRedeemQueue.demand: Total pending redemptions
QueryRedeemQueue.fulfillment: Available liquidity
Average wait time: User experience metric Vehicle Health
Per-vehicle: totalAssets, share price, query states
Distribution: Actual vs target allocations Best Practices
- Monitor vehicle staging sectors: High staged balances indicate slow query settlement
- Maintain Redeem Liquidity: Keep sufficient DEPOSIT/REDEEM for immediate withdrawals
- Regular Queue Fulfillment: Minimize QueryRedeemQueue demand wait times
- Validate Sector Balances: Ensure accounting integrity with regular audits
- Track Ephemeral Accounting: Verify share price accuracy during high query volume
Summary
The MultiVehicle accounting system provides:
- Accurate pricing through ephemeral accounting
- Complete auditability via double-entry bookkeeping
- Flexible allocation through queue-based strategies
- Async support for real-world DeFi protocols
- Operator transparency with comprehensive sector tracking