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

Events & Callbacks

Moolah hands control back to you in two ways:

  • Callbacks — synchronous, in-transaction hooks that Moolah invokes on the caller mid-execution (before it pulls the tokens it is owed), enabling atomic flows such as leverage loops, flash-liquidations, and position migrations.

  • Events — the logs Moolah, the IRM, and the vault layer emit for off-chain indexers, subgraphs, and monitoring.

For function signatures, structs, and the Id/MarketParams types referenced below, see the Contract & Interface Reference. For the higher-level provider/broker model, see Integration Patterns.


Callbacks

Moolah follows the Morpho-style callback pattern: several core entry points optionally call back into msg.sender after updating internal accounting but before transferring in the assets the caller owes. This lets an integrator source those assets inside the same transaction — for example, minting/swapping collateral, or repaying a loan out of seized collateral — so the whole operation is atomic and reverts as a unit if any leg fails.

Firing rule

Each entry point takes a trailing bytes data argument. For supply, repay, supplyCollateral, and liquidate, the callback fires only when data is non-empty (data.length > 0); pass empty data to skip it. flashLoan is the exception: its callback is always invoked, because the flash loan has no purpose without it.

The callback is always made on msg.sender, so the contract that calls Moolah must be the contract that implements the interface.

Callback interfaces

All five interfaces are declared in moolah/interfaces/IMoolahCallbacks.sol.

Interface
Method
Fired by
Fires when

IMoolahSupplyCallback

onMoolahSupply(uint256 assets, bytes data)

supply

data non-empty

IMoolahRepayCallback

onMoolahRepay(uint256 assets, bytes data)

repay

data non-empty

IMoolahSupplyCollateralCallback

onMoolahSupplyCollateral(uint256 assets, bytes data)

supplyCollateral

data non-empty

IMoolahLiquidateCallback

onMoolahLiquidate(uint256 repaidAssets, bytes data)

liquidate

data non-empty

IMoolahFlashLoanCallback

onMoolahFlashLoan(uint256 assets, bytes data)

flashLoan

always

The first argument is the settled amount for that action: assets supplied/repaid/supplied-as-collateral, repaidAssets for a liquidation, or the flash-loaned amount. The data argument is the same opaque payload you passed into the originating call, so you can encode a route, a target market, or step parameters and decode them inside the callback.

Execution order (the atomic window)

Within the originating call, Moolah:

  1. Updates its own accounting.

  2. Emits the corresponding event (Supply, Repay, SupplyCollateral, Liquidate, FlashLoan).

  3. For the actions where Moolah owes you assets, transfers them out now — the seized collateral for liquidate, the loaned token for flashLoan. (supply, repay, and supplyCollateral have no outbound transfer; you are the one paying in.)

  4. Calls back into msg.sender (subject to the firing rule above), handing you control while the position is mid-update.

  5. On return, pulls in the assets you owe via transferFrom (the supplied/repaid/collateral token, or the flash-loaned amount plus zero fee). If your balance or allowance is short, the whole transaction reverts.

Your callback body runs in step 4, so it must leave msg.sender holding enough of the inbound token (and allowance to Moolah) for step 5 to succeed.

Typical atomic flows

Read this before designing a flow. Moolah inherits a single global reentrancy guard, and eight entry points carry it — supply, withdraw, borrow, repay, supplyCollateral, withdrawCollateral, liquidate and liquidateBrokerPosition. flashLoan is the only callback entry point without it. So from inside onMoolahSupply, onMoolahRepay, onMoolahSupplyCollateral or onMoolahLiquidate you cannot re-enter any of those eight — they revert ReentrancyGuardReentrantCall(). Views, accrueInterest and flashLoan carry no guard and remain callable, but no position-mutating composition is possible. Multi-step Moolah composition is possible only from onMoolahFlashLoan. This is a deliberate divergence from upstream Morpho Blue, where the equivalent flows do work, so do not carry a Morpho design over unchanged.

Flow
Callback used
Sketch

Leverage loop

onMoolahFlashLoan

Flash-loan the loan token; in the callback swap it to collateral, supplyCollateral, then borrow enough to repay the flash loan. Must be built on flashLoan — the supplyCollateral callback cannot borrow.

Flash-liquidation

onMoolahLiquidate

Call liquidate with data; Moolah sends you the seized collateral first, then calls back. Swap that collateral on an external venue to raise the repayment. No Moolah call is needed inside the callback, which is why this one works.

Debt / position migration

onMoolahFlashLoan

Flash-loan the loan token, use it to repay a position elsewhere, withdraw the freed collateral, move it, and re-borrow to repay the flash loan.

