Interfaces
Two interfaces define the boundaries that keep the Liquidity Hub layered. The Hub depends only on IYieldGroupBase; a YieldGroup depends only on IResourceAdapter. Neither the Hub nor a YieldGroup ever reaches past its boundary into the layer below.
IYieldGroupBase— the Hub ↔ YieldGroup boundary.IResourceAdapter— the YieldGroup ↔ adapter boundary.
The Hub's own external interface, IHub, is documented on the Hub page.
IYieldGroupBase
The boundary between the Hub and the concrete typed YieldGroups: the generic YieldGroup (which serves both the Core-vToken and Fluid-fToken deployments) and YieldGroupFRV. The Hub depends only on this interface; adding a new Source family never touches the Hub.
Two leaf interfaces extend it with family-specific surface:
IYieldGroup— the per-resource deposit cap setters andsetBlocksPerYear, implemented by the Core and Flux deployments.IYieldGroupFRV—forceRemoveResource, implemented by the FRV deployment.
There is no
ISourcetype in the code. Earlier drafts of this page used that name; the Solidity uses YieldGroup end to end.
Conventions
All amounts are denominated in
asset()units — there is no share conversion at this layer.deposit/withdraw/depositResource/withdrawResourceareonlyHub-gated (any other caller revertsNotHub) andnonReentrant.depositanddepositResourcepull frommsg.senderviasafeTransferFrom, so the Hub approves the YieldGroup immediately before calling.withdrawpushes the underlying to the suppliedtoaddress (today the Hub passesaddress(this)), leaving room for a future Hub optimization that routes straight to the end receiver without an interface revision.depositreturns the actually-placed amount, which may be less thanamountwhen the inner deposit queue cannot absorb it all. The implementer MUST refund the unplaced remainder to the Hub and return the placed amount; the Hub's cascade routes that remainder to the next YieldGroup rather than reverting. Fee-on-transfer underlyings are a separate matter — they are unsupported and fail closed inside the deposit.withdrawhas no return value: it MUST deliver exactlyamounttotoor revert. The Hub defensively measures the resulting balance delta rather than trusting a self-reported number.A Source may hold assets across multiple internal resources and route via its own inner queue; that detail is opaque to the Hub.
Mutating
deposit— moveamountfrom the caller into the Source's resources via the inner deposit queue.depositedmay be less thanamountwhenever the inner queue cannot absorb the full amount (per-resource caps, protocol supply caps, or paused / rejecting resources). The Source refunds the unplaced remainder to the Hub, which routes it onward; the Hub does not revert on a short return here.withdraw— pullamountvia the inner withdraw queue and send it toto; must deliver exactlyamountor revert (no partial fill).depositResource/withdrawResource— target one specific resource, bypassing the inner queue. They exist solely for Hub-orchestratedreallocate. The Hub treatsresourceas an opaque address it does not interpret; the Source validates it against its own registry. The fullamountmoves into / out of that one resource or the call reverts.withdrawResourcedoes not consume idle first and does not cascade; pulling from a paused resource is permitted (wind-down).accrue—onlyHub. The Hub calls it on every registered Source before reading NAV, so the management fee is charged on interest-current value. All three families override the base no-op: Core and Flux share theYieldGrouploop that callsIResourceAdapter.accrueper resource (real work only for Core), whileYieldGroupFRVpokes each vault'supdateVaultState(). The poke is best-effort per resource — one whose accrual reverts is skipped and reported viaResourceAccrualFailedrather than bubbling. A Source implementing only the four asset-moving functions would revert the Hub's fee accrual and therefore brick deposits and withdrawals.
Views
asset— the ERC-20 the Source accepts; must equalHub.asset().totalAssets— sum ofassetheld across all of the Source's resources; the Hub sums this over its registry.maxDeposit— aggregate remaining deposit capacity. The honesty contract requires thatdeposit(maxDeposit())does not revert.maxWithdraw— aggregate currently-withdrawable amount.withdraw(maxWithdraw(), to)must not revert (subject to upstream liquidity).spotAPYBps— TVL-weighted spot supply-side APY across the Source's resources, in BPS. This is an instantaneous reading; the Source layer has no notion of historical realization, and the Hub does not currently aggregate or smooth it.
IResourceAdapter
The boundary between a YieldGroup and a specific yield-protocol ABI. A single deployment of each implementation is shared across every YieldGroup that registers a resource of the matching family.
Dispatch model
The asset-moving mutating functions (
deposit,withdraw) MUST be invoked viadelegatecallfrom the YieldGroup, so they execute in the YieldGroup's storage context and receipt-token credits / debits land on the YieldGroup. Implementations enforce this with anaddress(this) != _ADAPTER_SELFguard.accrueis invoked via normalcall, notdelegatecall, and deliberately carries no guard: it settles the resource's own global interest state rather than the holder's position, so it needs neither the YieldGroup's storage context nor a delegatecall.View functions are invoked via normal
call/staticcalland take an explicitholderparameter where the answer depends on whose position is queried.
Implementation invariants
Verified at audit:
The contract declares zero storage variables; only
immutableandconstantvalues are permitted (both live in bytecode, not storage slots).No inline assembly performs
sstore.External calls go only to the supplied
resourceand its trusted, chain-fixed dependencies — the resource'sunderlying()/asset()/comptroller()and (for Flux) the FluidLendingResolver— never an arbitrary user-supplied address.The delegatecall-dispatched mutating functions (
deposit,withdraw) revert when called outside a delegatecall context.accrueis the deliberate exception — it is invoked by plaincalland has no such guard.
Mutating
deposit— delegatecalled; the YieldGroup already holdsamountof underlying. Post-conditions: receipt tokens are credited to the YieldGroup, and the fullamountof underlying leaves the YieldGroup intoresource. Because the code runs in the YieldGroup's storage context, the adapter contract itself never custodies funds.withdraw— delegatecalled; redeems at leastamountand transfers exactlyamounttoto. Any surplus stays as idle on the YieldGroup, counted intotalAssetsand consumed idle-first on the next withdrawal.AdapterCoreV1produces surplus from two sources: the sub-one-vToken dust bump, and — only if Venus governance later enables it — the ComptrollertreasuryPercentgross-up.accrue— invoked via normalcall, NOT delegatecall; settlesresource's own global interest state so a followingtotalAssetsread prices the position at a fresh rate. Only block-lazy adapters do real work:AdapterCoreV1calls the vToken'saccrueInterest(), whileAdapterFluxandAdapterFRVare no-ops.
Views
asset— the underlying accepted byresource; the YieldGroup uses it to validate a resource matches its own asset at registration.totalAssets— underlying valueholderholds viaresource, valued at realizable terms. The basis is per-adapter:AdapterCoreV1uses the stale (non-accruing)exchangeRateStored, net of any ComptrollertreasuryPercent;AdapterFluxuses the fToken's livepreviewRedeem;AdapterFRVuses a time-based linear accrual (principal plus the coupon accrued so far over the lock). Cheap and safe for Hub-level aggregation.maxDeposit— spare deposit headroom onresourceright now (before its own caps or pause reject a mint).maxWithdraw— underlyingholdercan withdraw right now, net of any redeem-time protocol fee, bounded by the lesser of the position value and the resource's available cash.spotAPYBps— spot supply-side APY in BPS; annualization uses theblocksPerYearthe YieldGroup passes (chain-dependent).receiptBalance— raw receipt-token balance (vToken / fToken / FRV shares), NOT underlying value. Used byremoveResourceas a share-based emptiness gate so a value-based check can't round a small balance to zero and orphan tokens.validateRegistration— reverts ifresourcefails a protocol-specific precondition.AdapterCoreV1rejects a vToken whose Comptroller charges a non-zerotreasuryPercent;AdapterFluxandAdapterFRVimplement it as a no-op.
Last updated

