Prepared by:
HALBORN
Last Updated 06/05/2026
Date of Engagement: April 7th, 2026 - May 11th, 2026
100% of all REPORTED Findings have been addressed
All findings
102
Critical
0
High
4
Medium
22
Low
1
Informational
75
Templar Protocol engaged Halborn to perform a focused Smart Contract Assessment of the Templar Soroban Vault, a curator-driven, over-collateralized lending vault deployed on Stellar's Soroban runtime. The vault is the deployable Soroban executor for the shared, chain-agnostic templar-vault-kernel and ships together with a dedicated governance contract, a SEP-41 share-token contract, and a Blend lending-market adapter. The objective of this engagement was to validate the safety of asset accounting, redemption, governance authority, migration, and adapter integrations under realistic adversarial conditions on Soroban.
Halborn performed manual review, line-by-line analysis, focused regression and property-style testing, and an adversarial hardening pass that converted theoretical findings into runnable PoC tests across the four in-scope contract crates. In total, 102 issues were confirmed: 4 High, 22 Medium, 1 Low, and 75 Informational. Severity was assigned using BVSS 2.0.
The most impactful issues are concentrated in three areas: (i) adapter-trust accounting, where the vault credits adapter-reported realized assets and external-asset totals without bounding cumulative growth or verifying token-balance deltas, enabling share-price corruption under a malicious or stale adapter; (ii) withdrawal liveness, where a routine allocator-class ExecuteWithdraw can place the vault into a persistent Withdrawing stuck state with no normal ABI recovery surface; and (iii) governance and migration semantics, where the governance contract cannot reach Pause / SetRestrictions under the default RBAC, several governance configuration paths bypass the migration freeze, and emergency-class roles can revoke unrelated proposals. A fourth high-severity issue, positional adapter mapping, can silently re-bind every market to the wrong adapter on a routine supply-queue reorder.
Medium-severity issues cluster around the fee-anchor lifecycle (the anchor is mishandled across initialize, deposit, resync, fee changes, migration, and emergency reset), the governance command surface (no production submit paths for several admin surfaces, default zero timelocks, TimelockConfig may be raised to u64::MAX, immediate Skim + default skim recipient drains foreign tokens, share-token set_vault has no timelock or event, virtual offsets remain mutable post-capitalization), and state durability (state and restrictions blobs lack a schema version and can exceed Soroban's per-entry size at high queue depths). Informational findings primarily concern observability gaps, view-side rounding / saturation, codec canonicalization, and dead or test-only paths.
All Medium and High findings include reproducible regression tests; the dynamic hardening pass added 46 adversarial tests across 10 files over the prior ~250 baseline tests and confirmed five concrete exploit chains (initialize frontrun + skim drain, multi-step adapter inflation, governance + migration lockout, skim-recipient rugpull, share-token rebind + admin chain).
Adapter accounting (Immediate priority, High): derive realized withdrawal amounts from balance_after - balance_before instead of trusting adapter return values, and enforce a per-window cumulative growth / shrink cap on external-asset syncs in addition to the existing per-call 2x guard. Bind adapters to market_id via an explicit map so supply-queue reorders cannot silently rebind markets.
Redemption liveness (High): prevent OpState::Withdrawing entry when idle assets cannot satisfy at least MIN_WITHDRAWAL_ASSETS, expose an allocator-only continuation or AbortWithdrawing recovery path, and allow ResyncIdleBalance while in Withdrawing so that direct asset transfers can unblock a stuck queue.
Governance correctness (Medium): grant the governance contract both Curator and Sentinel in load_vault_bootstrap so it can actually reach Pause / SetRestrictions; gate every SetGovernanceConfig branch on migration_in_progress; cap MAX_TIMELOCK_NS at a realistic upper bound; reject zero-timelock deployments; add submit paths for allocators, adapter allowlist, virtual offsets, and the upgrade / migrate / cancel commands.
Auxiliary contracts (Medium): convert share-token set_vault / set_admin and Blend adapter set_vault / set_pool / set_admin into two-step rotations with timelocks and structured events; lock share-token decimals; propagate vault pause to auxiliaries; add upgrade / migrate paths.
State durability (Medium): prepend a schema-version byte to every persisted blob and route migrate() through it; lower kernel-side MAX_PENDING to a value that fits comfortably below Soroban's per-entry size cap, or page the withdraw and governance queues into per-entry storage; validate encoded sizes on every write.
Observability (Informational / Medium): resolve kernel-hash addresses to Soroban addresses before emit, use structured topics and versioned event payloads, add events for initialize and auxiliary deployments, and emit per-token amounts on Skim.
The assessment covered the entire Soroban-side vault stack, the chain-agnostic kernel it embeds, and every auxiliary contract bound to the deployed vault. All file paths are relative to the repository at github.com/Templar-Protocol/contracts.
Soroban vault runtime: contract/vault/soroban/src/** (entrypoints, curator vault, effect interpreter, address map, storage, helpers, market adapter wrappers, fungible-vault math, proxy view).
Governance contract: contract/vault/soroban/governance/** (submission, timelock, revocation, abdication, queued action persistence).
Share-token (SEP-41): contract/vault/soroban/share-token/** (admin rotation, vault rebind, mint / burn, allowance, TTL).
Blend adapter: contract/vault/soroban/blend-adapter/** (supply / withdraw, pool / vault retargeting, admin-only manual paths, reserve freshness).
Vault kernel and curator primitives: contract/vault/kernel/** and contract/vault/curator-primitives/** (state machine, queue, fees math, RBAC classes, policy state).
Shared types and wire codec: contract/vault/soroban/shared-types/** (postcard-based VaultCommand / VaultCommandResult).
STRIDE model: contract/vault/soroban/STRIDE.md was reviewed and used to seed adversarial test cases.
External protocols (Blend pool internals, SEP-41 SAC reference implementation, Stellar core protocol).
Off-chain infrastructure (relayer, keepers, indexers) unless their assumptions affect on-chain invariants.
Front-end and operational tooling.
The diagram below summarizes how a caller-supplied VaultCommand travels through the Soroban entrypoint, the CuratorVault wrapper, and the shared kernel, then materializes as on-chain effects against the share token, the asset token, and the Blend lending pool. The governance contract acts as an in-protocol caller that dispatches authorized commands into the same vault surface.
Layer | Component | Role |
|---|---|---|
Entry |
| Public entrypoints: |
Runtime |
| Loads bootstrap state, maps addresses, authorizes by |
Kernel |
| Pure state machine returning new state plus a |
State |
| Persists state, policy, restrictions, supply queue, fees, address map; gates entry while migrating. |
Effects |
| Executes share / asset effects, publishes postcard kernel events. |
Governance |
| Submits, accepts, revokes, and dispatches commands; manages timelocks, abdication, queued actions. |
Tokens | Share token, asset token | SEP-41 contracts holding share supply and underlying asset balances. |
External | Blend adapter, Blend lending pool | Sit across an adapter trust boundary; report |
The engagement combined manual code review, threat-model-driven analysis, and dynamic regression / property testing structured into phased work blocks. Severity assignment used BVSS 2.0 and consolidation removed duplicates, false positives, and reclassified cross-cutting issues.
P1. Vault entrypoints, deposit and conversion math.
P2. RBAC, initialize, and governance authority surfaces.
P3. Withdrawal pipeline, queue, and cooldown invariants.
P4. Fee accrual anchor lifecycle and proxy-view consistency.
P5. Storage, migration, and TTL handling.
P6. Effect interpreter, address mapping, and cross-contract calls.
P7. Governance contract, timelocks, revocation, and abdication.
P8. Share-token (SEP-41) contract.
P9. Blend adapter.
P10. Command wire codec and command dispatch surface.
P11. Existing test surface, parity tests, and harness assumptions.
D1. Storage codec round-trips and malformed input.
D2. Initialize and bootstrap state.
D3. Deposit and share conversion at boundaries.
D4. Fuzz targets and property tests on conversion / storage.
D5. Effect interpreter and address mapping property tests.
D6. Withdrawal pipeline, FIFO, cooldown, partial-idle settlement.
D7. Governance timelock matrix, revoker matrix, queue replacement.
D8. Share-token and Blend adapter integration tests.
D9. Migration / upgrade / cancel lifecycle.
D10. Performance, storage sizing, and queue depth.
D11. Kernel / Soroban parity and high-value arithmetic.
D12. Event integrity (kernel, admin, governance, auxiliary).
Hardening pass. 46 adversarial tests in 10 files covering inflation, donation griefing, initialize frontrun, storage corruption, adapter compromise, governance compromise chain, atomic-vs-Withdrawing race, skim-recipient rugpull, share-token rebind, and governance queue DoS.
Audience: Protocol engineers, Smart-contract reviewers, Operators, Curators, Governance signers, Executive stakeholders.
Scope: Templar Soroban Vault stack as defined in the Scope section, with cross-references to the existing contract/vault/soroban/STRIDE.md threat model.Method / Framework
DFD-lite plus trust boundaries (Section 1) to clarify where each control must hold.
STRIDE-informed risk register (Section 0) expressed for non-engineers, with operational regulation, hardening, and ongoing assurance per risk.
Scenario catalog (Section 2) covering accounting, governance, adapter trust, migration, observability, and operational risk.
Recurring assurance cadence (Section 0.c) to ensure these controls are maintained release over release.
Vault asset accounting: state.idle_assets, state.external_assets, and per-market principal must reflect on-chain token balances at all times.
Share-price integrity: convert_to_shares / convert_to_assets outputs must depend only on real assets and shares plus governance-bounded offsets, with virtual offsets immutable post-capitalization.
Redemption liveness: queued withdrawals must always have a callable recovery path, even at low idle balances or after partial allocations.
Governance authority: the governance contract is the documented control plane; pause, restrictions, and configuration changes must route through it, with timelocks where intended.
Migration safety: while migration_in_progress is set, no state-mutating command should be reachable, including configuration writes that change recovery authority.
Auxiliary contracts: share-token and Blend adapter must not provide silent rotation surfaces that break vault expectations.
What could happen: A malicious, stale, or compromised adapter reports realized withdrawals or external-asset totals that the vault credits without a balance-delta check. The vault's accounting drifts upward or downward independent of real tokens held.
Confirmed in: Withdraw allocations trust adapter-reported realized assets (High); adapter-reported external assets can be inflated cumulatively or deflated to zero (High); admin-only manual Blend shortcuts desync vault accounting (Medium); positional adapter mapping silently misroutes funds on supply-queue reorder (High).
Operational regulation: require curators to whitelist adapter implementations via on-chain governance; monitor every RefreshMarkets call for delta size; alert on adapter rotations.
Hardening controls: derive realized amounts from token-balance deltas; enforce cumulative per-window growth / shrink caps; map adapters by market_id instead of queue position; reject queue reorders that change any existing market-to-adapter binding.
Ongoing assurance: property tests fuzzing adapter return values independent of token transfers, and regression tests for queue / adapter rebinding on every release.
What could happen: The vault transitions into OpState::Withdrawing without enough idle assets to make progress; the shipped Soroban ABI exposes no normal recovery path (start_allocation, ResyncIdleBalance, and RefreshMarkets all require Idle; VaultCommand has no AbortWithdrawing).
Confirmed in: Low-liquidity withdrawal execution can trap the vault in Withdrawing with no normal ABI recovery path (High).
Operational regulation: monitor for transitions into Withdrawing followed by zero asset movement; alert on stuck queues approaching cooldown expiry.
Hardening controls: gate the Withdrawing transition on minimum idle availability; allow ResyncIdleBalance while in Withdrawing; expose an allocator-only abort / continuation command.
Ongoing assurance: stuck-state regression test in CI; structured VaultCommandResult exposing "entered Withdrawing but paid nothing" so off-chain automation can react.
What could happen: A compromised governance key promotes itself to sentinel (immediate), pauses indefinitely (immediate), then uses SetGovernanceConfig during migration to rotate curator / governance into attacker-controlled addresses while operators expect the freeze to hold.
Confirmed in: Governance contract cannot pause / unpause or update restrictions on the deployed RBAC (Medium); several governance commands proceed during migration_in_progress (Medium); TimelockConfig can be raised to u64::MAX immediately (Medium); guardian / sentinel revokers can cancel any pending proposal (Medium); default deploy initializes all timelocks to zero (Medium).
Operational regulation: use phishing-resistant MFA and multi-party signing for governance keys; treat upgrade windows as monitored events; require post-upgrade attestation that no role rotated during migration.
Hardening controls: grant governance both Curator and Sentinel; enforce require_not_migrating on every set_governance_config_impl branch; cap MAX_TIMELOCK_NS at a realistic upper bound; scope revoker authority by action kind; reject zero-timelock deployments at the governance constructor.
Ongoing assurance: dynamic test that asserts every state-mutating command is rejected while the migration flag is set; matrix tests for revoker scope and timelock-bound enforcement.
What could happen: Governance moves virtual offsets after capitalization, instantly changing the conversion formula and diluting or rewarding existing holders without an explicit price event. In parallel, the share-token admin redirects the mint / burn privilege to an attacker contract in a single transaction with no event.
Confirmed in: Virtual offsets can be changed after capitalization (Medium); share-token admin can redirect vault-mint privilege without timelock or event (Medium); auxiliary contract admin rotation is single-step (Low / Medium variants).
Operational regulation: require change control and post-mortem review for any virtual-offset update; monitor share-token vault() / admin() accessors; bind share-token admin keys to multi-party signing.
Hardening controls: lock virtual offsets after the first deposit (or after a configurable bootstrap window); convert share-token set_vault / set_admin to two-step rotations with timelocks and events; reject rotations to self, asset-token, or share-token addresses.
Ongoing assurance: share-price regression test that asserts convert_to_shares is stable across offset writes once capitalization has occurred; share-token rotation event monitoring in CI.
What could happen: The fee-accrual anchor is left at (0, 0) at initialize, never advanced on Soroban handle_deposit, frozen by resync_idle_balance for shrinks, neutralized by the early-return on anchor_total_assets == 0, not reset on SetFees, and preserved across migrate() / emergency-reset. The deployed VaultCommand set never reaches RefreshFees.
Confirmed in: Cluster of fee-anchor findings across initialize, handle_deposit divergence from NEAR, resync_idle_balance timestamp handling, total_assets_for_fee_accrual early return, migrate / handle_emergency_reset preservation, retroactive fee application, and proxy-view share-count inflation.
Operational regulation: document the fee anchor as a single invariant (the anchor reflects (total_assets, ledger_timestamp_ns) after every deposit, fee change, refresh, resync, migrate, and emergency-reset) and require updates to be reviewed against it.
Hardening controls: advance the anchor on every Soroban deposit; reset on SetFees and after recovery paths; remove the anchor_total_assets == 0 early return; expose RefreshFees through a governance-routed command; fuzz the full state machine and assert the anchor invariant.
Ongoing assurance: property test walking the full state machine and asserting the anchor invariant; preview tests that drive proxy_view instead of duplicating math.
What could happen: State, policy, restrictions, supply-queue, market, principal, cap-group, and fees-spec blobs lack a schema version. The withdraw queue and governance pending queue each live in a single Soroban entry capped at 64 KiB, so kernel-advertised limits are unreachable in practice and large restrictions writes can brick subsequent updates. cancel_migration() cannot roll back an already-swapped WASM.
Confirmed in: State blob size cap restricts effective MAX_PENDING (Medium); restrictions blob can exceed entry-size limit (Medium); state blobs lack schema version and migrate() does not migrate state / policy blobs (Medium); decoders accept trailing bytes or non-canonical fees blobs (Medium / Low); cancel_migration() cannot roll back WASM (Low).
Operational regulation: document the per-entry size budget and require change control for any structural addition; preserve the previous WASM hash so an emergency revert is possible.
Hardening controls: prepend a schema-version byte to every persisted blob; lower kernel-side MAX_PENDING; page the withdraw and governance queues; validate encoded sizes on write; reject trailing bytes in policy / restrictions / fees decoders.
Ongoing assurance: regression tests across blob-size boundaries and migration round-trip tests for downgrade or forward migration.
What could happen: Skim is Immediate, the default skim recipient is the governance contract itself, and the action accepts any non-asset, non-share token. A single governance approval moves every non-principal token balance with no timelock, no per-token allowlist, and no per-balance event.
Confirmed in: Immediate Skim action with default skim recipient drains foreign tokens in one transaction (Medium); fee recipient configuration is positional (Informational).
Operational regulation: document skim policy and recipient; review every SetGovernanceConfig(SKIM_RECIPIENT) and Skim command against an allowlist of expected tokens.
Hardening controls: make Skim timelocked or require a per-token allowlist; emit per-token, per-balance events; tighten SetGovernanceConfig(SKIM_RECIPIENT) to the timelocked governance path.
Ongoing assurance: regression test that Skim emits a structured per-token event and that the recipient rotation requires the governance flow.
What could happen: Off-chain monitors miss state changes because addresses are raw kernel hashes, events have a single topic, and several action surfaces emit no event at all (e.g., share-token set_vault, set_admin, constructor). The wire codec has no version field, collapses decoder errors, and accepts multiple encodings for the same logical action.
Confirmed in: Event observability gaps cluster (Informational); wire codec lacks version, collapses error variants (Informational); multiple encodings map to the same governance action (Informational); burn / burn_from emit identical events (Informational).
Operational regulation: treat events as a public API; review event schema changes alongside ABI changes.
Hardening controls: resolve addresses on emit; use structured topics; version event payloads; prepend a codec version byte; route decoder errors into structured variants; canonicalize commands.
Ongoing assurance: event-integrity test suite (added in D12) extended over time as the surface grows.
Every release: run accounting parity, fee anchor invariant, withdrawal liveness, migration freeze, and adapter accounting regression packs.
Daily: monitor for Withdrawing entries with zero payout, abnormal RefreshMarkets deltas, and governance-key access.
Weekly: review queued governance proposals, pending timelock matures, and any share-token / adapter admin events.
Monthly: verify configured timelocks against the documented minimums; verify per-entry blob sizes are within the budget; run the adapter compromise property tests.
Quarterly: purple-team exercise covering initialize-frontrun, governance-compromise chain, skim rugpull, adapter inflation, and migration lockout.
Semi-annual: emergency-reset and migration tabletop, including WASM rollback rehearsal.
The deployed system is a four-contract Soroban deployment plus the curator-driven vault kernel embedded in the runtime. The primary trust boundaries are summarized below.
TB-1, Caller / role boundary: Soroban-side RbacAuth over ActionKind classes (Curator, Sentinel, Allocator, PolicyAdmin) gates access to vault entrypoints.
TB-2, Vault / governance boundary: Governance is the documented control plane and the only address granted Role::Curator by load_vault_bootstrap; configuration writes must travel through it.
TB-3, Vault / adapter boundary: Adapters are external contracts that may report realized amounts and total assets; the vault must not credit those values without local verification.
TB-4, Vault / share-token boundary: Mint / burn is privileged on the share-token via require_vault_invoker; rotation of vault or admin on the share-token instantly changes the trust chain.
TB-5, Vault / migration boundary: While migration_in_progress = true, no state-mutating command should be reachable; only migrate and cancel_migration should clear the flag.
TB-6, Storage boundary: Persisted blobs (state, policy, restrictions, supply-queue, markets, principal, cap-groups, fees-spec) define the schema contract; any change must preserve forward and backward compatibility.
Adapter realized-amount inflation: credit-without-balance-check, mitigated by deriving realized amounts from token-balance deltas (High).
External-asset cumulative inflation: per-step 2x cap insufficient; mitigated by cumulative per-window caps and an explicit loss / finality flow (High).
Direct-transfer mispricing: unaccounted donations until manual resync; mitigated by auto-resync or pre-deposit balance check (Medium).
Virtual-offset repricing post-capitalization: immutable-after-bootstrap or bounded change rate (Medium).
Share-conversion truncation: as_u128_trunc downcast must be replaced with a checked conversion (Medium).
Low-idle stuck state: withdrawal liveness failure with no normal ABI recovery (High).
Queue durability: single-entry queue capped by per-entry size (Medium); withdraw queue decode does not validate invariants (Low).
Partial-idle dequeue semantics: documented but currently surfaces as proportional payout without explicit caller indication.
Pause / restrictions unreachable from governance under default RBAC (Medium).
Migration-freeze bypass via SetGovernanceConfig (Medium).
TimelockConfig can be raised to u64::MAX (Medium).
Guardian / sentinel revoker overreach (Medium).
Default zero-timelock deployment (Medium).
Missing submit paths for allocators, allowed-adapters, virtual offsets, upgrade, migrate, and cancel_migration (Medium).
Permanent governance and adapter admin roles with no rotation (Medium / Low).
SetGovernance accepts arbitrary contracts including the vault itself (Low).
Share-token set_vault / set_admin with no timelock or event (Medium / Low).
Blend adapter set_vault / set_pool with no timelock or event (Medium).
Admin-only manual Blend supply / withdraw bypassing vault accounting (Medium).
Auxiliary contracts lack upgrade / migrate and pause / freeze mechanisms (Low).
Vault Restrictions not propagated to share-token transfers (Low).
Anchor lifecycle mishandled end-to-end (Medium cluster, see Risk 5).
Fee recipients positional (Informational).
Schema versioning absent across persisted blobs (Medium).
Per-entry size constraints on state, restrictions, and pending queues (Medium / Low).
Permissionless ExtendTtl not refreshing address-book entries (Medium).
cancel_migration() WASM rollback gap (Low).
Trailing-byte / non-canonical decoding across policy, restrictions, and fees (Medium / Low).
Raw kernel addresses, single topics, unversioned payloads across vault, governance, share-token, and adapter (Informational cluster).
Wire codec lacks version, collapses decoder errors (Informational).
Multiple encodings represent the same governance action (Informational).
Permissive auth helpers in tests diverge from production RBAC (Medium).
Allocation lifecycle helpers bypass apply_kernel_action (Medium).
External-growth and parity tests do not exercise Soroban paths or high-value boundaries (Low).
Preview, fee, and onboarding tests duplicate production internals or pre-seed state (Low / Informational).
Positional adapter mapping causes silent market remap on supply queue reorder. Adapters are bound to markets by queue position; a routine queue reorder rebinds every market to the wrong adapter and silently misroutes allocator funds.
Low-liquidity withdrawal execution can trap the vault in Withdrawing with no normal ABI recovery path. ResyncIdleBalance, start_allocation, and refresh_markets all require Idle; no AbortWithdrawing exists.
Withdraw allocations trust adapter-reported realized assets without verifying token-balance delta. Adapter return value is credited to idle_assets without a before / after balance check.
Adapter-reported external assets can be inflated cumulatively or deflated to zero. The single-step 2x cap does not prevent compounded growth across transactions; deflation is unbounded in one step.
Governance contract cannot pause / unpause or update restrictions under default RBAC, governance only holds Curator; pause routes through Sentinel.
Several governance commands proceed during migration_in_progress, curator, governance, sentinel, guardians, allocators, skim recipient, virtual offsets, fees, and Skim are reachable mid-migration.
Immediate Skim action with default skim recipient drains foreign tokens in one transaction.
Share-token set_vault has no timelock or event.
Default governance deployment initializes all timelocks to zero; TimelockConfig can be raised to u64::MAX immediately, blocking future reductions.
Shipped governance contract is missing submit methods for allocators, allowed-adapters, virtual offsets, upgrade, migrate, and cancel_migration.
Fee accrual is unreachable from the deployed Soroban ABI (no RefreshFees command) and the fee anchor is mishandled across initialize, deposit, resync, migration, and emergency reset.
State and restrictions blobs lack a schema version and can exceed Soroban's per-entry size at high queue depths.
Permissionless ExtendTtl does not refresh persisted address-book mappings used by future effects.
Direct underlying transfers remain outside total_assets until manual resync, allowing pre-resync deposits to capture the surplus.
Virtual conversion offsets can be changed after the vault is capitalized, instantly changing share pricing.
Vault set_cap rejects every matured governance-proposed cap increase because of a current = None classification mismatch.
Blend adapter admin can retarget the vault receiver or pool without timelock or event, including admin-only manual paths that desync vault accounting.
Permissive auth helpers in the test suite diverge from production RBAC, masking production-only failures.
Bind adapters by market_id; derive realized amounts from balance deltas; enforce cumulative external-asset caps.
Guard Withdrawing entry on minimum idle and expose a recovery surface.
Grant governance Sentinel; gate every SetGovernanceConfig branch on the migration flag.
Cap MAX_TIMELOCK_NS at a realistic upper bound; reject zero-timelock deployments; restrict revoker scope.
Convert share-token set_vault / set_admin and Blend adapter set_vault / set_pool / set_admin into two-step rotations with timelocks and events.
Reset the fee anchor on every SetFees, deposit, resync, migrate, and emergency-reset; expose RefreshFees via governance.
Prepend a schema-version byte to every persisted blob and route migrate() through it; page the withdraw and governance queues.
Add governance submit methods for allocators, allowed-adapters, virtual offsets, and the upgrade / migrate / cancel commands.
Lock virtual offsets after the bootstrap window; reject post-capitalization moves that imply a price delta beyond a configurable tolerance.
Add admin-rotation and upgrade / migrate / pause paths to the share-token and Blend adapter.
Tighten Skim with a per-token allowlist or governance-routed timelock.
Resolve kernel addresses on emit, use structured topics, and version event payloads across all four contracts.
Add the BVSS-flagged property tests to CI: adapter-trust fuzz, fee-anchor invariant, withdrawal liveness, migration freeze, governance revoker scope, and storage round-trip / size boundaries.
Replace permissive auth helpers in critical tests with production RbacAuth wiring and add one full deploy-to-allocate path that uses only production entrypoints.
| Security analysis | Risk level | Remediation |
|---|---|---|
| Positional adapter mapping causes silent market remap on supply queue reorder | High | Solved - 05/19/2026 |
| Low-liquidity withdrawal execution can trap the vault in Withdrawing with no normal ABI recovery path | High | Solved - 05/19/2026 |
| Withdraw allocations trust adapter-reported realized assets without verifying token balance delta | High | Solved - 05/19/2026 |
| Adapter-reported external assets can be inflated cumulatively or deflated to zero | High | Solved - 05/19/2026 |
| Governance contract cannot pause, unpause, or update restrictions on the deployed RBAC | Medium | Solved - 05/19/2026 |
| Share-token admin can redirect vault-mint privilege without timelock or event | Medium | Solved - 05/19/2026 |
| Shipped governance contract cannot manage allocators or the adapter allowlist | Medium | Solved - 05/19/2026 |
| Contract-governance deployments have no routed path for upgrade, migrate, or cancel_migration | Medium | Solved - 05/19/2026 |
| Default governance deployment initializes all timelocks to zero | Medium | Solved - 05/19/2026 |
| No production code path adds new markets to the vault | Medium | Solved - 05/19/2026 |
| Vault set_cap rejects every matured governance-proposed cap increase | Medium | Solved - 05/19/2026 |
| Fee accrual is unreachable from the deployed Soroban ABI | Medium | Solved - 05/19/2026 |
| Initial fee_anchor at (0, 0) inflates proxy_view total_shares once performance fees are enabled | Medium | Solved - 05/19/2026 |
| Soroban handle_deposit does not advance fee_anchor, diverging from the NEAR implementation | Medium | Solved - 05/19/2026 |
| Permissionless ExtendTtl does not refresh persisted address-book mappings required by future effects | Medium | Solved - 05/19/2026 |
| State blobs have no schema version and migrate() does not migrate state or policy blobs | Medium | Solved - 05/19/2026 |
| State blob size cap restricts effective MAX_PENDING to 580 pending withdrawals on Soroban | Medium | Solved - 05/19/2026 |
| Admin-only Blend adapter shortcuts can desynchronize vault accounting | Medium | Solved - 05/19/2026 |
| Blend adapter admin can retarget the vault receiver or pool without timelock or event | Medium | Solved - 05/19/2026 |
| Governance pending queue is stored as a single size-capped instance entry | Medium | Solved - 05/19/2026 |
| Governance configuration and skim commands bypass the migration freeze | Medium | Solved - 05/19/2026 |
| Guardian and sentinel revokers can cancel unrelated governance proposals | Medium | Solved - 05/19/2026 |
| Virtual conversion offsets can be changed after the vault is capitalized | Medium | Solved - 05/19/2026 |
| TimelockConfig can be raised to u64::MAX immediately, blocking future reductions | Medium | Solved - 05/19/2026 |
| Immediate `Skim action with default skim recipient drains foreign tokens in one transaction | Medium | Solved - 05/19/2026 |
| Direct underlying transfers remain outside `total_assets until manual resync, allowing pre-resync deposits to capture the surplus | Medium | Solved - 05/19/2026 |
| Permissionless initialize allows deployment-race governance takeover if initialization is not atomic | Low | Risk Accepted - 05/19/2026 |
| Share-conversion helpers truncate 256-bit quotients to u128 | Informational | Solved - 05/19/2026 |
| deserialize_fees_spec accepts a 113-byte blob with no-growth tag and silently discards 16 trailing bytes | Informational | Acknowledged - 05/19/2026 |
| resync_idle_balance advances fee_anchor.total_assets without updating timestamp_ns, distorting future fee math | Informational | Solved - 05/19/2026 |
| total_assets_for_fee_accrual early-returns when anchor_total_assets == 0, neutralizing the configured max_growth_rate cap | Informational | Solved - 05/22/2026 |
| migrate() and handle_emergency_reset preserve fee_anchor, perpetuating staleness across recovery paths | Informational | Solved - 05/22/2026 |
| Policy and restrictions decoders accept trailing bytes, unlike the state and command codecs | Informational | Solved - 05/19/2026 |
| Withdraw queue deserialization does not validate queue invariants or sorted pending-withdrawal IDs | Informational | Solved - 05/19/2026 |
| compose_policy_state silently constructs default values for missing storage parts | Informational | Solved - 05/19/2026 |
| cancel_migration() clears the migration flag but cannot roll back the already-swapped WASM | Informational | Solved - 05/19/2026 |
| Restrictions blob can exceed the Soroban entry-size limit when governance configures large blacklist or whitelist sets | Informational | Solved - 05/19/2026 |
| proxy_view returns max_withdraw and max_redeem for atomic operations the deployed ABI cannot perform | Informational | Solved - 05/22/2026 |
| proxy_view max_deposit and max_mint only check the same-side overflow | Informational | Solved - 05/19/2026 |
| prepare_atomic_call discards the EffectSummary returned by the conditional RefreshFees kernel action | Informational | Solved - 05/22/2026 |
| load_vault_bootstrap runs migrate_legacy_paused on every contract call | Informational | Solved - 05/19/2026 |
| Guardian configuration replaces the full guardian set silently | Informational | Acknowledged - 05/19/2026 |
| Share-token burn paths rely on vault invoker checks instead of owner authorization | Informational | Solved - 05/20/2026 |
| Constructors and governance config setters accept nonsensical role addresses | Informational | Solved - 05/19/2026 |
| Governance timelock legacy fallback can drop per-kind customizations | Informational | Acknowledged - 05/19/2026 |
| Auxiliary contract admin rotation is single-step and emits no event | Informational | Solved - 05/22/2026 |
| SetGovernance accepts the vault or arbitrary non-governance contracts | Informational | Solved - 05/19/2026 |
| Auxiliary contracts lack pause or freeze mechanisms | Informational | Solved - 05/20/2026 |
| Auxiliary contracts have no upgrade or migrate path | Informational | Solved - 05/22/2026 |
| Share-token metadata can be changed after deployment without timelock or event | Informational | Solved - 05/19/2026 |
| Vault restrictions do not propagate to share-token transfers | Informational | Solved - 05/20/2026 |
| Effect-address preflight can succeed for mappings that fail during execution | Informational | Acknowledged - 05/19/2026 |
| Adapter calls can consume the shared transaction instruction budget | Informational | Acknowledged - 05/22/2026 |
| Stale Blend reserve data can revert vault allocation and refresh flows | Informational | Acknowledged - 05/22/2026 |
| RefreshMarkets materializes all markets before caller authorization | Informational | Acknowledged - 05/19/2026 |
| Governance command bodies are decoded before authorization and kind validation | Informational | Solved - 05/19/2026 |
| Blend adapter admin cannot be rotated after deployment | Informational | Solved - 05/22/2026 |
| Governance admin cannot be rotated after deployment | Informational | Solved - 05/25/2026 |
| SetMembership governance decisions use incomplete current-state classification | Informational | Solved - 05/25/2026 |
| Blend adapter constructor accepts unvalidated vault pool asset and admin addresses | Informational | Solved - 05/19/2026 |
| Governance abdication symbols disable grouped submit methods rather than one action | Informational | Acknowledged - 05/19/2026 |
| SetMembership policy defaults missing market_id to market zero | Informational | Solved - 05/19/2026 |
| Governance constructor does not reject self-referential or colliding role addresses | Informational | Solved - 05/19/2026 |
| Newly attached governance starts with default local cache rather than vault state | Informational | Solved - 05/25/2026 |
| accept_kind is ambiguous when multiple proposals share the same action kind | Informational | Solved - 05/19/2026 |
| Vault dispatch helpers accept empty or duplicate input lists | Informational | Solved - 05/19/2026 |
| Storage decoders pre-allocate vectors from untrusted length prefixes | Informational | Solved - 05/22/2026 |
| Test suite uses permissive authorization paths that diverge from production RBAC | Informational | Solved - 05/19/2026 |
| Allocation lifecycle test helpers bypass production kernel action handling | Informational | Solved - 05/19/2026 |
| Vault uses StellarAssetClient for custom share-token interactions | Informational | Acknowledged - 05/22/2026 |
| Integration tests seed state outside the production onboarding flow | Informational | Solved - 05/19/2026 |
| Test allocation helper drops leased targets unlike production allocation | Informational | Acknowledged - 05/19/2026 |
| Property tests labeled external growth do not model real adapter growth | Informational | Solved - 05/19/2026 |
| Preview and fee tests duplicate production internals with different semantics | Informational | Solved - 05/19/2026 |
| Soroban kernel parity tests skip Soroban-specific paths and high-value ranges | Informational | Solved - 05/19/2026 |
| Governance MockVault ignores invalid or incomplete command payloads | Informational | Solved - 05/19/2026 |
| proxy_view casts u128 values to i128 without bounds checks | Informational | Solved - 05/19/2026 |
| Governance is granted full Curator role instead of the narrower policy-admin need | Informational | Solved - 05/25/2026 |
| Fee recipient configuration is positional and can be miswired by reordering | Informational | Solved - 05/22/2026 |
| Address text conversion can panic or silently fall back on malformed inputs | Informational | Acknowledged - 05/19/2026 |
| Fee preview saturates supply values without surfacing an overflow signal | Informational | Solved - 05/19/2026 |
| Test-only SorobanAuth creates misleading production authorization coverage | Informational | Solved - 05/19/2026 |
| Event payloads omit structured addresses and typed topics across vault components | Informational | Acknowledged - 05/22/2026 |
| Asset and share-token admin assumptions are enforced only by deployment discipline | Informational | Solved - 05/19/2026 |
| proxy_view fee snapshot omits the configured fee growth-rate cap | Informational | Solved - 05/19/2026 |
| Governance abdicate accepts unknown symbols as silent no-ops | Informational | Acknowledged - 05/19/2026 |
| Other effect approvals are unreachable from the deployed vault command surface | Informational | Acknowledged - 05/19/2026 |
| revoke_kind cancels all proposals of a kind while accept_kind accepts only one | Informational | Solved - 05/19/2026 |
| Vault command wire codec lacks versioning and collapses decoder error variants | Informational | Acknowledged - 05/22/2026 |
| Multiple wire encodings can represent the same governance policy action | Informational | Solved - 05/22/2026 |
| Governance direction classifiers inconsistently detect no-change proposals | Informational | Solved - 05/25/2026 |
| load_timelocks can mutate storage during view-style paths | Informational | Solved - 05/19/2026 |
| Share-token burn and burn_from emit indistinguishable burn events | Informational | Solved - 05/19/2026 |
| Share-token contract-address validation relies on StrKey prefix inspection | Informational | Solved - 05/19/2026 |
| In-flight governance proposals retain submit-time timelock after later timelock raises | Informational | Acknowledged - 05/19/2026 |
| Share-token archival recovery procedure is undefined | Informational | Solved - 05/19/2026 |
| Share-token tests omit admin rotation and allowance security coverage | Informational | Solved - 05/19/2026 |
| Share-token read-only methods do not refresh instance TTL | Informational | Solved - 05/19/2026 |
| Share-token constructor emits no deployment configuration event | Informational | Solved - 05/22/2026 |
| Share-token extend_ttl does not refresh holder balances or allowances | Informational | Solved - 05/19/2026 |
| AddressMap constructor accepts an unused environment parameter | Informational | Solved - 05/22/2026 |
| Immediate governance submissions write the pending queue twice | Informational | Solved - 05/19/2026 |
Halborn strongly recommends conducting a follow-up assessment of the project either within six months or immediately following any material changes to the codebase, whichever comes first. This approach is crucial for maintaining the project’s integrity and addressing potential vulnerabilities introduced by code modifications.
// Download the full report
Smart Contract Assessment
* Use Google Chrome for best results
** Check "Background Graphics" in the print settings if needed