For the complete documentation index, see llms.txt. This page is also available as Markdown.

Contract & Interface Reference

Moolah is the singleton that holds every Lista Lending market. Call it directly from Solidity, or from any stack that cannot use the SDK; the signatures, structs and Lista-specific controls below are what you need to do that.

Moolah is powered by Morpho and built on the Morpho Blue smart contracts, then extended with Lista-specific controls. For the layers around it: the ERC-4626 vault is in Vault Reference, provider-gated collaterals in Providers, and the fixed-term broker surface in Broker Reference. Every market — regardless of collateral, oracle, or IRM — lives inside this one contract and is addressed by a market Id. For deployed addresses, see the Smart Contract reference; For higher-level flows see Integration Patterns; for the callback interfaces and emitted events see Events & Callbacks.


Types & structs

Id

type Id is bytes32;

A market's identifier is the keccak256 hash of the ABI-encoded MarketParams (five 32-byte words, in struct order). Two markets with identical parameters therefore collapse to the same Id.

Id id = Id.wrap(keccak256(abi.encode(marketParams)));

The same value is exposed on-chain via the idToMarketParams(Id) view (the reverse mapping) and is used as the key for position, market, providers, brokers, and both whitelists.

MarketParams

The immutable definition of a market. Once created, these values cannot change; a new combination is simply a new market.

Field
Type
Meaning

loanToken

address

The asset supplied and borrowed.

collateralToken

address

The asset posted as collateral.

oracle

address

Price source; must expose peek(address). Every oracle Lista deploys on the IOracle path except IdleOracle (which returns a literal 0 for its idle collateral) returns an 8-decimal price — the legacy CDP's own peek() surface is 18-decimal, but that's a separate, unrelated contract. Moolah itself never validates the decimals convention either way: market creation merely probes that peek does not revert — see Consuming Oracle Prices.

irm

address

Interest Rate Model. Must be enabled via isIrmEnabled.

lltv

uint256

Liquidation Loan-To-Value, scaled by WAD (1e18). Must be enabled via isLltvEnabled.

Position

Per-user, per-market state: mapping(Id => mapping(address => Position)).

Field
Type
Meaning

supplyShares

uint256

Supply-side shares owned by the user.

borrowShares

uint128

Borrow-side (debt) shares owed by the user.

collateral

uint128

Collateral balance posted by the user.

For feeRecipient, supplyShares does not include fee shares accrued since the last interest accrual.

Market

Aggregate per-market accounting: mapping(Id => Market).

Field
Type
Meaning

totalSupplyAssets

uint128

Total supplied assets (excl. interest since last accrual).

totalSupplyShares

uint128

Total supply shares outstanding.

totalBorrowAssets

uint128

Total borrowed assets (excl. interest since last accrual).

totalBorrowShares

uint128

Total borrow shares outstanding.

lastUpdate

uint128

Timestamp of the last interest accrual. A non-zero value means the market exists.

fee

uint128

Market fee on interest, scaled by WAD. Capped at MAX_FEE.

Shares use OpenZeppelin's virtual-shares method (VIRTUAL_SHARES = 1e6, VIRTUAL_ASSETS = 1) to mitigate share-price manipulation; conversions round in the protocol's favor.

Authorization & Signature

Used by setAuthorizationWithSig for gasless (EIP-712) authorization delegation.

Authorization field

Type

Meaning

authorizer

address

The account granting authorization (and the recovered signer).

authorized

address

The account being authorized to manage authorizer's positions.

isAuthorized

bool

The authorization value to set.

nonce

uint256

Must equal authorizer's current nonce; prevents replay.

deadline

uint256

Signature expiry timestamp.

Signature is a standard { uint8 v; bytes32 r; bytes32 s; }. The EIP-712 domain is EIP712Domain(uint256 chainId,address verifyingContract); the type hash is Authorization(address authorizer,address authorized,bool isAuthorized,uint256 nonce,uint256 deadline). The current chain's separator is readable via domainSeparator().


Core external functions

