Crypto Training

Kiln DeFi: Architecture, User Flows, and Security Boundaries

A code-level breakdown of Kiln DeFi vault architecture, connector model, fund flows, user deposit and withdrawal journeys, role/governance controls, and practical risk analysis.

Crypto Training2025-10-0419 min read

Kiln DeFi is a modular ERC-4626 vault system designed to route a single vault asset into external yield protocols through protocol-specific connectors. The core architecture is simple in interface and explicit in control boundaries: users interact with a single vault, while governance and operations control the connector registry, fees, sanctions controls, and emergency posture.

This post is a code-first architecture analysis based on:

What Kiln DeFi is trying to optimize#

At a product level, Kiln DeFi is optimizing for:

  • single-asset user UX: users hold one vault share token and do not manage protocol-specific actions
  • connector modularity: Aave, Compound, Morpho/MetaMorpho, sDAI, sUSDS, Angle savings are accessed through swappable connector contracts
  • operational controls: fine-grained roles, connector pause/freeze, vault deposit pause, sanctions/blocklist checks
  • fee extraction without strategy-specific share classes: deposit fee and reward fee are handled by dedicated accounting paths

The architecture favors operational simplicity over maximal decentralization. The trust model is not "set and forget". It is "governed vault with explicit operator powers and emergency levers".

Contract architecture map#

Core modules#

  • Vault/src/Vault.sol: user-facing ERC-4626 + accounting + role-gated admin actions
  • VaultFactory/src/VaultFactory.sol: beacon-proxy deployment and upgrade orchestrator
  • ConnectorRegistry/src/ConnectorRegistry.sol: connector catalog with pause/freeze mechanics
  • FeeDispatcher/src/FeeDispatcher.sol: fee accrual splitting and dispatch
  • ExternalAccessControl/src/ExternalAccessControl.sol: external role authority used by vault transfer controls
  • BlockList/src/BlockList.sol: internal blocklist + underlying sanctions list adapter

Connector implementations#

  • AaveV3Connector/src/connectors/AaveV3Connector.sol
  • CompoundV3Connector/src/connectors/CompoundV3Connector.sol
  • MetamorphoConnector/src/connectors/MetamorphoConnector.sol
  • SDAIConnector/src/connectors/SDAIConnector.sol
  • SUSDSConnector/src/connectors/SUSDSConnector.sol
  • AngleSavingConnector/src/connectors/AngleSavingConnector.sol