Zero-capital deleverage

onMoolahFlashLoan

Flash-loan the loan token, repay with it, withdrawCollateral, swap part of the collateral back to the loan token and repay the flash loan. Not available via onMoolahRepay, which cannot call withdrawCollateral.

Callbacks are a low-level primitive. If you are integrating in TypeScript and do not need custom atomic routing, the Moolah Lending SDK builds standard supply/borrow/repay transactions for you.


Events

Core market events (Moolah)

Signatures and indexed parameters are in moolah/libraries/EventsLib.sol — read them from there or from the ABI. Id is the market identifier (the keccak hash of MarketParams). Five things will catch you out when reconstructing state from these logs:

  • Position reconstruction. A supplier's balance evolves through Supply/Withdraw; a borrower's through Borrow/Repay/Liquidate; collateral through SupplyCollateral/WithdrawCollateral. All are keyed by the indexed Id and the indexed onBehalf (or borrower) address.

  • Interest. AccrueInterest carries the prevBorrowRate used for the elapsed period, the interest added to the market, and the feeShares minted to the fee recipient. Note that the fee recipient can receive shares during accrual without a Supply event, so reconstruct fee-recipient balances from AccrueInterest, not Supply.

  • Repay/Liquidate rounding. The repaid amount — assets on Repay, repaidAssets on Liquidate — may exceed the market's totalBorrowAssets by 1 due to rounding. Account for this when reconciling.

  • Bad debt. On Liquidate, non-zero badDebtAssets/badDebtShares mean the position was underwater and the loss was socialized to suppliers of that market.

  • Authorization. Watch SetAuthorization (and IncrementNonce for signature-based authorizations) to track which managers can act on a position via the authorized/onBehalf model.

Lista-specific administrative events (provider/broker wiring, whitelists, blacklists, min-loan, default fee) are also declared in the same EventsLib and documented alongside the functions that emit them in the Contract & Interface Reference.

Interest-rate model events (InterestRateModel)

The IRMs emit their own events, and three of them are emitted by both IRMs with byte-identical signatures, so they share a topic0 — BorrowRateCapUpdate, BorrowRateFloorUpdate and MinCapUpdate come from either InterestRateModel or FixedRateIrm. Filter by the emitting address, not by topic alone, or you will file fixed-rate broker-market logs as adaptive-curve events. Only BorrowRateUpdate is exclusive to the adaptive curve (FixedRateIrm declares it but never emits it, since its borrowRate is view). BorrowRateUpdate is emitted each time Moolah accrues interest against a market that uses this IRM (Moolah calls IIrm.borrowRate during _accrueInterest), so it is the finest-grained rate signal available to indexers.

avgBorrowRate is the average per-second rate (scaled by WAD) applied over the accrual interval; rateAtTarget is the model's rate at target utilization after the update. Fixed-rate broker markets use a separate FixedRateIrm, which emits SetBorrowRate(Id indexed id, int256 newBorrowRate) when its rate is set.

Vault events (MoolahVault)

The ERC-4626 curator vault layer emits the standard Deposit/Withdraw/Transfer events plus its own lifecycle and configuration set, declared in moolah-vault/libraries/EventsLib.sol. Two declared event groups require special handling:

Do not index the vault's Submit* / Revoke* events. SubmitCap, SubmitTimelock, SetTimelock, SubmitGuardian, SetGuardian, SubmitMarketRemoval and the RevokePending* family are all declared in EventsLib but emitted nowhere. They are inherited declarations; Lista uses two external OpenZeppelin TimelockControllers per vault instead of the in-contract pending/timelock mechanism. Their addresses appear in CreateMoolahVault as managerTimeLock and curatorTimeLock; watch those for CallScheduled / CallExecuted / Cancelled. SetCap is emitted directly by the vault, with no preceding SubmitCap.

SetCurator and SetIsAllocator are declared in the same library and likewise never emitted. The vault uses OpenZeppelin AccessControl, so role changes surface as RoleGranted / RoleRevoked filtered on the CURATOR / ALLOCATOR role hashes.

To reconstruct how a vault allocates deposits across underlying Moolah markets, track SetSupplyQueue/SetWithdrawQueue for ordering, SetCap for per-market limits, and ReallocateSupply/ReallocateWithdraw for actual moves.

Vault allocator events (VaultAllocator)

The public reallocation helper (src/vault-allocator/VaultAllocator.sol) declares its events in vault-allocator/libraries/EventsLib.sol.

A public reallocation emits one PublicWithdrawal per market it pulls from and a PublicReallocateTo for the market it supplies into, all correlated by the indexed vault.


See also

Last updated

Was this helpful?