Where a function takes both assets and shares, exactly one must be zero (enforced by exactlyOneZero); the other side is derived by the shares math.

Market creation

Creates a market. Reverts unless irm and lltv are enabled, loanToken/collateralToken/oracle are non-zero, and the market does not already exist. Records lastUpdate = block.timestamp, sets the market fee to defaultMarketFee, stores the reverse mapping, probes the oracle for both tokens, and initializes the IRM. Emits CreateMarket. When the OPERATOR role has members, only an OPERATOR may create markets; otherwise creation is permissionless.

Approve the contract that actually pulls the tokens — that isn't always Moolah. For direct core calls with no provider or broker in the way, supply and supplyCollateral pull with transferFrom where the spender is the Moolah singleton, so approve Moolah for those. Gating is per leg, not per market: a collateral provider takes over supplyCollateral / withdrawCollateral; a broker takes over borrow / repay. A broker does not gate collateral — on a broker market with no collateral provider you still call supplyCollateral on Moolah and approve Moolah for it. Two cases route the pull elsewhere: when a collateral provider is set, Moolah requires msg.sender == provider, so the provider itself calls supplyCollateral and must have already pulled the tokens from you (e.g. SmartProvider/SlisBNBProvider both do transferFrom(msg.sender, address(this), ...) before calling Moolah) — approve the provider, not Moolah. When a broker is set, Moolah.repay requires msg.sender == broker, so the end user cannot call it directly — the broker itself pulls the repayment from the user (e.g. LendingBrokerOperatorLib does transferFrom(user, address(this), ...)) before forwarding to Moolah — approve the broker contract, not Moolah, for repay on a broker market. See Integration Patterns.

Supply / withdraw (loan-side liquidity)

Supplies loan assets on behalf of onBehalf, minting supply shares. Accrues interest first. If data is non-empty, calls back onMoolahSupply on the caller before pulling tokens via transferFrom. Subject to the per-market whitelist and vault blacklist, and to the minLoan floor on the resulting position.

Burns supply shares of onBehalf and sends assets to receiver. Caller must be onBehalf or authorized. Reverts if it would push totalBorrowAssets above totalSupplyAssets (insufficient liquidity).

Borrow / repay (debt-side)

Borrows loan assets against onBehalf's collateral and sends them to receiver. Accrues interest, then requires the resulting position to be healthy (_isHealthy) and the market to keep totalBorrowAssets <= totalSupplyAssets. Access depends on the market's routing: if a broker is set, only the broker may borrow (and must be the receiver); else if the caller is the market's provider, the receiver must be that provider; otherwise the caller must be onBehalf or authorized. Subject to the whitelist and the minLoan floor.

Repays onBehalf's debt, burning borrow shares. Accrues interest first; if data is non-empty, calls back onMoolahRepay before pulling tokens. If a broker is set for the market, only the broker may repay. A partial repay that would leave debt below minLoan reverts.

To close a position entirely, pass the full borrowShares and leave assets = 0. Reading the debt as assets and repaying that figure leaves sub-minLoan dust once interest accrues between your read and execution, and the whole call reverts — a repay-all button built that way fails for every user.

Collateral management

Posts collateral for onBehalf. Does not accrue interest (not required, saves gas). If a provider is set for the collateral token, only the provider may call. If data is non-empty, calls back onMoolahSupplyCollateral before pulling tokens. Subject to the whitelist.

Withdraws collateral to receiver. Accrues interest, then requires the position to remain healthy. If a provider is set, only the provider may call (and must be the receiver); otherwise the caller must be onBehalf or authorized.

Liquidation

Liquidates an unhealthy borrower. Exactly one of seizedAssets / repaidShares must be zero; the other is derived using the liquidation incentive factor min(MAX_LIQUIDATION_INCENTIVE_FACTOR, 1 / (1 - LIQUIDATION_CURSOR * (1 - lltv))). Accrues interest, checks the position is unhealthy at the oracle price, seizes collateral to the caller, and pulls the repaid loan assets. If collateral reaches zero, remaining debt is realized as bad debt against the market's supply side. Gated by the per-market liquidation whitelist. If data is non-empty, onMoolahLiquidate is called before the repay transfer.

