Crypto Training

Veda vs Mellow: Architecture, Fund Flows, and Security Tradeoffs

A code-level comparison of Veda BoringVault and Mellow Flexible Vaults: module boundaries, async flows, curator calldata controls, oracle models, and security assumptions.

Crypto Training2025-12-227 min read

Veda and Mellow both market modular vault systems, but they optimize different trust surfaces.

  • Veda keeps a thin vault and pushes policy into manager/teller/accountant modules.
  • Mellow keeps explicit queue/oracle/risk machinery around a core vault and strategy containers.

This post compares them from Solidity code and operational semantics.

Codebases:

Scope and framing#

The comparison focuses on:

  1. module boundaries and responsibilities
  2. end-to-end fund flows
  3. curator execution controls
  4. pricing/oracle update paths
  5. failure modes and user protections
flowchart LR subgraph Mellow [Mellow Flexible Vaults] direction TB MU["User"] --> MDQ["DepositQueue"] MU --> MRQ["RedeemQueue"] MO["Oracle"] --> MV["Vault"] MDQ --> MV MV --> MSV["Subvault"] MC["Curator"] --> MSV MSV --> MEP["External Protocols"] end subgraph Veda [Veda Boring Vault] direction TB VU["User"] --> VT["Teller"] VT --> VB["BoringVault"] VM["ManagerWithMerkleVerification"] --> VB VA["AccountantWithRateProviders"] --> VT VC["Strategist"] --> VM VB --> VEP["External Protocols"] end MU --- VU

Component-by-component architecture#

Mellow core graph#

Primary contracts:

flowchart TB U["User"] --> DQ["DepositQueue"] U --> RQ["RedeemQueue"] DQ --> SM["ShareModule"] RQ --> SM O["Oracle"] --> SM V["Vault"] --- SM V --- VM["VaultModule"] VM --> RM["RiskManager"] VM --> SV["Subvault"] SV --> CM["CallModule"] CM --> VF["Verifier"] CM --> EP["External Protocol"]

Veda core graph#

Primary contracts:

flowchart TB U["User"] --> T["TellerWithMultiAssetSupport"] T --> B["BoringVault"] A["AccountantWithRateProviders"] --> T S["Strategist"] --> M["ManagerWithMerkleVerification"] M --> B WQ["WithdrawQueue"] --> B H["ShareWarden or Hook"] --> B B --> EP["External Protocol"]

Side-by-side responsibility map#

ResponsibilityMellowVeda
Vault coreVault composes modulesBoringVault stays minimal (manage, enter, exit)
Deposit pathExplicit async DepositQueue request/claimTellerWithMultiAssetSupport sync mint path
Redeem pathExplicit async RedeemQueue batch/claimWithdrawQueue or delayed withdraw modules
Strategy executionSubvault + CallModule + VerifierManagerWithMerkleVerification controlling BoringVault.manage
Calldata authorizationCompact/extended/merkle/custom verification typesMerkle leaf over decoder + target + selector + sensitive args
PricingOracle.submitReports + suspicious report handlingAccountantWithRateProviders.updateExchangeRate bounds and pause
Risk accountingRiskManager tracks pending/vault/subvault balancesStrong call-level policy; less native queue risk accounting
Transfer controlsShare manager flags and queue constraintsBeforeTransferHook, deny/allow lists, optional permissioned transfers

Fund flow: deposits and redemptions#

Mellow deposit flow (asynchronous)#

sequenceDiagram autonumber participant User participant DQ as DepositQueue participant RM as RiskManager participant O as Oracle participant V as ShareModule/Vault participant SM as ShareManager User->>DQ: deposit(assets, referral, proof) DQ->>RM: modifyPendingAssets(+assets) O->>V: handleReport(asset, price, depositTs, redeemTs) V->>DQ: handleReport(price, depositTs) DQ->>SM: allocateShares(shares) DQ->>V: transfer settled assets DQ->>RM: pending--, vaultBalance++ User->>V: claimShares(user) V->>DQ: claim(user) DQ->>SM: mintAllocatedShares(user)

