RFP ID
RFP-012 — Curated Lending Vaults
Your Project Name
Horae
Team or Organization Name
Humea
Primary Contact
Vasyl Kyryliuk — vasyl@betterthin.gs · Telegram @VasylKyryliuk · Discord vasyl_eth
Team Members
Humea — the joint brand of Better Things (product and research practice, https://betterthin.gs) and Uddug (engineering studio, https://uddug.com · https://github.com/uddugteam).
Andrey Skurlatov, Lead / architect
- GitHub / LinkedIn: https://github.com/andskur · https://linkedin.com/in/andrew-skurlatov
- Role on this project: owns the vault program design — the call-budget architecture (§4), the adapter conformance specification (§5), custody and the role model — plus the QML + C++ GUI additions to the RFP-008 client, and the security sign-off on every milestone before delivery.
- Status: allocation-based, 11.75 engineer-weeks; per-milestone split in the Milestones section.
- Background relevant to RFP-012: 12+ years of blockchain engineering across Go, Rust and Solidity, on a foundation of C++ desktop engineering that predates the blockchain decade — which is why the Qt/QML client work sits with him rather than with a subcontractor. Led the DLT team at https://gateway.fm after our engineering studio was acquired in 2024, then owned Platform with roughly twenty engineers and full commercial and product responsibility. Product lead for Presto, on which client rollups including the Wirex Pay Chain launched. Regulated-finance application kits — stablecoin issuance and treasury, RWA tokenization — are the closest commercial analogue to role-gated custody with timelocks.
Pavel Dodonov, Program engineer
- GitHub / LinkedIn: https://github.com/pashteto · https://www.linkedin.com/in/paveldodonov/
- Role on this project: the Rust guest against SPEL — share accounting with the virtual offset, ID-based cap algebra, role and timelock state machines, the adapter boundary — plus the Kani, proptest and fuzzing verification stack (§13) and cycle-cost measurement.
- Status: allocation-based, 10.5 engineer-weeks.
- Background relevant to RFP-012: around fifteen years of engineering, a decade of scientific R&D with 20+ published papers. Built shared prover infrastructure for Hermez and Miden — reusable proving capacity with GPU and CPU autoscaling. The zkVM guest is his daily terrain, and the share-maths core is
no_std Rust of exactly the shape Kani verifies — the reason the bounded-verification commitment in §13 is a plan rather than a hope.
Andrey Solovov, Client engineer
- GitHub / LinkedIn: https://github.com/asolovov · https://linkedin.com/in/andrey-solovov-bb665884
- Role on this project: the core-module extensions, the CLI generated from the SPEL IDL, the privacy path with the ephemeral-key registry,
horae-index with the APY series, and the end-to-end integration tests against a standalone sequencer.
- Status: allocation-based, 8.75 engineer-weeks.
- Background relevant to RFP-012: senior blockchain engineering across cross-chain oracle and DeFi protocol implementation in Go, Solidity and zk tooling. Shipped a multi-asset push oracle at 500 ms, a vRNG and pull feed at roughly 1,000 RPS, and the backend architecture for the Wirex Pay Chain. Oracle staleness reasoning is the direct analogue of the cached-valuation freshness bound (§13) and the self-published NAV feed (§11) — the two places in this build where a stale number is a depositor's loss rather than a bug report.
Coverage note. The RFP's recommended team profile asks for four capabilities. Vault-program and custody design — the call-budget architecture, the role model, the adapter boundary — is Skurlatov's, from platform ownership at Gateway.fm and regulated-finance kits. The Rust guest, share accounting and machine-checked verification are Dodonov's, from prover infrastructure on Hermez and Miden. Client-library and integration engineering, including the privacy pattern and the ephemeral-key discipline RFP-008 requires the client to document, is Solovov's. The QML + C++ client surface is Skurlatov's own — C++ desktop engineering precedes his blockchain decade — with Better Things' interface practice supplying the Figma artefacts Supportability 7 requires. Risk and curation is deliberately not an engineering seat: it is the operator's, and §14 names the operator.
Senior engineering bench. Beyond the named engineers, Uddug maintains a senior Rust and Go bench built on the same body of work: zkVM and prover infrastructure — Miden and Hermez; Substrate and IPFS protocol engineering; Polygon-CDK and zkEVM rollup infrastructure behind Presto and the Wirex Pay Chain; and oracle systems including https://github.com/uddugteam/oracle-flare. Better Things adds the product and interface half — the practice behind the published work on https://betterthin.gs/works. Additional engineers and designers are allocated from these benches as scope requires, without external recruitment. The named team is the headline; the bench is depth.
Team composition. The named lead is fixed for the engagement. We further commit to retaining at least half of the named engineers above for its duration; any remaining change is an equivalent-seniority substitution from the bench above. Milestone scope, delivery accountability, review sign-off and the quality bar committed to in this proposal remain unchanged regardless of individual allocation.
Project Summary
The isolated-market core delivered by RFP-008 is the right primitive and the wrong interface
for most of the capital it wants to attract. A lender who supplies a market directly must
evaluate the collateral, the oracle and the LLTV themselves, then monitor and rebalance. Most
passive capital will never do that, which is why on Ethereum the curated vault layer is where
the deposits actually sit. On Morpho today: ~$12.4B total deposits, ~$7.8B TVL and ~$4.6B
active loans, of which $3.81B is under curation in Vaults V2 across 36 curators generating
~$10.0M a year in curation fees — a blended ~26bps, far below the 50% performance-fee cap
(data.morpho.org, 2026-08-17).
We note two corrections to the figures in RFP-012’s overview, because they will be challenged
otherwise. $11.78B is Total Deposits, not TVL — Total Deposits ≈ TVL + Active Loans, and
those three reconcile exactly on Morpho’s own dashboard. And “Blue + vaults” is not
additive: vault deposits are supplied into Blue markets, so vault TVL is a subset of Blue
TVL, not an addend — DefiLlama reports morpho at $8.047B and morpho-blue at $7.975B,
effectively the same number. The ~$4.9B figure fits the Active Loans series rather than the
Blue core. The RFP’s directional claim is right and the underlying case is stronger when
stated as the curation triple above.
We propose Horae: the Morpho Vaults V2 equivalent for LEZ. Permissionless vault
creation over a single loan token; transferable LEZ fungible-token shares; adapter-based
routing into RFP-008 markets with no supply or withdraw queues; absolute and relative caps
keyed to risk ids; curator, allocator and sentinel roles with per-action timelocks and an
immutable minimum floor; a share-denominated performance fee; in-kind redemption via
forceDeallocate with a bounded exit penalty; and on-touch bad-debt realisation with the
exit-ordering asymmetry documented and disclosed rather than hidden. Delivered by extending
RFP-008’s core module, so the GUI and CLI gain the vault surface with no separate front end.
Three things distinguish this bid, and all three come from reading the runtime rather than the
specification.
First, we found a hard requirement that cannot be met as written, and it is arithmetic
rather than opinion. RFP-012 Performance 2 asks that a vault with up to ten allocated
markets not exceed LEZ compute limits on a single deposit or withdrawal. Compute is not the
constraint — the heaviest instruction shipped on LEZ today uses 1.9% of the per-execution
cycle cap. The constraint is that a LEZ transaction may contain at most eleven program
executions in total, and a ten-market fan-out consumes all eleven before a single share is
minted. §4 sets out the proof, the collision with Reliability 2 and 3, and what we propose to
do about it. Neither filed proposal mentions this.
Second, the RFP’s only stated hard blocker is unsatisfied, and we treat that as an
engineering problem rather than a caveat. RFP-012 says development is blocked until RFP-008
is live on devnet or testnet. No lending protocol exists in any public Logos repository, and
RFP-008 was closed on 2026-08-14 with none of its four proposals labelled accepted — so the
interface we must build against belongs to a team the public record does not name. Our answer
is a published adapter conformance specification, a reference mock market implementing
RFP-008’s hard requirements, and a conformance test suite the RFP-008 team can run against
their own implementation — all delivered in M0 and M1, before we depend on anything we do not
control. §5.
Third, we verified every primitive against a commit SHA, and the dependency picture has
drifted in both directions. LP-0015 is genuinely delivered and runtime-enforced. LP-0012 is
closed as a prize and is not upstream at any ref, despite RFP-008 listing it under
“delivered on LEZ” — so vault performance history must be indexed by polling, behind a swap
boundary. Conversely, RFP-008’s claim that no oracle provider is available on LEZ is no
longer true: a TWAP oracle is merged in canonical lez-programs with a shipped IDL and a
deliberately source-agnostic price account. §3 gives the full inventory with paths and refs.
Technical Approach
1. Stack, target and build model
Rust against the SPEL framework, compiled to RISC Zero zkVM guest binaries. State-transition logic in the guest; IDL generation, clients and integration tests as host code. SPEL is in production use — logos-blockchain/lez-programs depends on spel_framework_macros — so this is integration, not research risk.
We target LEZ v0.2.0 — what both canonical consumers pin (lez-programs/Cargo.toml, spel-framework-core/Cargo.toml). For the record: the newest tag is v0.2.4 (2026-08-07), main equals it, dev is 154 commits ahead, and there is no v0.3 tag, branch or release. If a coordinated bump is wanted we will scope it, but we will not build against dev and call it a target.
Two naming traps, stated so nobody rediscovers them: the v0.2.0 crate rename (nssa → lee, nssa_core → lee_core, tree split under lee/ and lez/) is package-level only — SPEL's public API still writes nssa and nssa_core, aliased to those packages. And /LEE/v0.3/… and V03State are internal state-version domain separators living inside v0.2.x, coexisting with /LEE/v0.2/ public-PDA prefixes in the same file. Neither is a release.
Proposed workspace:
horae/
horae-core/ # no_std: share maths, id/cap algebra, timelock state machine,
# pure state transitions
horae-program/ # SPEL guest program: instruction handlers, PDA derivation, custody
horae-adapter/ # the RFP-008 market adapter + the conformance spec and mock (§5)
horae-index/ # account-polling indexer behind the event swap boundary (§10)
horae-coremod/ # extensions to the RFP-008 core module (no GUI dependency)
horae-cli/ # generated from the SPEL IDL
horae-gui/ # QML + C++ additions, Basecamp-loadable via git repo
examples/ # runnable scripts against a standalone sequencer
All code under the MIT + Apache-2.0 dual licence.
2. What we are re-implementing, and from which source
The reference is Morpho Vaults V2, not MetaMorpho V1. RFP-012 was deliberately moved onto V2 semantics (commits 04808f7, d1ee4b0, 7908a9d, May 2026), so V1's ordered queues, single global timelock and 24-hour minimum are all out.
There is no Vaults V2 whitepaper — the normative specification is VaultV2.sol's header NatSpec plus the README; docs.morpho.org is derived prose, wrong in at least one load-bearing place (its "0–3 weeks" timelock bound exists nowhere in the contracts). We cite NatSpec throughout.
The adapter interface we mirror is three functions. In V2's Solidity:
function allocate(bytes data, uint256 assets, bytes4 selector, address sender)
external returns (bytes32[] ids, int256 change);
function deallocate(bytes data, uint256 assets, bytes4 selector, address sender)
external returns (bytes32[] ids, int256 change);
function realAssets() external view returns (uint256 assets);
Three properties of that shape drive our design. The adapter returns both the ids and the signed change, so the vault never learns what a market is, and risk taxonomy is adapter-supplied. The entry-point selector and the original sender are forwarded, so an adapter can behave differently on allocate versus deposit versus forceDeallocate. And realAssets() is the valuation oracle — in V2 the vault loops every adapter on each interest accrual, which Morpho's own NatSpec flags as a denial-of-service surface. On LEZ that loop is the direct cause of the problem in §4, and the main reason a line-by-line port fails.
3. Primitive inventory — verified, with the refs we read
Each line was read at an immutable commit, not /master/ or /main/, which have served stale content for these repositories before. Refs: LEZ-dev = logos-execution-zone refs/heads/dev @ 89815f1 (2026-08-17) · LEZ-main = refs/heads/main ≡ refs/tags/v0.2.4 @ 47eba25 · LEZ-v0.2.0 = refs/tags/v0.2.0 @ a58fbce · PROG = lez-programs refs/heads/main @ 09f3d59 (2026-08-14) · SPEL = logos-co/spel refs/heads/main ≡ v0.6.0 @ 0cb7e09 · PRIZE = lambda-prize refs/heads/master @ de7c5ae.
Headline status — the full table, with file paths, symbols and per-ref evidence, is posted as Appendix A in the first comment on this issue:
- Available and runtime-enforced: general cross-program calls (LP-0015; forgery rejected by upstream negative tests); the chained-call budget (
MAX_NUMBER_CHAINED_CALLS = 10 — materially constraining, §4); fungible-token mint and burn under PDA authority (AMM as production precedent); the clock (1/10/50-block cadence); the TWAP oracle (merged in lez-programs with a shipped IDL — RFP-008's "no oracle provider" claim is stale); the privacy model (per-account witness masks, private PDAs); credit to a foreign private account (balance conservation enforced across the touched set); transaction and data limits (no CU budget — the analogue is RISC Zero cycles, 32 Mi per execution); SPEL (chained calls as a raw Vec<ChainedCall>, no ergonomic macro).
- Not upstream: the event/log mechanism (LP-0012 — no events crate at any ref, §10) and any vault-share price feed (§11).
- Pattern available, no program: the flash-loan prep → callback → assert idiom
forceDeallocate needs — proven by upstream tests, costing 5 of the 11 executions in the simplest case, no reusable program or penalty accounting.
Upstream benchmarks we build on rather than guess: token operations ~117–128k user cycles, AMM AddLiquidity 643k, a fixed non-amortised ~30.4 ms per-call overhead — an eleven-call transaction is roughly 335 ms of sequencer wall time (docs/benchmarks/cycle_bench.md at LEZ-dev; reproduced in Appendix A).
Two clock limitations we design around and document. Pre-states are checked for exact equality and the state machine has no clock special-casing, so a transaction reading CLOCK_01 — which mutates every block — is valid only against the block it was built on; the 10- and 50-block accounts widen the window at the cost of freshness. twap_oracle hard-requires CLOCK_01 (programs/twap_oracle/src/create_current_tick_account.rs, negative test rejecting CLOCK_10), so every oracle-touching vault transaction inherits a one-block retry window: we measure the retry rate on testnet and publish it, and use block_validity_window / timestamp_validity_window on ProgramOutput where an actual timestamp read is not needed.
4. The requirement that cannot be met as written, and what we propose instead
Performance 2 requires that "a vault with up to 10 allocated markets must not exceed LEZ compute limits on any single deposit or withdrawal operation." Compute is not the binding limit. The per-execution cycle cap is 32 Mi, and the heaviest instruction shipped on LEZ today — AMM AddLiquidity at 643,464 cycles — uses 1.9% of it.
The binding limit is the chained-call budget, and its shape is not what its name suggests: the error is named MaxChainedCallsDepthExceeded, but no depth limit is implemented. The driver loop in lee/state_machine/src/validated_state_diff/mod.rs keeps a single chain_calls_counter, incremented once per program execution anywhere in the tree — never decremented, no notion of level — and ensure!(counter <= MAX_NUMBER_CHAINED_CALLS) runs before each execution. Fan-out and nesting draw on one shared flat budget, so:
A LEZ transaction may perform at most eleven program executions — one top-level call plus ten chained calls — regardless of how they are arranged.
Proven upstream rather than inferred: the test suite passes MAX_NUMBER_CHAINED_CALLS + 1 pure siblings and asserts the error — fan-out alone trips the limit named "depth".
What that does to a ten-market vault operation:
| Shape |
Executions |
Fits in 11 |
| Vault root + 10 market calls, nothing else |
11 |
exactly at the ceiling, zero headroom |
| + share mint or burn via the token program |
12 |
no |
| + a self-call continuation for the invariant check |
12–13 |
no |
| Each adapter chaining to the token program to move the loan token (unavoidable) |
~21 |
no, by 2× |
Calibration from shipped code: AMM AddLiquidity, a two-token operation, already spends 5 of 11 executions; N markets plus a share mint plus an oracle tick is roughly 1 + 2N + 2 — about 23 at N = 10.
And it collides with two other requirements. Reliability 2 demands atomic cap enforcement — only achievable inside one transaction; Reliability 3 demands withdrawal succeed whenever total liquidity across adapters covers it. RFP-008's own remedy for compute pressure — a two-phase tail-call continuation — splits the transaction and breaks Reliability 2. As written, the three cannot all hold.
What we propose. V2 already solves most of this, and RFP-012 has mis-transcribed how V2 behaves: an ordinary deposit touches exactly one adapter — the liquidityAdapter — and multi-market fan-out happens only on allocator-driven allocate/deallocate, separate transactions by design. A V2 deposit never fans out across ten markets. So:
- Ordinary deposit and withdraw: idle plus
liquidityAdapter. Measured, published, fits with headroom.
- The bounded multi-adapter drain for Reliability 3: the maximum adapters-per-withdrawal K is derived from the eleven-execution budget, measured, published with its arithmetic, and enforced in-program rather than failing opaquely at the runtime boundary.
- The residual beyond K:
forceDeallocate, the in-kind redemption path RFP-012 already requires — the documented escape when a withdrawal needs more adapters than one transaction can reach, and why non-custodiality survives.
- Cap atomicity is preserved: each transaction's cap check is complete for the ids it touches; nothing is split mid-check.
- Valuation is not a loop over every adapter per accrual: per-adapter valuation is cached and refreshed on touch, so cost is bounded by the adapters an operation actually reaches — a deliberate, budget-forced divergence from V2.
We would rather raise this in the repository than discover it in milestone three. If Logos intends different semantics for Performance 2 we implement those and reprice. We will not pre-commit to a K we have not measured.
5. The RFP-008 dependency, treated as an engineering problem
RFP-012's Platform Dependencies section names exactly one blocker: "RFP-008 is live on LEZ devnet/testnet: this RFP extends the deployed protocol; it cannot proceed without the base layer."
That blocker is not satisfied: no lending protocol exists in any public Logos repository — lez-programs/programs/ contains amm, ata, benchmark, integration_tests, stablecoin, token and twap_oracle, and a search across it and the runtime for lending, morpho, lltv or liquidat finds only a stablecoin doc. RFP-008 was closed as an RFP on 2026-08-14 (commit 3221feb), which also removed it from the proposal form's dropdown — but none of its filed proposals carries an accepted label, so the award, if made, was executed off-issue.
Another team owns the interface and we cannot read it yet; "blocked on RFP-008" is not a plan. So:
- We publish an adapter conformance specification in M0 — the exact instruction surface, account shapes and error semantics an RFP-008 market must expose, derived from RFP-008's own hard requirements.
- A reference mock market implements that surface against
v0.2.0, so the vault is built and tested end to end from M1.
- The conformance suite ships as a deliverable the RFP-008 team can run. Pass — integration is a configuration change; fail — both teams learn which side moves, early and in public.
- Integration against the real protocol is a named M5 deliverable with an explicit no-charge fallback (see M5).
We would welcome Logos naming the RFP-008 team and whether early interface access is possible; that answer reduces this milestone rather than expanding it.
6. Vault state and account layout
One program. Each vault is an independent PDA set keyed by (creator, vault_index), so the account set any operation touches is fixed and per-transaction cost does not grow with the number of vaults.
| Account |
Seeds |
Holds |
Touched by |
Vault |
["vault", creator, index] |
loan token, name, symbol, owner, curator, total assets, total supply, virtual-share offset, performance fee, fee recipient, max growth rate, immutable timelock floor, liquidity_adapter |
every operation on that vault |
Idle |
["idle", creator, index] |
program-owned holding of the loan token not currently allocated |
deposit, withdraw, allocate, deallocate |
AdapterSlot |
["adapter", vault, adapter_id] |
enabled flag, cached valuation, last-touch block, per-adapter exit penalty |
allocate, deallocate, accrual |
CapEntry |
["cap", vault, risk_id] |
allocation, absolute cap, relative cap |
allocate, cap changes |
RoleSet |
["roles", vault] |
allocator set, sentinel set |
role changes, authority checks |
Pending |
["pending", vault, action_hash] |
queued timelocked action, executable-at block |
submit, execute, revoke |
ShareDef |
["shares", vault] |
the LEZ fungible-token definition whose authority is this PDA |
mint and burn on deposit and withdraw |
EventLog |
["events", vault] |
append-only records behind the swap boundary (§10) |
every state transition |
An ordinary deposit touches { Vault, Idle, one AdapterSlot, its CapEntries, ShareDef, destination holding, EventLog } and chains to the token program twice — well inside the eleven-execution budget; the measured figure is published.
Vault state carries an explicit version: u8. The program is immutable once deployed; a confirmed defect follows a documented redeploy-and-migrate path, with migration tooling and a user-communication template shipped rather than pretending the case cannot arise.
7. Share accounting
Share value is assets / shares with a virtual offset on both sides, integer-only, 128-bit intermediates, rounding always in the vault's favour so that no sequence of operations can be used to extract value through rounding:
virtual_shares = 10 ^ max(0, 18 - asset_decimals)
preview_deposit(assets) = assets * (supply + virtual_shares) / (total_assets + 1) // down
preview_mint(shares) = shares * (total_assets + 1) / (supply + virtual_shares) // up
preview_withdraw(assets) = assets * (supply + virtual_shares) / (total_assets + 1) // up
preview_redeem(shares) = shares * (total_assets + 1) / (supply + virtual_shares) // down
Because the LEZ token program stores no decimals field — amounts are raw base units and decimals are a client convention — asset_decimals is a vault parameter set at creation and published, not read from the token. Stated plainly because implicit assumptions of this kind become audit findings.
Morpho's own NatSpec concedes a virtual offset alone is not sufficient, so vault creation requires a non-refundable seed deposit, enforced in the program rather than a runbook, and surfaced in the GUI at creation time.
Growth-rate cap (F7). V2's maxRate: new_total = min(real_assets, total + total * elapsed * rate), capped at 200% APR. It throttles gains only — losses pass through in full — and exists to neutralise donation- and penalty-driven share-price jumps exploitable against vault-share collateral.
One V2 behaviour we will not replicate. V2's maxRate defaults to zero, making a freshly created vault a strictly loss-making instrument — it accrues nothing while realising every loss. We set a documented non-zero default, reject a zero rate with an explicit error, and record the divergence.
Monotonicity, plainly stated. Reliability 1 asks that share value never decrease absent bad debt. Because we implement no management fee — F8 requires only a performance fee — the only exception is realised bad debt, the one the RFP names; Morpho's equivalent proof needs a second carve-out for management fees.
8. Risk ids, caps, and one place the RFP diverges from shipped code
Caps are { allocation, absolute_cap, relative_cap } keyed by an opaque 32-byte risk id. The vault never interprets an id; the adapter supplies both the ids and the signed change, which keeps risk taxonomy out of the vault core. Curators address ids by pre-image, carried in the instruction data so an indexer can recover the human meaning.
F5 says ids key to "a collateral asset, a market, or a protocol." The first two exist in shipped Morpho code and the third does not — Morpho's adapter emits adapter, collateral and market ids, and only the collateral id genuinely aggregates across adapters; no shipped adapter emits a protocol-level id.
We implement the three-tier hierarchy the RFP asks for explicitly — adapter, collateral asset, market — plus an optional protocol id an adapter may emit that genuinely aggregates across adapter instances — what F5 literally requires, and cheap to support once ids are opaque. We will confirm this reading with Logos rather than assume it.
Enforcement, following V2 and stating its limits rather than overselling:
- Checked on allocation, for every id the adapter returns;
absolute_cap == 0 is the kill switch.
- Relative caps measured against total assets at transaction start — the flash-deposit defence; Morpho is candid it is incomplete, and we inherit that.
- Relative caps are not checked on exit — deliberate in V2, kept, because the alternative is blocking withdrawals.
- Caps can be exceeded by accrued interest and donations. Documented, not hidden.
- A sharp edge surfaced in the GUI: markets sharing all their ids cannot be disabled individually. Curators see that before they configure, not after.
Reliability 2's atomicity holds because the cap update and check for every touched id happen in one state transition, and concurrent operations on the same vault touch the same CapEntry accounts and cannot interleave.
9. Roles and timelocks
Four roles, with the same bounded scopes as V2, because the separation is the security property.
| Role |
Can |
Cannot |
| Owner |
set owner, set curator, appoint and rotate sentinels, set vault name and symbol |
allocate, set caps, set fees. The owner does not inherit the other roles' powers |
| Curator |
enable and disable adapters, increase caps, set the performance fee and its recipient, add and remove allocators, set the exit penalty, adjust timelocks above the floor, transfer the curator role — all timelocked. Instantly: decrease any cap, and revoke a pending action |
allocate or deallocate capital, at all |
| Allocator |
allocate from idle into enabled adapters and deallocate back, designate the liquidity_adapter, set the maximum growth rate — all within the curator's caps |
enable adapters, change caps, set fees |
| Sentinel |
deallocate from any enabled adapter back to idle; instantly decrease any cap; revoke any pending timelocked action |
anything risk-increasing — no sentinel branch in allocation, cap increases, adapter enabling or submission |
The sentinel is the defence against a compromised curator key; what makes it worth anything is liveness: proving "a sentinel can always de-risk, from every reachable state" is the hard direction, and it is what we verify (§13).
One sharp edge, mitigated in the GUI rather than silently inherited: an allocator can instantly point the liquidity_adapter at an illiquid market, stopping ordinary deposits and withdrawals. We keep V2's forceDeallocate backstop and surface the liquidity adapter's available liquidity in both depositor and curator views.
Timelocks. Per-action, keyed by action selector, with submit keyed on the full action data so an action cannot be double-queued; any address may execute once the delay expires; cap decreases and revocation are instant.
F9's immutable minimum improves on the reference itself: V2 has no floor at all — zero default, decreaseTimelock unbounded. We implement the RFP's version and state the divergence rather than let a reviewer assume a mis-port.
Our proposed floor: 48 hours for cap increases, adapter enabling, fee changes, allocator additions, gate changes and sentinel rotation; instant for every risk-reducing action; set at creation, immutable, per-action delays configurable upwards only. 48 hours because a depositor's only protection against a risk-increasing change is leaving before it takes effect, and one business day is not a usable notice period. We will take direction on the number.
Milestones, Payout and Timeline
Seven milestones, ~12 weeks wall-clock, inside the RFP's published 12-week estimate. Each milestone is demonstrated against its completion criteria before the next begins, except where overlap is stated. Engineer-week allocations are per milestone and per person.
Milestone 0 — Foundations, primitive verification, RFP-008 conformance specification
Payout: $9,000
Duration: 2 weeks (Engineer-weeks: 3.5 — Skurlatov 1.5 / Dodonov 1 / Solovov 1)
Deliverables:
- Public repository live,
v0.2.0 toolchain green, CI running, SPEL IDL generation wired in.
- Five spikes, each documented with a pass or fail verdict and exact source references at named refs: the eleven-execution budget reproduced and the per-operation execution count measured; PDA-authority mint and burn of a share token via chained call; the flash-loan prep → callback → assert path for forced deallocation; self-published share price into an
OraclePriceAccount and its CLOCK_01 validity-window cost; private-account interaction including a private PDA position.
- The adapter conformance specification and the reference mock market, published for review.
- Design published for Logos review: vault state and account layout, id and cap algebra, role and timelock state machine with the proposed 48-hour floor, event schema and swap boundary.
- Three decisions recorded for Logos to accept or redirect: the Performance 2 architecture (§4); whether socialisation-at-submit is wanted (§12); whether a self-published NAV feed is acceptable for Usability 6 (§11).
- Audit engagement booked with the named firm, with the review window fixed.
Completion criteria: repository live with green CI; all five spikes documented with source references; conformance specification and mock market published; the three decisions reviewable by Logos before implementation begins; auditor booked in writing.
Milestone 1 — Vault core: shares, ids, caps, roles, timelocks
Payout: $16,000
Duration: 2.5 weeks (Engineer-weeks: 6 — Dodonov 2.5 / Skurlatov 2 / Solovov 1.5)
Deliverables:
horae-core: share conversion with the virtual offset and vault-favouring rounding; id and cap algebra; timelock state machine.
create_vault, deposit, withdraw against idle balance; share mint and burn under PDA authority; enforced seed deposit.
- Curator, allocator, sentinel and owner authority surfaces, with negative tests for every power each role must not have.
- Per-action timelocks with the immutable floor, instant cap decreases, revocation, and execute-by-anyone.
proptest suites for share monotonicity, cap algebra and timelock ordering.
Completion criteria: every listed test green, including all negative role tests; CI green on the default branch.
Milestone 2 — Adapters, allocation, liquidity path, the call-budget architecture
Payout: $14,000
Duration: 2 weeks (Engineer-weeks: 5.5 — Dodonov 2.5 / Skurlatov 2 / Solovov 1)
Deliverables:
horae-adapter against the reference mock market; adapter enable and disable.
allocate and deallocate with full id-set cap enforcement, including shared-collateral-id aggregation across two adapter instances.
liquidity_adapter designation and the ordinary deposit and withdraw path through it.
- Cached per-adapter valuation with refresh-on-touch and an enforced staleness bound.
- Maximum growth rate, with a zero rate rejected.
- Measured execution count and cycle cost per operation, and the derived K published, with the arithmetic, per Performance 1 and 2.
Completion criteria: allocation and cap tests green including the cross-adapter aggregation case; concurrent-deposit cap test green under parallel submission; K measured, enforced in-program and published in the repository.
Milestone 3 — Withdrawal, forced deallocation, bad debt, performance fee
Payout: $14,000
Duration: 2 weeks (Engineer-weeks: 5.5 — Dodonov 2 / Skurlatov 2 / Solovov 1.5)
Deliverables:
- Bounded multi-adapter withdrawal drain up to K.
force_deallocate with the bounded per-adapter exit penalty, penalty taken as a withdrawal to the vault so total assets and supply fall together, and the optimal-exit sizing documented.
- On-touch bad-debt realisation, proportional socialisation, realised bad debt tracked and reported per market, and the exit asymmetry reproduced in a test.
- Share-denominated performance fee with lazy accrual, pending fee reflected on read, and the fee-recipient invariant.
- Share-price publication into an
OraclePriceAccount with staleness semantics.
- Transaction size and cycle cost published per operation.
Completion criteria: withdrawal-liquidity test green including the forced-deallocation residual path; bad-debt socialisation and asymmetry tests green; fee accounting reconciles against a computed reference; figures published.
Milestone 4 — Core module, GUI, CLI, privacy path
Payout: $14,000
Duration: 2.5 weeks, overlapping the tail of M3 (Engineer-weeks: 5.5 — Skurlatov 2.5 / Solovov 2.5 / Dodonov 0.5)
Deliverables:
horae-coremod extending the RFP-008 core module, with no GUI dependency; every GUI action reachable through it.
- QML + C++ GUI additions: per-vault and per-user views with every field Usability 2 and 3 require; curator view with cap management, the shared-id warning, pending timelocked actions, and the emergency sequence; risk disclosures for the bad-debt asymmetry, the self-reported share price, and liquidity-adapter capacity.
horae-cli generated from the IDL, covering creation, deposit, withdrawal, allocation, cap and role management, and queries.
- Private-account path: enforced deshield → interact → reshield including private vault share transfer; atomic deshield of token plus gas; fee estimate and balance sufficiency check; pre-confirmation disclosure; single-use ephemeral accounts with a persisted registry plus deterministic derivation.
horae-index and the APY series (soft Functionality 1).
- Figma or equivalent designs for all GUI additions (Supportability 7).
Completion criteria: core module builds and is exercised with no GUI present; CLI runs the full command set against a standalone sequencer; GUI loads in Basecamp from a git repo and completes deposit and withdrawal on both the public and private paths; ephemeral-reuse rejection survives a client restart; designs delivered.
Milestone 5 — Bounded verification, devnet/testnet deployment, documentation
Payout: $9,000
Duration: 1.5 weeks (Engineer-weeks: 3.5 — Dodonov 1.5 / Skurlatov 1 / Solovov 1)
Deliverables:
- Kani harnesses for the three soft-Reliability-1 invariants, plus sentinel liveness and valuation freshness, running in CI;
cargo-fuzz and cargo-mutants results published.
- Deployed and end-to-end tested on LEZ devnet/testnet at
v0.2.0, program address recorded.
- Integration against the delivered RFP-008 protocol, with the conformance report published. Fallback, stated in advance: if RFP-008 is not on devnet at this point, this milestone delivers verified integration against the reference market plus the published conformance report, and the switch to the real protocol is a documented, tested configuration step carried out at no additional charge within the support window.
- README addendum covering vault creation, curator setup, market allocation, the deposit and withdrawal flow, and sentinel configuration.
- Doc packets for each new SDK feature and for the CLI additions (Supportability 5 and 6).
- Privacy and anonymisation properties document updated for the vault layer, aligned with the RFP-008 baseline (Supportability 8).
Completion criteria: all five harnesses pass within stated bounds; testnet smoke test green; conformance report published; a reviewer can follow the README addendum unaided; doc packets submitted; properties document published.
Milestone 6 — Tier-1 security review and remediation
Payout: $36,000 — itemised: $30,000 auditor engagement, $6,000 remediation
Duration: 2.5 weeks, overlapping M5 (Engineer-weeks: 1.5 Humea, plus auditor time — Skurlatov 0.75 / Dodonov 0.5 / Solovov 0.25)
Scope of review, matching Supportability 9: the vault program; the role-permission surface — curator, allocator and sentinel — explicitly, including every negative capability; the share accounting and its rounding; the id and cap enforcement path including cross-adapter aggregation; the timelock state machine and its floor; custody and the adapter boundary; the forced-deallocation and penalty path; and bad-debt realisation and socialisation.
Firm: Zellic as primary. The reasoning is specific rather than a name-drop — Zellic audited Morpho Vaults V2 itself and has Solana Core-BPF program engagements (Address Lookup Table, Config, Feature Gate, Stake Program), which is a rare combination given that most of Morpho's audit bench is EVM-only. Stated alternatives of equivalent standing for the Rust and SVM surface: OtterSec and Neodyme. If the zkVM guest and host-to-guest boundary is pulled into scope, Veridise is the firm with the deepest RISC Zero record. Logos may prefer to contract the auditor directly; in that case this milestone reduces to the remediation engineer-weeks and the bid falls to $82,000 Humea-side.
Deliverables:
- Review report published with the codebase before any mainnet recommendation, per Supportability 9.
- All critical and high findings remediated commit by commit; documented decisions on medium and low.
- Audit-ready artefact pack: test matrix, traceability table, verification harness results, deployment runbook, role and key-separation operations guide.
Completion criteria: report published; no unremediated critical or high findings; CI green.
Total: $112,000 · ~12 weeks · ~31 Humea engineer-weeks plus auditor time
| Milestone |
Payout |
Duration |
Engineer-weeks |
| M0 Foundations, primitive verification, RFP-008 conformance spec |
$9,000 |
2 wks |
3.5 |
| M1 Vault core: shares, ids, caps, roles, timelocks |
$16,000 |
2.5 wks |
6 |
| M2 Adapters, allocation, liquidity path, call-budget architecture |
$14,000 |
2 wks |
5.5 |
| M3 Withdrawal, forced deallocation, bad debt, performance fee |
$14,000 |
2 wks |
5.5 |
| M4 Core module, GUI, CLI, privacy path |
$14,000 |
2.5 wks |
5.5 |
| M5 Bounded verification, testnet deployment, documentation |
$9,000 |
1.5 wks |
3.5 |
| M6 Tier-1 review and remediation |
$36,000 |
2.5 wks |
1.5 + auditor |
| Total |
$112,000 |
~12 wks |
31 + auditor |
M4 overlaps the tail of M3 and M6 overlaps M5, which is how seven milestones fit inside twelve weeks. M0 runs before the development clock in the sense that it is the review gate — but it is inside the twelve weeks and inside the total, not additional to either.
Per-person totals across the engagement: Skurlatov 11.75 · Dodonov 10.5 · Solovov 8.75 = 31 engineer-weeks. No single engineer carries more than 38% of the engagement, so no milestone depends on one person being available.
Total Requested Budget (USD)
$112,000
Relevant Experience
- Price oracles in production, with staleness reasoning — https://github.com/uddugteam/oracle-flare: Go, MIT, 66 commits, generated from our own https://github.com/andskur/go-microservice-template, default endpoints pointing at Gateway infrastructure (wss://oracle.gateway.fm) — an epoch-driven commit/reveal loop that must not double-submit or lose state across reconnects. Alongside it: a multi-asset push oracle at 500 ms and a vRNG and pull feed at roughly 1,000 RPS, built by the client engineer on this team.
Transfers: the cached-valuation freshness bound (§13) and the self-published NAV feed (§11) are the same staleness discipline, applied to a share price instead of a market price.
- Rust in a zkVM context — shared prover infrastructure for Hermez and Miden: reusable proving capacity, GPU and CPU autoscaling, built by the program engineer on this team. Miden is a STARK-based zkVM.
Transfers: RFP-012's guest program is the application half of the same stack, and the no_std share-maths core is exactly the shape Kani verifies (§13).
- Role-gated custody with timelocks, in systems holding user funds — regulated-finance application kits built as a product line inside Gateway.fm: stablecoin issuance and treasury, cross-border payments, RWA tokenization. And custom vesting contracts for DeepNode's token distribution (https://deepnode.ai) on EVM: separate release and cancellation authorities over locked user allocations.
Transfers: who may move funds, who may cancel, and what is disclosed to whom — the same authority-separation shape as curator / allocator / sentinel, and the reason the negative role tests in M1 are written before the positive ones.
- Desktop client and interface practice — the lead's C++ desktop engineering predates his blockchain decade, which is why the QML + C++ client additions sit with him rather than with a subcontractor. The interface half is the practice behind the published work on https://betterthin.gs/works: Revolut budgeting; DoorDash (32M users, cancellations −3%); Opera (300M users); Johnson & Johnson (NPS +40); Tele2 (support calls −14%).
Transfers: the curator view, the shared-id warning and the risk disclosures in §12 and §15 are product work, and this is the practice that does it.
- Risk curation and vault operations, on the operator side — Shift (https://www.shiftrwa.xyz), the operator named in §14: a leveraged-RWA protocol on Solana whose products are built on NAV-managed reserves with daily rebalancing.
Transfers: reserve management and holder disclosure are the curator's daily discipline — cap policy, de-risking under stress, and what a depositor needs to see — and Shift intends to hold the curator role on the first vaults deployed on this program.
- Platform delivery against someone else's acceptance criteria — our studio operated as an embedded department inside https://gateway.fm (2024–2026); the technical lead ran the DLT team, then owned Platform at roughly twenty engineers. Presto, the rollup platform behind the Wirex Pay Chain, a Polygon-CDK validium on L2BEAT.
Transfers: a small named senior squad delivering a platform component end to end, against someone else's roadmap and acceptance criteria — the working model of this grant.
- Specifications that survive external review — the low-latency PerpsV3 data path for Synthetix and Infinex, approved as public XIP-9. On a grant where every deliverable is a public artefact reviewed by someone else, that is the relevant proof.
- Published performance numbers, not adjectives — DeepNode and its Dive platform: 8,777 requests, 0 errors, p50 130.8 ms / p95 138.2 ms, published with its run conditions. Performance 1 asks for exactly this discipline, and it is already practised.
Post-Delivery Plan
The program is immutable once deployed, so post-delivery is about operating it, supporting the teams that compose against it, and having an honest answer for the case where a defect reaches mainnet — and the operating half has a named answer, not a hope.
Handover. Mainnet deployment, curator and sentinel key custody, and any future fee activation are Logos and operator decisions. We hand over what those decisions need: the audit report, verification harness results, transaction-size and cycle-cost benchmarks, the deployment runbook, and a role and key-separation operations guide that spells out why owner, curator, allocator and sentinel keys should sit in different custody — the whole point of the sentinel is that a compromised curator cannot increase risk, and that property evaporates if one key holds both.
Operations. Humea will not hold the curator role on any production vault. Shift, an independent team running its own token project and treasury, intends to act as the curator of the first vaults deployed on this program.
Defect tier, free, 12 months from mainnet. Critical and high-severity defects in delivered code fixed at no cost, with CVE-class findings triaged within 48 hours. Because the program cannot be patched in place, a confirmed core defect follows a documented redeploy-and-migrate path; we provide the migration tooling and a depositor-communication template as part of this tier rather than as a change request.
Maintenance. Core module, GUI, CLI and indexer kept current across LEZ releases through the support window. Two specific obligations we treat as ours rather than as new engagements: migrating the event path onto the canonical LEZ mechanism when it lands (§10), and completing the switch from the reference market to the delivered RFP-008 protocol if that has not happened by M5 (§5).
Integration support. Direct support for the first teams composing against the program. Two concrete cases: RFP-014's liquidation and auction engine will need to reason about vault-held positions during a liquidation, and any RFP-008 market creator considering vault shares as collateral needs the share-price feed's trust assumptions in front of them (§11). We ship both as documentation and worked examples and will work with those teams directly.
Extensions, paid. A management fee, share-access gates, a public allocator, an off-chain risk-monitoring daemon, additional adapters for future yield sources, a second independent audit or a public contest — scoped separately if Logos or the ecosystem wants them. None is assumed.
Permissions and Consent
Program Requirements
RFP ID
RFP-012 — Curated Lending Vaults
Your Project Name
Horae
Team or Organization Name
Humea
Primary Contact
Vasyl Kyryliuk — vasyl@betterthin.gs · Telegram @VasylKyryliuk · Discord vasyl_eth
Team Members
Humea — the joint brand of Better Things (product and research practice, https://betterthin.gs) and Uddug (engineering studio, https://uddug.com · https://github.com/uddugteam).
Andrey Skurlatov, Lead / architect
Pavel Dodonov, Program engineer
no_stdRust of exactly the shape Kani verifies — the reason the bounded-verification commitment in §13 is a plan rather than a hope.Andrey Solovov, Client engineer
horae-indexwith the APY series, and the end-to-end integration tests against a standalone sequencer.Project Summary
The isolated-market core delivered by RFP-008 is the right primitive and the wrong interface
for most of the capital it wants to attract. A lender who supplies a market directly must
evaluate the collateral, the oracle and the LLTV themselves, then monitor and rebalance. Most
passive capital will never do that, which is why on Ethereum the curated vault layer is where
the deposits actually sit. On Morpho today: ~$12.4B total deposits, ~$7.8B TVL and ~$4.6B
active loans, of which $3.81B is under curation in Vaults V2 across 36 curators generating
~$10.0M a year in curation fees — a blended ~26bps, far below the 50% performance-fee cap
(data.morpho.org, 2026-08-17).
We note two corrections to the figures in RFP-012’s overview, because they will be challenged
otherwise. $11.78B is Total Deposits, not TVL —
Total Deposits ≈ TVL + Active Loans, andthose three reconcile exactly on Morpho’s own dashboard. And “Blue + vaults” is not
additive: vault deposits are supplied into Blue markets, so vault TVL is a subset of Blue
TVL, not an addend — DefiLlama reports
morphoat $8.047B andmorpho-blueat $7.975B,effectively the same number. The
~$4.9Bfigure fits the Active Loans series rather than theBlue core. The RFP’s directional claim is right and the underlying case is stronger when
stated as the curation triple above.
We propose Horae: the Morpho Vaults V2 equivalent for LEZ. Permissionless vault
creation over a single loan token; transferable LEZ fungible-token shares; adapter-based
routing into RFP-008 markets with no supply or withdraw queues; absolute and relative caps
keyed to risk ids; curator, allocator and sentinel roles with per-action timelocks and an
immutable minimum floor; a share-denominated performance fee; in-kind redemption via
forceDeallocatewith a bounded exit penalty; and on-touch bad-debt realisation with theexit-ordering asymmetry documented and disclosed rather than hidden. Delivered by extending
RFP-008’s core module, so the GUI and CLI gain the vault surface with no separate front end.
Three things distinguish this bid, and all three come from reading the runtime rather than the
specification.
First, we found a hard requirement that cannot be met as written, and it is arithmetic
rather than opinion. RFP-012 Performance 2 asks that a vault with up to ten allocated
markets not exceed LEZ compute limits on a single deposit or withdrawal. Compute is not the
constraint — the heaviest instruction shipped on LEZ today uses 1.9% of the per-execution
cycle cap. The constraint is that a LEZ transaction may contain at most eleven program
executions in total, and a ten-market fan-out consumes all eleven before a single share is
minted. §4 sets out the proof, the collision with Reliability 2 and 3, and what we propose to
do about it. Neither filed proposal mentions this.
Second, the RFP’s only stated hard blocker is unsatisfied, and we treat that as an
engineering problem rather than a caveat. RFP-012 says development is blocked until RFP-008
is live on devnet or testnet. No lending protocol exists in any public Logos repository, and
RFP-008 was closed on 2026-08-14 with none of its four proposals labelled
accepted— so theinterface we must build against belongs to a team the public record does not name. Our answer
is a published adapter conformance specification, a reference mock market implementing
RFP-008’s hard requirements, and a conformance test suite the RFP-008 team can run against
their own implementation — all delivered in M0 and M1, before we depend on anything we do not
control. §5.
Third, we verified every primitive against a commit SHA, and the dependency picture has
drifted in both directions. LP-0015 is genuinely delivered and runtime-enforced. LP-0012 is
closed as a prize and is not upstream at any ref, despite RFP-008 listing it under
“delivered on LEZ” — so vault performance history must be indexed by polling, behind a swap
boundary. Conversely, RFP-008’s claim that no oracle provider is available on LEZ is no
longer true: a TWAP oracle is merged in canonical
lez-programswith a shipped IDL and adeliberately source-agnostic price account. §3 gives the full inventory with paths and refs.
Technical Approach
1. Stack, target and build model
Rust against the SPEL framework, compiled to RISC Zero zkVM guest binaries. State-transition logic in the guest; IDL generation, clients and integration tests as host code. SPEL is in production use —
logos-blockchain/lez-programsdepends onspel_framework_macros— so this is integration, not research risk.We target LEZ
v0.2.0— what both canonical consumers pin (lez-programs/Cargo.toml,spel-framework-core/Cargo.toml). For the record: the newest tag isv0.2.4(2026-08-07),mainequals it,devis 154 commits ahead, and there is nov0.3tag, branch or release. If a coordinated bump is wanted we will scope it, but we will not build againstdevand call it a target.Two naming traps, stated so nobody rediscovers them: the
v0.2.0crate rename (nssa→lee,nssa_core→lee_core, tree split underlee/andlez/) is package-level only — SPEL's public API still writesnssaandnssa_core, aliased to those packages. And/LEE/v0.3/…andV03Stateare internal state-version domain separators living inside v0.2.x, coexisting with/LEE/v0.2/public-PDA prefixes in the same file. Neither is a release.Proposed workspace:
All code under the MIT + Apache-2.0 dual licence.
2. What we are re-implementing, and from which source
The reference is Morpho Vaults V2, not MetaMorpho V1. RFP-012 was deliberately moved onto V2 semantics (commits
04808f7,d1ee4b0,7908a9d, May 2026), so V1's ordered queues, single global timelock and 24-hour minimum are all out.There is no Vaults V2 whitepaper — the normative specification is
VaultV2.sol's header NatSpec plus the README;docs.morpho.orgis derived prose, wrong in at least one load-bearing place (its "0–3 weeks" timelock bound exists nowhere in the contracts). We cite NatSpec throughout.The adapter interface we mirror is three functions. In V2's Solidity:
Three properties of that shape drive our design. The adapter returns both the ids and the signed change, so the vault never learns what a market is, and risk taxonomy is adapter-supplied. The entry-point selector and the original sender are forwarded, so an adapter can behave differently on
allocateversusdepositversusforceDeallocate. AndrealAssets()is the valuation oracle — in V2 the vault loops every adapter on each interest accrual, which Morpho's own NatSpec flags as a denial-of-service surface. On LEZ that loop is the direct cause of the problem in §4, and the main reason a line-by-line port fails.3. Primitive inventory — verified, with the refs we read
Each line was read at an immutable commit, not
/master/or/main/, which have served stale content for these repositories before. Refs: LEZ-dev =logos-execution-zonerefs/heads/dev@89815f1(2026-08-17) · LEZ-main =refs/heads/main≡refs/tags/v0.2.4@47eba25· LEZ-v0.2.0 =refs/tags/v0.2.0@a58fbce· PROG =lez-programsrefs/heads/main@09f3d59(2026-08-14) · SPEL =logos-co/spelrefs/heads/main≡v0.6.0@0cb7e09· PRIZE =lambda-prizerefs/heads/master@de7c5ae.Headline status — the full table, with file paths, symbols and per-ref evidence, is posted as Appendix A in the first comment on this issue:
MAX_NUMBER_CHAINED_CALLS = 10— materially constraining, §4); fungible-token mint and burn under PDA authority (AMM as production precedent); the clock (1/10/50-block cadence); the TWAP oracle (merged inlez-programswith a shipped IDL — RFP-008's "no oracle provider" claim is stale); the privacy model (per-account witness masks, private PDAs); credit to a foreign private account (balance conservation enforced across the touched set); transaction and data limits (no CU budget — the analogue is RISC Zero cycles, 32 Mi per execution); SPEL (chained calls as a rawVec<ChainedCall>, no ergonomic macro).forceDeallocateneeds — proven by upstream tests, costing 5 of the 11 executions in the simplest case, no reusable program or penalty accounting.Upstream benchmarks we build on rather than guess: token operations ~117–128k user cycles, AMM
AddLiquidity643k, a fixed non-amortised ~30.4 ms per-call overhead — an eleven-call transaction is roughly 335 ms of sequencer wall time (docs/benchmarks/cycle_bench.mdat LEZ-dev; reproduced in Appendix A).Two clock limitations we design around and document. Pre-states are checked for exact equality and the state machine has no clock special-casing, so a transaction reading
CLOCK_01— which mutates every block — is valid only against the block it was built on; the 10- and 50-block accounts widen the window at the cost of freshness.twap_oraclehard-requiresCLOCK_01(programs/twap_oracle/src/create_current_tick_account.rs, negative test rejectingCLOCK_10), so every oracle-touching vault transaction inherits a one-block retry window: we measure the retry rate on testnet and publish it, and useblock_validity_window/timestamp_validity_windowonProgramOutputwhere an actual timestamp read is not needed.4. The requirement that cannot be met as written, and what we propose instead
Performance 2 requires that "a vault with up to 10 allocated markets must not exceed LEZ compute limits on any single deposit or withdrawal operation." Compute is not the binding limit. The per-execution cycle cap is 32 Mi, and the heaviest instruction shipped on LEZ today — AMM
AddLiquidityat 643,464 cycles — uses 1.9% of it.The binding limit is the chained-call budget, and its shape is not what its name suggests: the error is named
MaxChainedCallsDepthExceeded, but no depth limit is implemented. The driver loop inlee/state_machine/src/validated_state_diff/mod.rskeeps a singlechain_calls_counter, incremented once per program execution anywhere in the tree — never decremented, no notion of level — andensure!(counter <= MAX_NUMBER_CHAINED_CALLS)runs before each execution. Fan-out and nesting draw on one shared flat budget, so:Proven upstream rather than inferred: the test suite passes
MAX_NUMBER_CHAINED_CALLS + 1pure siblings and asserts the error — fan-out alone trips the limit named "depth".What that does to a ten-market vault operation:
Calibration from shipped code: AMM
AddLiquidity, a two-token operation, already spends 5 of 11 executions; N markets plus a share mint plus an oracle tick is roughly1 + 2N + 2— about 23 at N = 10.And it collides with two other requirements. Reliability 2 demands atomic cap enforcement — only achievable inside one transaction; Reliability 3 demands withdrawal succeed whenever total liquidity across adapters covers it. RFP-008's own remedy for compute pressure — a two-phase tail-call continuation — splits the transaction and breaks Reliability 2. As written, the three cannot all hold.
What we propose. V2 already solves most of this, and RFP-012 has mis-transcribed how V2 behaves: an ordinary deposit touches exactly one adapter — the
liquidityAdapter— and multi-market fan-out happens only on allocator-drivenallocate/deallocate, separate transactions by design. A V2 deposit never fans out across ten markets. So:liquidityAdapter. Measured, published, fits with headroom.forceDeallocate, the in-kind redemption path RFP-012 already requires — the documented escape when a withdrawal needs more adapters than one transaction can reach, and why non-custodiality survives.We would rather raise this in the repository than discover it in milestone three. If Logos intends different semantics for Performance 2 we implement those and reprice. We will not pre-commit to a K we have not measured.
5. The RFP-008 dependency, treated as an engineering problem
RFP-012's Platform Dependencies section names exactly one blocker: "RFP-008 is live on LEZ devnet/testnet: this RFP extends the deployed protocol; it cannot proceed without the base layer."
That blocker is not satisfied: no lending protocol exists in any public Logos repository —
lez-programs/programs/containsamm,ata,benchmark,integration_tests,stablecoin,tokenandtwap_oracle, and a search across it and the runtime forlending,morpho,lltvorliquidatfinds only a stablecoin doc. RFP-008 was closed as an RFP on 2026-08-14 (commit3221feb), which also removed it from the proposal form's dropdown — but none of its filed proposals carries anacceptedlabel, so the award, if made, was executed off-issue.Another team owns the interface and we cannot read it yet; "blocked on RFP-008" is not a plan. So:
v0.2.0, so the vault is built and tested end to end from M1.We would welcome Logos naming the RFP-008 team and whether early interface access is possible; that answer reduces this milestone rather than expanding it.
6. Vault state and account layout
One program. Each vault is an independent PDA set keyed by
(creator, vault_index), so the account set any operation touches is fixed and per-transaction cost does not grow with the number of vaults.Vault["vault", creator, index]liquidity_adapterIdle["idle", creator, index]AdapterSlot["adapter", vault, adapter_id]CapEntry["cap", vault, risk_id]RoleSet["roles", vault]Pending["pending", vault, action_hash]ShareDef["shares", vault]EventLog["events", vault]An ordinary deposit touches
{ Vault, Idle, one AdapterSlot, its CapEntries, ShareDef, destination holding, EventLog }and chains to the token program twice — well inside the eleven-execution budget; the measured figure is published.Vault state carries an explicit
version: u8. The program is immutable once deployed; a confirmed defect follows a documented redeploy-and-migrate path, with migration tooling and a user-communication template shipped rather than pretending the case cannot arise.7. Share accounting
Share value is
assets / shareswith a virtual offset on both sides, integer-only, 128-bit intermediates, rounding always in the vault's favour so that no sequence of operations can be used to extract value through rounding:Because the LEZ token program stores no decimals field — amounts are raw base units and decimals are a client convention —
asset_decimalsis a vault parameter set at creation and published, not read from the token. Stated plainly because implicit assumptions of this kind become audit findings.Morpho's own NatSpec concedes a virtual offset alone is not sufficient, so vault creation requires a non-refundable seed deposit, enforced in the program rather than a runbook, and surfaced in the GUI at creation time.
Growth-rate cap (F7). V2's
maxRate:new_total = min(real_assets, total + total * elapsed * rate), capped at 200% APR. It throttles gains only — losses pass through in full — and exists to neutralise donation- and penalty-driven share-price jumps exploitable against vault-share collateral.One V2 behaviour we will not replicate. V2's
maxRatedefaults to zero, making a freshly created vault a strictly loss-making instrument — it accrues nothing while realising every loss. We set a documented non-zero default, reject a zero rate with an explicit error, and record the divergence.Monotonicity, plainly stated. Reliability 1 asks that share value never decrease absent bad debt. Because we implement no management fee — F8 requires only a performance fee — the only exception is realised bad debt, the one the RFP names; Morpho's equivalent proof needs a second carve-out for management fees.
8. Risk ids, caps, and one place the RFP diverges from shipped code
Caps are
{ allocation, absolute_cap, relative_cap }keyed by an opaque 32-byte risk id. The vault never interprets an id; the adapter supplies both the ids and the signed change, which keeps risk taxonomy out of the vault core. Curators address ids by pre-image, carried in the instruction data so an indexer can recover the human meaning.F5 says ids key to "a collateral asset, a market, or a protocol." The first two exist in shipped Morpho code and the third does not — Morpho's adapter emits adapter, collateral and market ids, and only the collateral id genuinely aggregates across adapters; no shipped adapter emits a protocol-level id.
We implement the three-tier hierarchy the RFP asks for explicitly — adapter, collateral asset, market — plus an optional protocol id an adapter may emit that genuinely aggregates across adapter instances — what F5 literally requires, and cheap to support once ids are opaque. We will confirm this reading with Logos rather than assume it.
Enforcement, following V2 and stating its limits rather than overselling:
absolute_cap == 0is the kill switch.Reliability 2's atomicity holds because the cap update and check for every touched id happen in one state transition, and concurrent operations on the same vault touch the same
CapEntryaccounts and cannot interleave.9. Roles and timelocks
Four roles, with the same bounded scopes as V2, because the separation is the security property.
liquidity_adapter, set the maximum growth rate — all within the curator's capsThe sentinel is the defence against a compromised curator key; what makes it worth anything is liveness: proving "a sentinel can always de-risk, from every reachable state" is the hard direction, and it is what we verify (§13).
One sharp edge, mitigated in the GUI rather than silently inherited: an allocator can instantly point the
liquidity_adapterat an illiquid market, stopping ordinary deposits and withdrawals. We keep V2'sforceDeallocatebackstop and surface the liquidity adapter's available liquidity in both depositor and curator views.Timelocks. Per-action, keyed by action selector, with
submitkeyed on the full action data so an action cannot be double-queued; any address may execute once the delay expires; cap decreases and revocation are instant.F9's immutable minimum improves on the reference itself: V2 has no floor at all — zero default,
decreaseTimelockunbounded. We implement the RFP's version and state the divergence rather than let a reviewer assume a mis-port.Our proposed floor: 48 hours for cap increases, adapter enabling, fee changes, allocator additions, gate changes and sentinel rotation; instant for every risk-reducing action; set at creation, immutable, per-action delays configurable upwards only. 48 hours because a depositor's only protection against a risk-increasing change is leaving before it takes effect, and one business day is not a usable notice period. We will take direction on the number.
Milestones, Payout and Timeline
Seven milestones, ~12 weeks wall-clock, inside the RFP's published 12-week estimate. Each milestone is demonstrated against its completion criteria before the next begins, except where overlap is stated. Engineer-week allocations are per milestone and per person.
Milestone 0 — Foundations, primitive verification, RFP-008 conformance specification
Payout: $9,000
Duration: 2 weeks (Engineer-weeks: 3.5 — Skurlatov 1.5 / Dodonov 1 / Solovov 1)
Deliverables:
v0.2.0toolchain green, CI running, SPEL IDL generation wired in.OraclePriceAccountand itsCLOCK_01validity-window cost; private-account interaction including a private PDA position.Completion criteria: repository live with green CI; all five spikes documented with source references; conformance specification and mock market published; the three decisions reviewable by Logos before implementation begins; auditor booked in writing.
Milestone 1 — Vault core: shares, ids, caps, roles, timelocks
Payout: $16,000
Duration: 2.5 weeks (Engineer-weeks: 6 — Dodonov 2.5 / Skurlatov 2 / Solovov 1.5)
Deliverables:
horae-core: share conversion with the virtual offset and vault-favouring rounding; id and cap algebra; timelock state machine.create_vault,deposit,withdrawagainst idle balance; share mint and burn under PDA authority; enforced seed deposit.proptestsuites for share monotonicity, cap algebra and timelock ordering.Completion criteria: every listed test green, including all negative role tests; CI green on the default branch.
Milestone 2 — Adapters, allocation, liquidity path, the call-budget architecture
Payout: $14,000
Duration: 2 weeks (Engineer-weeks: 5.5 — Dodonov 2.5 / Skurlatov 2 / Solovov 1)
Deliverables:
horae-adapteragainst the reference mock market; adapter enable and disable.allocateanddeallocatewith full id-set cap enforcement, including shared-collateral-id aggregation across two adapter instances.liquidity_adapterdesignation and the ordinary deposit and withdraw path through it.Completion criteria: allocation and cap tests green including the cross-adapter aggregation case; concurrent-deposit cap test green under parallel submission; K measured, enforced in-program and published in the repository.
Milestone 3 — Withdrawal, forced deallocation, bad debt, performance fee
Payout: $14,000
Duration: 2 weeks (Engineer-weeks: 5.5 — Dodonov 2 / Skurlatov 2 / Solovov 1.5)
Deliverables:
force_deallocatewith the bounded per-adapter exit penalty, penalty taken as a withdrawal to the vault so total assets and supply fall together, and the optimal-exit sizing documented.OraclePriceAccountwith staleness semantics.Completion criteria: withdrawal-liquidity test green including the forced-deallocation residual path; bad-debt socialisation and asymmetry tests green; fee accounting reconciles against a computed reference; figures published.
Milestone 4 — Core module, GUI, CLI, privacy path
Payout: $14,000
Duration: 2.5 weeks, overlapping the tail of M3 (Engineer-weeks: 5.5 — Skurlatov 2.5 / Solovov 2.5 / Dodonov 0.5)
Deliverables:
horae-coremodextending the RFP-008 core module, with no GUI dependency; every GUI action reachable through it.horae-cligenerated from the IDL, covering creation, deposit, withdrawal, allocation, cap and role management, and queries.horae-indexand the APY series (soft Functionality 1).Completion criteria: core module builds and is exercised with no GUI present; CLI runs the full command set against a standalone sequencer; GUI loads in Basecamp from a git repo and completes deposit and withdrawal on both the public and private paths; ephemeral-reuse rejection survives a client restart; designs delivered.
Milestone 5 — Bounded verification, devnet/testnet deployment, documentation
Payout: $9,000
Duration: 1.5 weeks (Engineer-weeks: 3.5 — Dodonov 1.5 / Skurlatov 1 / Solovov 1)
Deliverables:
cargo-fuzzandcargo-mutantsresults published.v0.2.0, program address recorded.Completion criteria: all five harnesses pass within stated bounds; testnet smoke test green; conformance report published; a reviewer can follow the README addendum unaided; doc packets submitted; properties document published.
Milestone 6 — Tier-1 security review and remediation
Payout: $36,000 — itemised: $30,000 auditor engagement, $6,000 remediation
Duration: 2.5 weeks, overlapping M5 (Engineer-weeks: 1.5 Humea, plus auditor time — Skurlatov 0.75 / Dodonov 0.5 / Solovov 0.25)
Scope of review, matching Supportability 9: the vault program; the role-permission surface — curator, allocator and sentinel — explicitly, including every negative capability; the share accounting and its rounding; the id and cap enforcement path including cross-adapter aggregation; the timelock state machine and its floor; custody and the adapter boundary; the forced-deallocation and penalty path; and bad-debt realisation and socialisation.
Firm: Zellic as primary. The reasoning is specific rather than a name-drop — Zellic audited Morpho Vaults V2 itself and has Solana Core-BPF program engagements (Address Lookup Table, Config, Feature Gate, Stake Program), which is a rare combination given that most of Morpho's audit bench is EVM-only. Stated alternatives of equivalent standing for the Rust and SVM surface: OtterSec and Neodyme. If the zkVM guest and host-to-guest boundary is pulled into scope, Veridise is the firm with the deepest RISC Zero record. Logos may prefer to contract the auditor directly; in that case this milestone reduces to the remediation engineer-weeks and the bid falls to $82,000 Humea-side.
Deliverables:
Completion criteria: report published; no unremediated critical or high findings; CI green.
Total: $112,000 · ~12 weeks · ~31 Humea engineer-weeks plus auditor time
M4 overlaps the tail of M3 and M6 overlaps M5, which is how seven milestones fit inside twelve weeks. M0 runs before the development clock in the sense that it is the review gate — but it is inside the twelve weeks and inside the total, not additional to either.
Per-person totals across the engagement: Skurlatov 11.75 · Dodonov 10.5 · Solovov 8.75 = 31 engineer-weeks. No single engineer carries more than 38% of the engagement, so no milestone depends on one person being available.
Total Requested Budget (USD)
$112,000
Relevant Experience
- Price oracles in production, with staleness reasoning — https://github.com/uddugteam/oracle-flare: Go, MIT, 66 commits, generated from our own https://github.com/andskur/go-microservice-template, default endpoints pointing at Gateway infrastructure (
wss://oracle.gateway.fm) — an epoch-driven commit/reveal loop that must not double-submit or lose state across reconnects. Alongside it: a multi-asset push oracle at 500 ms and a vRNG and pull feed at roughly 1,000 RPS, built by the client engineer on this team.Transfers: the cached-valuation freshness bound (§13) and the self-published NAV feed (§11) are the same staleness discipline, applied to a share price instead of a market price.
Transfers: RFP-012's guest program is the application half of the same stack, and the
no_stdshare-maths core is exactly the shape Kani verifies (§13).Transfers: who may move funds, who may cancel, and what is disclosed to whom — the same authority-separation shape as curator / allocator / sentinel, and the reason the negative role tests in M1 are written before the positive ones.
Transfers: the curator view, the shared-id warning and the risk disclosures in §12 and §15 are product work, and this is the practice that does it.
Transfers: reserve management and holder disclosure are the curator's daily discipline — cap policy, de-risking under stress, and what a depositor needs to see — and Shift intends to hold the curator role on the first vaults deployed on this program.
Transfers: a small named senior squad delivering a platform component end to end, against someone else's roadmap and acceptance criteria — the working model of this grant.
Post-Delivery Plan
The program is immutable once deployed, so post-delivery is about operating it, supporting the teams that compose against it, and having an honest answer for the case where a defect reaches mainnet — and the operating half has a named answer, not a hope.
Handover. Mainnet deployment, curator and sentinel key custody, and any future fee activation are Logos and operator decisions. We hand over what those decisions need: the audit report, verification harness results, transaction-size and cycle-cost benchmarks, the deployment runbook, and a role and key-separation operations guide that spells out why owner, curator, allocator and sentinel keys should sit in different custody — the whole point of the sentinel is that a compromised curator cannot increase risk, and that property evaporates if one key holds both.
Operations. Humea will not hold the curator role on any production vault. Shift, an independent team running its own token project and treasury, intends to act as the curator of the first vaults deployed on this program.
Defect tier, free, 12 months from mainnet. Critical and high-severity defects in delivered code fixed at no cost, with CVE-class findings triaged within 48 hours. Because the program cannot be patched in place, a confirmed core defect follows a documented redeploy-and-migrate path; we provide the migration tooling and a depositor-communication template as part of this tier rather than as a change request.
Maintenance. Core module, GUI, CLI and indexer kept current across LEZ releases through the support window. Two specific obligations we treat as ours rather than as new engagements: migrating the event path onto the canonical LEZ mechanism when it lands (§10), and completing the switch from the reference market to the delivered RFP-008 protocol if that has not happened by M5 (§5).
Integration support. Direct support for the first teams composing against the program. Two concrete cases: RFP-014's liquidation and auction engine will need to reason about vault-held positions during a liquidation, and any RFP-008 market creator considering vault shares as collateral needs the share-price feed's trust assumptions in front of them (§11). We ship both as documentation and worked examples and will work with those teams directly.
Extensions, paid. A management fee, share-access gates, a public allocator, an off-chain risk-monitoring daemon, additional adapters for future yield sources, a second independent audit or a public contest — scoped separately if Logos or the ecosystem wants them. None is assumed.
Permissions and Consent
Program Requirements