Broker-only path to write down badDebtShares of a broker-market borrower against the market's supply side — the loss is socialized across all suppliers by reducing totalSupplyAssets, and the fee recipient's position is not touched. Callable only by the market's broker; this path performs no health check, so the broker is responsible for determining that the position is unhealthy.

Flash loans

Transfers assets of token to the caller, invokes onMoolahFlashLoan, then pulls the same assets back in the same transaction. Has access to the contract's entire balance of token (all markets' liquidity and collateral combined). The flash fee is zero. Reverts if the token is on the flash-loan blacklist or assets is zero. Not ERC-3156 compliant. whenNotPaused applies; for the reentrancy model see Reentrancy guard.

Authorization

setAuthorization sets whether authorized may manage msg.sender's positions across all markets. setAuthorizationWithSig does the same via an EIP-712 signature, consuming the authorizer's nonce and requiring block.timestamp <= deadline. Anyone may always manage their own positions regardless of these flags.

Interest accrual

Permissionlessly accrues interest for a market: queries borrowRate from the IRM, compounds it over the elapsed time (three-term Taylor approximation of continuous compounding), grows totalBorrowAssets/totalSupplyAssets, and mints fee shares to feeRecipient when the market fee is non-zero. Emits AccrueInterest. The interest-bearing entry points — supply, withdraw, borrow, repay, withdrawCollateral, liquidate, and liquidateBrokerPosition — accrue interest internally before touching balances. supplyCollateral deliberately skips accrual (collateral neither earns nor owes interest, so it saves gas), and flashLoan does not accrue.


View functions

View
Signature
Returns

Position

position(Id id, address user)

The Position struct for a user in a market.

Market

market(Id id)

The Market accounting struct.

Params by id

idToMarketParams(Id id)

The MarketParams for a market (reverse of the Id hash).

Authorization

isAuthorized(address authorizer, address authorized)

Whether authorized may manage authorizer's positions.

Nonce

nonce(address authorizer)

The authorizer's current EIP-712 nonce.

Domain separator

domainSeparator()

The EIP-712 domain separator for the current chain.

Health

isHealthy(MarketParams, Id, address borrower)

Whether a borrower's position is healthy.

Price

getPrice(MarketParams)

Collateral price in loan-asset terms, scaled by 10 ** (36 + quoteDecimals - baseDecimals).

IRM enabled

isIrmEnabled(address irm)

Whether an IRM may be used for new markets.

LLTV enabled

isLltvEnabled(uint256 lltv)

Whether an LLTV may be used for new markets.

Fee recipient

feeRecipient()

The global fee recipient.

Default fee

defaultMarketFee()

Fee applied to newly created markets.

Computing a market Id off-chain is keccak256(abi.encode(loanToken, collateralToken, oracle, irm, lltv)); on-chain the equivalent is MarketParamsLib.id(marketParams). The reverse (Id → params) is idToMarketParams.

Relevant constants

These are compile-time constants of the interface, not adjustable parameters — no governance call changes them.

Constant
Value
Role

WAD

1e18

Fixed-point scale for rates, fees, and lltv.

ORACLE_PRICE_SCALE

1e36

Internal price scale used in health/liquidation math.

MAX_FEE

0.25e18 (25%)

Upper bound on any market fee.

LIQUIDATION_CURSOR

0.3e18

Input to the liquidation incentive formula.

MAX_LIQUIDATION_INCENTIVE_FACTOR

1.15e18

Cap on the liquidation incentive factor.


Lista-specific extensions

These controls are additions on top of the Morpho Blue base and are what distinguishes Moolah from a vanilla deployment.

Minimum loan floor (minLoanValue / minLoan)