Mellow redeem flow (asynchronous with batches)#

sequenceDiagram autonumber participant User participant RQ as RedeemQueue participant SM as ShareManager participant O as Oracle participant V as ShareModule/Vault participant H as RedeemHook User->>RQ: redeem(shares) RQ->>SM: lock(user, shares) O->>V: handleReport(asset, price, depositTs, redeemTs) V->>RQ: handleReport(price, redeemTs) RQ->>SM: burn(locked shares) Note over RQ: batch assets demand created User->>RQ: handleBatches(n) RQ->>V: getLiquidAssets() RQ->>V: callHook(demand) V->>H: pull available liquidity User->>RQ: claim(receiver, timestamps) RQ-->>User: assets

Veda deposit flow (synchronous teller path)#

sequenceDiagram autonumber participant User participant T as TellerWithMultiAssetSupport participant A as AccountantWithRateProviders participant B as BoringVault User->>T: deposit(asset, amount, minShares, receiver) T->>A: getRateInQuoteSafe(asset) T->>B: enter(user, asset, amount, receiver, shares) B-->>T: shares minted T-->>User: deposit complete

Veda withdraw flow (request then completion)#

sequenceDiagram autonumber participant User participant WQ as WithdrawQueue participant A as AccountantWithRateProviders participant B as BoringVault User->>WQ: requestWithdraw(asset, shares, maxLoss, allow3rdParty) WQ->>A: getRateInQuoteSafe(asset) WQ->>B: transfer shares to queue Note over WQ: request matures after withdrawDelay User->>WQ: completeWithdraw(asset, account) WQ->>A: getRateInQuoteSafe(asset) WQ->>B: burn shares and source assets WQ-->>User: assetsOut

Curator calldata control: where each stack is strict#

Mellow verification model#

CallModule.call always invokes Verifier.verifyCall first.

  • ONCHAIN_COMPACT: allowlist by (caller, target, selector)
  • MERKLE_COMPACT: same compact tuple but Merkle-proved
  • MERKLE_EXTENDED: exact (caller, target, value, full calldata)
  • CUSTOM_VERIFIER: pluggable verifier logic
flowchart TB C["Curator calls Subvault.call"] --> P["Verifier payload type"] P --> A1["ONCHAIN_COMPACT"] P --> A2["MERKLE_COMPACT"] P --> A3["MERKLE_EXTENDED"] P --> A4["CUSTOM_VERIFIER"] A1 --> R1["allowedCalls set lookup"] A2 --> R2["merkle proof + compact hash match"] A3 --> R3["merkle proof + exact calldata hash match"] A4 --> R4["delegated verifier decision"] R1 --> X{"pass"} R2 --> X R3 --> X R4 --> X X -->|yes| EX["external call executes"] X -->|no| RV["revert VerificationFailed"]

Veda verification model#

ManagerWithMerkleVerification verifies every call leaf using a decoder/sanitizer staticcall.

Leaf shape includes:

  • decoder/sanitizer contract
  • target
  • non-zero value bit
  • selector
  • packed sensitive address arguments
flowchart TB S["Strategist submits batch"] --> D["for each call: staticcall decoder/sanitizer"] D --> L["build leaf hash(decoder,target,valueBit,selector,args)"] L --> M["verify Merkle proof against strategist root"] M --> K{"valid"} K -->|yes| B["BoringVault.manage executes call"] K -->|no| E["revert FailedToVerifyManageProof"] B --> TS{"totalSupply unchanged?"} TS -->|no| R["revert TotalSupplyMustRemainConstant"] TS -->|yes| OK["batch success"]

Oracle and pricing trust surfaces#

Mellow oracle pipeline#

Oracle.submitReports validates deviation windows, marks suspicious reports, and can route suspicious reports through explicit acceptance.