Primary code references used in this analysis#

  • Vault/src/Vault.sol (deposit, mint, withdraw, redeem, claimAdditionalRewards, forceWithdraw, fee and pause controls)
  • VaultFactory/src/VaultFactory.sol (createVault, upgradeVault)
  • ConnectorRegistry/src/ConnectorRegistry.sol (getOrRevert, pause, pauseFor, freeze)
  • FeeDispatcher/src/FeeDispatcher.sol (dispatchFees, pending fee accounting, recipient split validation)
  • BlockList/src/BlockList.sol (isBlocked, internal and external sanctions list composition)
  • ExternalAccessControl/src/ExternalAccessControl.sol (external role authority for transfer restrictions)
  • connector implementations under each */src/connectors/*.sol
flowchart TB U["User"] --> V["Vault ERC-4626"] V --> CR["ConnectorRegistry"] V --> FD["FeeDispatcher"] V --> BL["BlockList"] V --> EAC["ExternalAccessControl"] VF["VaultFactory"] --> V CR --> C1["AaveV3Connector"] CR --> C2["CompoundV3Connector"] CR --> C3["MetamorphoConnector"] CR --> C4["SDAIConnector"] CR --> C5["SUSDSConnector"] CR --> C6["AngleSavingConnector"] C1 --> P1["Aave V3"] C2 --> P2["Compound V3"] C3 --> P3["MetaMorpho Vaults"] C4 --> P4["Maker sDAI"] C5 --> P5["sUSDS"] C6 --> P6["Angle Savings"]

Deployment surface and known addresses#

From the provided address set and Sourcify metadata resolution, the following contracts are directly identified:

AddressResolved contract
0xdE63817c82e93499357aE198518f90Ac1bE93A72ConnectorRegistry
0x4A1Ede66750e8e44a1569A4Af3F53fb31De3Dd32VaultFactory
0x533DD3A719968Dba0cf454C2B2a692d196DF3605ExternalAccessControl
0x637F9D0E032EFb98fe8Ae55C6D798FD54060Be04FeeDispatcher
0x0d87F2834b4766CAf25aD5dBE193BEd70f5D9458BlockListFactory
0xB58700939159Db7a47b64FF74cF98150AccBF904BlockListUpgradeableBeacon
0x08c28e1c82C09487DCB15a3e0839e8C888EeE3CDAaveV3Connector
0xbeaa30DCB697CFFB64E319A3Fc4b0688Be5aE790CompoundV3Connector
0x22Fc700401FABbB7de1872461E8733d74e02f88aSDAIConnector
0xDa5FfFCF097A95E0aE6e6eC9b966da5ba89844f2MetamorphoConnector
0x3443Ea9BcC9E1E515e567a278bDae103e7324d1dAngleSavingConnector
0xe68c8E20C4E469800A13ABeBF0Dfd094CC2C4DE2SUSDSConnector

Some addresses in the list are not fully resolved via Sourcify full/partial metadata and likely correspond to implementation variants, proxy instances, or additional deployment artifacts in the same system.

Why the connector model matters#

Vault.sol delegates actual protocol interaction via functionDelegateCall to connector code fetched from ConnectorRegistry.getOrRevert(connectorName). This gives Kiln a flexible strategy endpoint with one critical implication: connector trust is system trust.

If connector logic is wrong, the vault is wrong. If registry governance changes connector pointers, vault behavior can change without changing vault bytecode.

sequenceDiagram autonumber participant User participant Vault participant Registry as ConnectorRegistry participant Connector participant Protocol User->>Vault: deposit(assets, receiver) Vault->>Registry: getOrRevert(connectorName) Registry-->>Vault: connector address Vault->>Connector: delegatecall deposit(asset, amount) Connector->>Protocol: supply/deposit Protocol-->>Connector: position updated Connector-->>Vault: return Vault-->>User: shares minted

User stories and real usage modes#

Story 1: Retail user passively farming a selected strategy#

  • User deposits a base asset into a specific Kiln vault.
  • User receives ERC-4626 shares immediately.
  • User can redeem later, subject to actual protocol liquidity available through the connector.

Story 2: Treasury using controlled transferability mode#

  • Vault transferability can be disabled.
  • Only entities with SPENDER_ROLE in ExternalAccessControl can move shares between parties.
  • This turns shares into policy-managed claims rather than free-float tokens.

Story 3: Managed reward operations#

  • Claim manager selects AdditionalRewardsStrategy (None, Claim, Reinvest).
  • For claim/reinvest connectors that support incentive flows, rewards can be harvested and either sent out or reinvested.

Story 4: Sanctions and forced offboarding#

  • Internal blocklist + sanctions source can block accounts.
  • For internally blocked users, forceWithdraw path exists (with liquidity constraints and sanctions-specific checks).
flowchart LR U1["Passive User"] --> V U2["Treasury User"] --> V Ops["Claim Manager"] --> V San["Sanctions Manager"] --> V V -->|shares| U1 V -->|restricted transfer checks| EAC["ExternalAccessControl"] V -->|block checks| BL["BlockList"] V -->|yield routing| CR["ConnectorRegistry"]

Deposit flow from the user perspective#

From UX perspective, deposit is one transaction. Under the hood, several accounting steps occur.

Step-by-step#

  1. User calls deposit(assets, receiver) or mint(shares, receiver).
  2. Vault accrues reward fee first (_accrueRewardFee) so share math uses updated total assets baseline.
  3. Deposit fee is computed in asset terms.
  4. Shares are minted to receiver based on net assets after deposit fee.
  5. Vault delegatecalls connector deposit for net amount.
  6. Deposit fee is booked as pending in FeeDispatcher.
sequenceDiagram autonumber participant User participant Vault participant FeeDispatcher participant Registry as ConnectorRegistry participant Connector participant Protocol User->>Vault: deposit(assets, receiver) Vault->>Vault: _accrueRewardFee() Vault->>Vault: _previewDeposit() Vault->>Vault: mint shares to receiver Vault->>Registry: getOrRevert(connectorName) Registry-->>Vault: connector Vault->>Connector: delegatecall deposit(netAssets) Connector->>Protocol: supply/deposit Protocol-->>Connector: success Vault->>FeeDispatcher: incrementPendingDepositFee(depositFeeAmount) Vault-->>User: Deposit complete

Important detail#

Deposit success does not mean all protocol-side risk vanished. It only means assets are now exposed to the selected connector/protocol stack. User principal is now coupled to:

  • connector correctness
  • protocol solvency/liquidity behavior
  • governance response if incidents occur

Withdrawal flow from the user perspective#

Withdrawal and redeem are synchronous calls, but they are bounded by connector-reported liquidity.

Step-by-step#

  1. User calls withdraw(assets, receiver, owner) or redeem(shares, receiver, owner).
  2. Vault checks max withdrawable amount based on owner position and connector liquidity.
  3. Shares are burned.
  4. Vault delegatecalls connector withdraw(asset, amount).
  5. Connector unwinds funds from protocol and returns assets to vault context.
  6. Vault transfers realized amount to receiver.
sequenceDiagram autonumber participant User participant Vault participant Registry as ConnectorRegistry participant Connector participant Protocol User->>Vault: redeem(shares, receiver, owner) Vault->>Vault: _accrueRewardFee() Vault->>Vault: check maxWithdraw(owner) Vault->>Vault: burn shares Vault->>Registry: getOrRevert(connectorName) Registry-->>Vault: connector Vault->>Connector: delegatecall withdraw(asset, amount) Connector->>Protocol: unwind/withdraw Protocol-->>Connector: funds returned Connector-->>Vault: return Vault-->>User: transfer assets

Where withdrawals can fail for users#

  • connector paused/frozen via registry
  • insufficient protocol liquidity
  • external protocol paused/degraded
  • blocklist/sanctions restrictions on caller/receiver
flowchart TB R["User redeem"] --> L{"Protocol liquidity available?"} L -- "Yes" --> W["Connector withdraw succeeds"] W --> T["User receives assets"] L -- "No" --> F["Revert InsufficientLiquidity or reduced maxWithdraw"] R --> P{"Connector paused?"} P -- "Yes" --> X["Revert via registry pause checks"] R --> B{"User blocked?"} B -- "Yes" --> Y["Revert AddressBlocked"]

Fee model and economics#

Kiln vault has two core fee channels:

  • deposit fee: charged on incoming assets
  • reward fee: charged on accrued yield (implemented via fee shares logic then collected)

Both are capped (_MAX_FEE = 35, scaled against asset decimals and _MAX_PERCENT) and routed through FeeDispatcher.

FeeDispatcher keeps pending fee balances and then dispatchFees(asset, decimals) transfers fees according to configured split recipients, requiring split sum consistency.

flowchart LR D["User deposit"] --> DF["Deposit fee booked"] Y["Yield accrual"] --> RF["Reward fee booked"] DF --> PD["pendingDepositFee"] RF --> PR["pendingRewardFee"] PD --> DISP["dispatchFees()"] PR --> DISP DISP --> R1["Recipient A"] DISP --> R2["Recipient B"] DISP --> R3["Recipient N"]

Additional rewards: claim vs reinvest#

Vault.claimAdditionalRewards(rewardsAsset, payload) delegates to connector logic and applies strategy mode.

  • Claim: collect reward token amount and emit RewardsClaimed
  • Reinvest: perform connector-defined claim/swap/redeposit path
  • None: no additional rewards operation

This path can introduce a separate operational risk because payload often includes external call data for swap routes.

sequenceDiagram autonumber participant ClaimManager participant Vault participant Connector participant RewardSystem participant SwapOrProtocol ClaimManager->>Vault: setAdditionalRewardsStrategy(Claim/Reinvest) ClaimManager->>Vault: claimAdditionalRewards(rewardAsset, payload) Vault->>Connector: delegatecall claim(..., payload) Connector->>RewardSystem: claim rewards alt Claim strategy RewardSystem-->>Vault: reward tokens else Reinvest strategy Connector->>SwapOrProtocol: swap/deposit via payload SwapOrProtocol-->>Vault: increased base position end

Access control and governance model#

The system uses role-based access heavily. The practical question is not whether roles exist, but who controls them and under what delay/multisig policy.

Key role surfaces in Vault#

  • FEE_MANAGER_ROLE: set fee recipients, deposit fee, reward fee
  • FEE_COLLECTOR_ROLE: collect reward fees
  • SANCTIONS_MANAGER_ROLE: blocklist-related control paths
  • CLAIM_MANAGER_ROLE: additional rewards strategy operations
  • PAUSER_ROLE / UNPAUSER_ROLE: deposit pause toggles

SPENDER_ROLE is checked on the external ACL contract when transferability mode is restricted.

flowchart TB Admin["Default Admin"] --> FM["FEE_MANAGER_ROLE"] Admin --> FC["FEE_COLLECTOR_ROLE"] Admin --> CM["CLAIM_MANAGER_ROLE"] Admin --> SM["SANCTIONS_MANAGER_ROLE"] Admin --> P["PAUSER_ROLE"] Admin --> UP["UNPAUSER_ROLE"] FM --> A1["setDepositFee"] FM --> A2["setRewardFee"] FM --> A3["setFeeRecipients"] FC --> A4["collectRewardFees"] CM --> A5["setAdditionalRewardsStrategy"] CM --> A6["claimAdditionalRewards"] P --> A7["pauseDeposit"] UP --> A8["unpauseDeposit"]

Sanctions and forced withdrawal controls#

BlockList combines:

  • internal list management
  • underlying sanctions list adapter (ISanctionsList)

Vault.notBlocked modifiers gate user actions. forceWithdraw is designed for internal sanctions offboarding and checks full-liquidity constraints before execution.

flowchart LR UserAction["deposit/withdraw/redeem/transfer path"] --> Check["BlockList.isBlocked(addr)"] Check --> I1["Internal list"] Check --> I2["Underlying sanctions list"] I1 --> Decision I2 --> Decision Decision{"Blocked?"} Decision -- "Yes" --> Revert["AddressBlocked"] Decision -- "No" --> Continue["Action proceeds"]

Deployment and upgrade lifecycle#

VaultFactory deploys vault beacon proxies and can perform upgrade migration steps, including fee dispatcher compatibility handling from archived versions.

sequenceDiagram autonumber participant Deployer participant Factory as VaultFactory participant Beacon as VaultBeaconProxy participant Vault Deployer->>Factory: createVault(params, salt) Factory->>Beacon: deploy proxy via CREATE2 Beacon->>Vault: initialize(initializationParams, upgradeParams) Vault-->>Factory: initialized Deployer->>Factory: upgradeVault(vault, upgradeParams) Factory->>Vault: delegateToFactory(get old fee state) Factory->>Vault: upgrade(new fee/connector params)

Security analysis: what can go wrong#

1) Connector trust and delegatecall blast radius#

Connector logic executes in vault context through delegatecall. Any connector bug is effectively a vault bug. Registry governance controls connector addresses, so registry compromise is equivalent to strategy logic compromise.

2) Reward payload misuse risk#

Connector claim flows can process externally supplied payloads. If payload validation assumptions fail, value extraction or bad route execution risks increase.

3) Liquidity illusion risk#

maxWithdraw depends on connector/protocol conditions that can degrade quickly. Synchronous withdraw UX can still be operationally constrained by protocol-side liquidity.

4) Governance centralization risk#

Pause, fee updates, sanctions toggles, and connector management are role-gated. Without robust multisig/timelock procedures, operator key compromise can cause severe user impact.

5) Sanctions path complexity#

Internal and external blocklist composition can create edge cases in forced exits and transfer restrictions, especially under rapid sanctions list changes.

6) Upgrade and migration risk#

Factory upgrade flows are powerful. Migration logic touching fee accounting and recipient splits must be treated as high-sensitivity changes with strict test and review coverage.

flowchart TB R1["Connector bug"] --> Impact["User funds at risk"] R2["Registry compromise"] --> Impact R3["Payload misuse in reward claim"] --> Impact R4["Protocol liquidity crunch"] --> UserPain["Redeem failures or delays"] R5["Role key compromise"] --> Impact R6["Faulty upgrade/migration"] --> Impact

Connector-by-connector behavior and design tradeoffs#

The connector layer is where Kiln becomes opinionated about each external protocol. The vault interface stays constant, but each connector decides:

  • what maxDeposit means for that protocol
  • what maxWithdraw means under current liquidity
  • whether incentives can be claimed
  • whether incentives can be reinvested

That means two vaults with the same ERC-4626 interface can have very different runtime behavior depending on connector choice.

Aave V3 connector#

AaveV3Connector routes deposits through Aave Pool supply and withdraws through Pool withdraw. It derives deposit limits from reserve cap/usage and derives withdraw limits from aToken liquidity constraints.

It also supports reward claiming and reinvest-style pathways with payload-driven logic, which is useful operationally but expands payload validation responsibility.

Compound V3 connector#

CompoundV3Connector integrates through Comet supply/withdraw and reward controller claim flow. Market selection relies on registry lookup for the asset-to-market mapping.

Operationally this is similar to Aave connector shape, but risk posture differs because Comet market and reward plumbing are distinct systems.

MetaMorpho connector#

MetamorphoConnector is effectively ERC-4626 passthrough behavior into a MetaMorpho vault for the configured asset. It intentionally does not implement extra reward claim/reinvest beyond core vault share value accrual.

This is cleaner from a call-surface perspective: fewer payload-heavy functions, but less flexibility for active rewards operations.

sDAI, sUSDS, Angle connectors#

These connectors mostly expose straightforward deposit/withdraw wrappers around ERC-4626-like savings constructs. Reward claim/reinvest is not the center of their model in this code path.

Angle connector includes paused-state awareness in max limits, which is a practical detail that prevents optimistic previews when protocol-level operations are paused.

flowchart LR V["Vault"] V --> A["AaveV3Connector"] V --> C["CompoundV3Connector"] V --> M["MetamorphoConnector"] V --> D["SDAIConnector"] V --> S["SUSDSConnector"] V --> G["AngleSavingConnector"] A --> A1["supply/withdraw + rewards"] C --> C1["Comet supply/withdraw + rewards"] M --> M1["ERC-4626 style vault route"] D --> D1["sDAI ERC-4626 route"] S --> S1["sUSDS route"] G --> G1["Angle savings route + pause check"]

Accounting invariants that should hold#

A robust user-facing vault is not only about successful calls. It is about preserving accounting invariants under stress.

Invariant family 1: share mint/burn coherence#

  • shares minted on deposit should match net assets after deposit fee under current conversion assumptions
  • shares burned on redeem should not exceed owner entitlement
  • fee share minting should not silently dilute beyond configured fee logic

Invariant family 2: fee buckets are conservative#

  • pending fee counters should only increase from authorized paths
  • dispatch should never exceed pending balances
  • recipient splits should sum correctly and avoid dust leakage from misconfigured scaling

Invariant family 3: total assets monotonicity assumptions are explicit#

During normal operation, total assets may increase with yield and decrease during withdrawals. But internally, fee accrual and conversion paths must not create accounting states where user-previewed conversions are structurally impossible to fulfill.

Invariant family 4: pause/freeze semantics are consistent#

  • connector pause should block runtime connector retrieval for operations that need it
  • vault deposit pause should reliably block deposit/mint paths
  • pausing should not create hidden bypasses through alternate user entry points
stateDiagram-v2 [*] --> Active Active --> DepositPaused: pauseDeposit() DepositPaused --> Active: unpauseDeposit() Active --> ConnectorPaused: registry.pause(name) ConnectorPaused --> Active: registry.unpause(name) Active --> FrozenConnector: registry.freeze(name) ConnectorPaused --> FrozenConnector: registry.freeze(name) FrozenConnector --> FrozenConnector: immutable from registry perspective

A complete user journey with realistic edge conditions#

Journey A: successful lifecycle#

  1. User evaluates vault and connector choice.
  2. User deposits assets and receives shares.
  3. Over time, rewards accrue and may be harvested/reinvested by ops.
  4. User redeems shares and receives base asset.

This is the ideal path, and for most users this is what matters.

Journey B: stress lifecycle#

  1. User deposits successfully.
  2. External protocol enters partial liquidity crunch.
  3. maxWithdraw falls below user expectation.
  4. User redeem reverts or can only redeem smaller chunks over time.
  5. Governance may pause deposits and adjust operations while liquidity normalizes.

The key takeaway: ERC-4626 UX can still be operationally constrained by external liquidity states.

Journey C: policy-enforced lifecycle#

  1. User shares are held in non-transferable mode environment.
  2. Transfer attempt fails unless sender or target has external SPENDER_ROLE.
  3. If internal sanctions logic flags account, normal interactions fail by design.

This is not a bug. It is policy posture.

sequenceDiagram autonumber participant User participant Vault participant EAC as ExternalAccessControl participant BL as BlockList participant Connector participant Protocol User->>Vault: deposit() Vault->>BL: isBlocked(user)? BL-->>Vault: false Vault->>Connector: delegatecall deposit() Connector->>Protocol: deposit Protocol-->>Connector: ok Vault-->>User: shares minted User->>Vault: transfer shares to another account Vault->>EAC: hasRole(SPENDER_ROLE, sender or target)? alt role not present and non-transferable mode Vault-->>User: revert NotTransferable else role present Vault-->>User: transfer succeeds end

Where Kiln DeFi differs from async queue vault designs#

Kiln DeFi here is primarily synchronous ERC-4626 interaction with protocol routing behind connectors. That is structurally different from async request/settlement queue systems.

Synchronous model strengths#

  • simple user mental model
  • immediate mint/redeem semantics when liquidity allows
  • thinner state machine around requests

Synchronous model weaknesses#

  • more direct dependence on real-time protocol liquidity
  • less natural buffering for batched settlement under stress
  • potential UX volatility during rapid market dislocations

Async model strengths (in general)#

  • explicit request queueing during stress
  • deterministic settlement windows
  • reduced immediate liquidity pressure on each user call

Async model weaknesses (in general)#

  • more moving parts and request accounting complexity
  • delayed user finality by design
  • more operator/reporting dependencies
flowchart TB subgraph Sync["Kiln-style sync user path"] U1["User call"] --> X1["Vault checks + connector call"] X1 --> X2["Immediate success or revert"] end subgraph Async["Queue-style async path"] U2["User request"] --> Q1["Queue state"] Q1 --> Q2["Batch settlement window"] Q2 --> Q3["Claim step"] end

Incident response design: what operators should predefine#

The contracts provide controls, but controls without runbooks are not enough. A production deployment should have explicit incident response runbooks.

Runbook 1: connector-level incident#

  • trigger: connector bug or abnormal protocol behavior
  • immediate action: pause connector in registry
  • secondary action: pause deposits at vault level if needed
  • communication action: publish user status and expected timeline
  • recovery action: move to alternate connector only after code review + simulation

Runbook 2: protocol liquidity drought#

  • trigger: withdraw slippage/revert patterns or liquidity metrics breach
  • immediate action: monitor maxWithdraw drift and publish guidance
  • optional action: temporary deposit pause to prevent one-sided flow
  • recovery action: staged operations once liquidity normalizes

Runbook 3: compromised operational key#

  • trigger: suspicious role action or key compromise evidence
  • immediate action: revoke role grants in admin flow
  • secondary action: freeze sensitive connectors
  • recovery action: rotate keys, perform postmortem, then re-enable operations
sequenceDiagram autonumber participant Monitor participant OpsMultisig participant Registry participant Vault participant Users Monitor->>OpsMultisig: alert abnormal connector behavior OpsMultisig->>Registry: pause(connectorName) OpsMultisig->>Vault: pauseDeposit() OpsMultisig-->>Users: publish incident status Note over OpsMultisig,Users: investigation and remediation window OpsMultisig->>Registry: unpause or migrate connector after validation OpsMultisig->>Vault: unpauseDeposit() OpsMultisig-->>Users: publish resolution

Deep dive on fee fairness from user perspective#

From a user perspective, fee fairness means:

  • can I predict effective entry price after deposit fee?
  • can I understand ongoing reward fee extraction impact?
  • are recipients and splits transparent and stable?

The fee model is legible because deposit and reward fees are separated and tracked in dispatcher state. But governance still controls parameters. So transparency and announcement policy matters as much as raw code.

Practical fairness checklist#

  • disclose fee values with decimals scaling interpretation
  • disclose split recipients and rationale
  • disclose admin delay and procedure for fee changes
  • monitor pending fee growth against vault growth
flowchart LR A["User deposits 100 units"] --> B["Deposit fee applied"] B --> C["Net assets converted to shares"] C --> D["Vault position grows with yield"] D --> E["Reward fee accrual path"] E --> F["FeeDispatcher pending buckets"] F --> G["dispatchFees transfers to recipients"]

Security test priorities for auditors and protocol engineers#

If auditing or extending this architecture, prioritize tests around the highest-blast-radius paths first.

Priority 1: delegatecall connector safety#

  • malicious connector simulation
  • connector upgrade/pointer swap abuse
  • storage collision assumptions if connector code changes drastically

Priority 2: conversion edge-case fuzzing#

  • low supply and low assets edge cases
  • fee near cap behavior under tiny deposits
  • rounding paths for preview vs execution parity

Priority 3: role bypass attempts#

  • unauthorized caller paths for all admin functions
  • transfer restrictions with and without external spender role
  • sanctions-related bypass attempts across all user entry points

Priority 4: rewards payload abuse#

  • malformed payloads
  • payloads that target unintended call graphs
  • gas griefing and reentrancy-adjacent scenarios in claim/reinvest flows
flowchart TB T1["Connector delegatecall tests"] --> S["Security confidence"] T2["Accounting fuzz tests"] --> S T3["Role and sanctions access tests"] --> S T4["Rewards payload adversarial tests"] --> S

Operational metrics worth tracking in production#

A mature deployment should track runtime metrics continuously, not only after incidents.

Core metrics#

  • vault TVL by connector and by protocol exposure
  • deposit and redeem success rates
  • average redeem gas and failure reasons
  • maxWithdraw trend vs total user position
  • pending fee balances and dispatch cadence
  • role grant/revoke events
  • connector pause/freeze events

Risk metrics#

  • concentration per external protocol
  • liquidity-at-risk under stressed withdrawal assumptions
  • claim/reinvest operation failure rates
  • sanctions/blocklist event frequency and impact scope
flowchart LR ChainEvents["On-chain events"] --> Indexer["Indexer/analytics"] Indexer --> OpsDash["Ops dashboard"] OpsDash --> Alerts["Real-time alerts"] Alerts --> Multisig["Operator multisig actions"] Multisig --> ChainEvents

Practical threat model by actor#

ActorCapabilityWhat users trust them for
Vault governance/adminrole grants, fee config, pause/unpausefair operation, non-abusive fee and policy changes
Connector managerconnector add/pause/freezesafe connector bytecode and incident response
Claim managerreward strategy and execution payloadsno value-destructive claim/reinvest behavior
Sanctions managerblocklist and sanctions list updateslawful and consistent offboarding behavior
External protocol governanceAave/Compound/Morpho/etc behaviorsolvency, liquidity, oracle safety

User-facing deposit and withdrawal UX summary#

What users see#

  • one vault token
  • deposit, mint, withdraw, redeem standard ERC-4626 style methods
  • fees reflected in shares/assets conversion

What users should understand before using#

  • this is not passive immutable logic
  • connector and governance controls are active moving parts
  • liquidity and reward behavior depend on external protocol conditions
  • sanctions and transferability settings can alter account behavior materially

What is strong in this design#

  • clear module boundaries
  • explicit and auditable role gates
  • broad connector portability for same ERC-4626 vault surface
  • operational controls for pause/freeze/blocklist scenarios

What deserves extra caution in production#

  • delegatecall connector architecture requires strict connector QA and governance hardening
  • reward payload surfaces should be tightly constrained and monitored
  • emergency and sanctions paths need runbooks and regression tests
  • users should receive clear documentation of who controls each role on each deployed vault

Engineering checklist for integrators#

If you integrate Kiln DeFi vaults in wallet/UI/backend stacks:

  1. Verify connector name and connector contract for each vault before depositing.
  2. Track role holders and admin delay settings, not just vault TVL/APY.
  3. Monitor registry pause/freeze events and vault deposit pause state.
  4. Track pending fee dynamics and recipient changes over time.
  5. Simulate redeem under stressed liquidity conditions, not only happy-path previews.
  6. Alert on claim strategy changes and reward payload execution anomalies.

Final view#

Kiln DeFi is a strong example of an operationally managed ERC-4626 platform architecture. It is simple for end users at the interface level and complex where it matters: connector routing, governance controls, and risk operations.

That tradeoff can work well in production if governance discipline is high and connector quality is continuously audited. If governance discipline degrades, the same flexibility that enables protocol agility can become the dominant risk factor.

References#