minLoanValue is a protocol-wide floor denominated in the oracle's 8-decimal USD unit. minLoan(marketParams) converts it into the market's loan-token amount using the oracle price and token decimals. Supply, borrow, and repay all enforce that a non-zero resulting position stays at or above this floor, which prevents dust positions that are uneconomical to liquidate. The value is a current, manager-adjustable on-chain value (setMinLoanValue, MANAGER role), not a fixed promise.

Liquidation whitelist

Per-market allowlist of eligible liquidators. When a market's list is empty, liquidation is open to anyone; once any address is added, only listed addresses may call liquidate for that market.

Third-party liquidators should not call liquidate on this contract directly — go through PublicLiquidator. Reaching a market gated here needs both sides to line up: the BOT must have opened the market or borrower on PublicLiquidator, and PublicLiquidator itself must be on this market's liquidationWhitelist. See Liquidator Integration.

Supply/borrow whitelist

Optional per-market gate on supply, supplyCollateral, and borrow (checked against onBehalf). An empty list means the market is open; a non-empty list restricts these actions to listed accounts.

Vault & flash-loan blacklists

vaultBlacklist blocks a blacklisted onBehalf from receiving new supply. flashLoanTokenBlacklist disables flashLoan for a specific token.

Providers & brokers routing

A provider is registered per market and token, and its effect depends on which token it backs. A collateral-token provider is an exclusive gate: when set, only the provider may call supplyCollateral/withdrawCollateral (and on withdraw the receiver must be the provider). A loan-token provider is not an exclusive gate — it only constrains the borrow receiver: if the loan-token provider is the borrow caller, receiver must be the provider; ordinary authorized borrowers are unaffected, and plain supply/withdraw of the loan asset are never provider-gated. A broker is registered per market and, when present, gates borrow and repay origination — only the broker may borrow or repay. For a broker market, _isHealthy still prices collateral with the plain market price (_getPrice with user = address(0)) but replaces the borrower's debt with the broker's total debt (Moolah principal + interest accrued at the broker); liquidateBrokerPosition is a separate broker-only bad-debt write-down that performs no health check (health responsibility sits with the broker). setMarketBroker validates that the broker's LOAN_TOKEN, COLLATERAL_TOKEN, and MARKET_ID match the market. See Integration Patterns for how these layers compose.

Resilient oracle routing

Markets read prices through the oracle in MarketParams, which exposes peek(address asset). Each market names its own oracle, so which contract answers — a Resilient Oracle aggregating a main / pivot / fallback source per asset, or an asset-specific adaptor — is per-market configuration; read marketParams.oracle and check it against the Standard Collaterals / bStock Collaterals tables. When a broker is present and a user address is supplied, price resolution routes through the broker so the effective price reflects the user's fixed-term/fixed-rate position; health checks otherwise use the plain oracle price. Oracle selection is a per-market safety responsibility. See Consuming Oracle Prices.

Reentrancy guard

The core mutating entry points — supply, withdraw, borrow, repay, supplyCollateral, withdrawCollateral, liquidate, and liquidateBrokerPosition — are nonReentrant. flashLoan is intentionally not nonReentrant (its safety comes from the same-transaction repayment invariant). All of the above are additionally whenNotPaused.

Pausing, and what it means for you

Pause is the privileged control that can halt every whenNotPaused entry point: a PAUSER can stop supply, withdraw, borrow, repay, collateral movement, liquidation and flashLoan. Views are unaffected. Handle its revert. It is not the only state that can change under you, though — providers, brokers, and minLoanValue are separate mappings/variables, not part of MarketParams or Market, so a market() or idToMarketParams() read does not tell you whether they changed either; re-read them, or watch Events & Callbacks, rather than assuming a cached read still holds. createMarket is additionally gated whenever any OPERATOR exists.

Everything else — enabling IRMs and LLTVs, setting fees, whitelists, providers, brokers, minLoanValue, and contract upgrades — is role-gated, so a third party cannot call any of it. Upgrade authority sits behind an OpenZeppelin TimelockController — read getMinDelay() on it for the delay in force.

Last updated

Was this helpful?