stateDiagram-v2 [*] --> NoReport NoReport --> Suspicious: first report accepted as suspicious Suspicious --> Accepted: ACCEPT_REPORT_ROLE accepts Accepted --> Healthy: next valid non-suspicious update Healthy --> Healthy: valid bounded report Healthy --> Suspicious: suspicious deviation Healthy --> Invalid: max deviation violated Invalid --> [*]

Veda accountant pipeline#

AccountantWithRateProviders expects off-chain computed exchange rate updates but enforces bounds and update delays; violations pause.

stateDiagram-v2 [*] --> Running Running --> Running: updateExchangeRate within bounds and delay Running --> Paused: out-of-bounds or too-fast update Paused --> Running: authorized unpause
flowchart LR Offchain["Off-chain NAV model"] --> Updater["Authorized updater"] Updater --> Acc["AccountantWithRateProviders.updateExchangeRate"] Acc --> Check1["min update delay"] Acc --> Check2["upper/lower bound"] Check1 --> Gate{"pass"} Check2 --> Gate Gate -->|yes| Rate["new exchange rate"] Gate -->|no| Pause["pause accountant"]

Security model and blast radius#

Mellow#

  • Strength: transparent on-chain async state machine for requests/claims.
  • Strength: explicit risk manager accounting channels (pending, vault, subvault).
  • Risk: if curator does not unwind external positions, redeem batches can stall until liquidity returns.
  • Risk: oracle/report submitter authority remains operationally sensitive.

Veda#

  • Strength: very strict per-call policy surface via Merkle + decoder/sanitizer.
  • Strength: share lock and permissioned withdrawal controls are explicit anti-MEV/operations tools.
  • Risk: exchange-rate update trust sits with authorized updater pipeline.
  • Risk: policy correctness depends on decoder/sanitizer coverage quality.
flowchart TB subgraph UserRisk["User-facing failure modes"] M1["Mellow: delayed claims if liquidity not returned"] M2["Mellow: suspicious report waiting acceptance"] V1["Veda: paused accountant blocks teller operations"] V2["Veda: withdraw maturity/completion windows"] end subgraph Mitigations["Built-in mitigations"] MM1["Mellow queue transparency"] MM2["Mellow oracle deviation checks"] MM3["Mellow risk manager"] VV1["Veda bounded rate updates"] VV2["Veda Merkle call constraints"] VV3["Veda share lock and deny lists"] end M1 --> MM1 M1 --> MM3 M2 --> MM2 V1 --> VV1 V2 --> VV3

Where Veda is stronger#

  1. Calldata precision by default Veda’s manager path forces each strategy call through a Merkle leaf derived from decoded sensitive arguments. This is a tight, explicit policy envelope for strategist execution.

  2. Operational pause posture around pricing Rate updates are bounded and delayed, and invalid updates move the system into paused mode. This creates a deterministic brake at the accounting layer.

  3. Transfer and withdrawal controls Share lock windows, deny/allow lists, and optional permissioned transfers are native in teller/hook workflows.

Where Mellow is stronger#

  1. First-class asynchronous vault semantics Deposit and redeem queues are explicit on-chain request/claim machines with report-driven settlement, which is very clear for users and auditors.

  2. Integrated risk/accounting channels The architecture has dedicated RiskManager, FeeManager, ShareManager, and queue handlers integrated with oracle reports, making state transitions explicit and inspectable.

  3. Strategy containerization Subvault isolation gives an extra blast-radius boundary between core accounting and strategy execution wallets.

Practical design takeaway#

The two systems are not substitutes at every layer.

  • If the priority is tight per-call strategy policy, Veda’s manager/decoder model is hard to beat.
  • If the priority is transparent async user lifecycle and queue semantics, Mellow’s core architecture is stronger.

A robust vault platform can combine both ideas: queue-native lifecycle plus strict, argument-aware call verification.

flowchart LR Q["Async request/claim queues"] --> C["Strategy call policy engine"] C --> O["Bounded oracle/accounting updates"] O --> R["Risk and liquidity controls"] R --> U["Predictable user claim semantics"]