Hooks

Step-by-step guide to implementing, testing, and deploying custom Lotus hooks with Solidity and TypeScript examples, including a MaxSupplyHook example.

Implement, test, and deploy a custom hook for Lotus markets.

circle-info

Prerequisites: Familiarity with Solidity and the Lotus protocol concepts (see Learn → Hooks), understanding of the ILotusHooks interface (see Reference → Hooks), and a development environment with Forge.

1

Implement the Interface

Inherit from BaseLotusHook, which provides default pass-through implementations for all five hook points. Override only the methods you need. Return the function selector on success; revert to deny the operation.

The following example implements a MaxSupplyHook that caps total supply per tranche:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {BaseLotusHook} from "lotus/hooks/base/BaseLotusHook.sol";
import {ILotusHooks} from "lotus/hooks/interfaces/ILotusHooks.sol";
import {ILotus} from "lotus/interfaces/ILotus.sol";
import {Id} from "lotus/interfaces/Types.sol";

contract MaxSupplyHook is BaseLotusHook {
    ILotus public immutable lotus;
    address public owner;

    // Maximum supply (in loan token assets) per market per tranche
    mapping(Id => mapping(uint256 => uint256)) public maxSupply;

    error NotOwner();
    error SupplyCapExceeded(uint256 currentSupply, uint256 cap);

    modifier onlyOwner() {
        if (msg.sender != owner) revert NotOwner();
        _;
    }

    constructor(address _owner, ILotus _lotus) {
        owner = _owner;
        lotus = _lotus;
    }

    function setMaxSupply(Id id, uint256 trancheIndex, uint256 cap) external onlyOwner {
        maxSupply[id][trancheIndex] = cap;
    }

    function afterSupply(
        ILotusHooks.SupplyParams calldata params
    ) external view override returns (bytes4) {
        uint256 cap = maxSupply[params.id][params.trancheIndex];
        if (cap == 0) return ILotusHooks.afterSupply.selector; // unconfigured

        // Read current tranche supply from Lotus
        ILotus.Tranche[] memory tranches = lotus.getMarketTranches(params.id);
        uint256 currentSupply = tranches[params.trancheIndex].trancheSupplyAssets;

        if (currentSupply > cap) {
            revert SupplyCapExceeded(currentSupply, cap);
        }

        return ILotusHooks.afterSupply.selector;
    }
}

Because BaseLotusHook provides pass-through implementations for the other four hooks, you do not need to implement them.

2

Set Permission Flags

Compute the permissions bitmask for your hook. Each hook point has a corresponding flag:

import {Hooks} from "lotus/hooks/libraries/Hooks.sol";

// Single hook point
uint256 permissions = Hooks.AFTER_SUPPLY_FLAG; // = 1

// Multiple hook points — combine with bitwise OR
uint256 permissions = Hooks.AFTER_SUPPLY_FLAG | Hooks.AFTER_BORROW_FLAG; // = 3

Only set flags for the hooks your contract actually overrides. Unnecessary flags add gas cost to operations that hit the default pass-through.

3

Deploy and Register

Deploy the hook contract and register it on a market via the admin's setMarketHook call.

// Deploy
MaxSupplyHook hook = new MaxSupplyHook(admin, lotus);

// Configure caps
hook.setMaxSupply(marketId, 0, 1_000_000e6); // 1M USDC cap on tranche 0

// Register on the market (admin only)
uint256 permissions = Hooks.AFTER_SUPPLY_FLAG;
lotus.setMarketHook(marketParams, address(hook), permissions);

In TypeScript with ethers.js:

import { ethers } from "ethers";

const AFTER_SUPPLY_FLAG = 1n << 0n;

// Deploy hook (assume artifact is available)
const HookFactory = new ethers.ContractFactory(abi, bytecode, signer);
const hook = await HookFactory.deploy(adminAddress, lotusAddress);
await hook.waitForDeployment();

// Register on market
const lotus = new ethers.Contract(lotusAddress, lotusAbi, adminSigner);
await lotus.setMarketHook(marketParams, await hook.getAddress(), AFTER_SUPPLY_FLAG);
4

Test the Hook

A Forge test verifying the hook allows normal supplies but blocks supplies that exceed the cap:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "forge-std/Test.sol";
import {MaxSupplyHook} from "../src/MaxSupplyHook.sol";
import {Hooks} from "lotus/hooks/libraries/Hooks.sol";

contract MaxSupplyHookTest is Test {
    // Assume lotus, market, and tokens are set up in setUp()

    function test_supplyUnderCap() public {
        // Set cap to 1M
        hook.setMaxSupply(marketId, 0, 1_000_000e6);

        // Register hook
        vm.prank(admin);
        lotus.setMarketHook(marketParams, address(hook), Hooks.AFTER_SUPPLY_FLAG);

        // Supply 500K — should succeed
        vm.prank(lender);
        lotus.supply(marketParams, 0, 500_000e6, 0, lender, "", "");
    }

    function test_supplyOverCap_reverts() public {
        hook.setMaxSupply(marketId, 0, 1_000_000e6);

        vm.prank(admin);
        lotus.setMarketHook(marketParams, address(hook), Hooks.AFTER_SUPPLY_FLAG);

        // Supply 500K first
        vm.prank(lender);
        lotus.supply(marketParams, 0, 500_000e6, 0, lender, "", "");

        // Supply 600K more — should revert (total 1.1M > 1M cap)
        vm.prank(lender);
        vm.expectRevert();
        lotus.supply(marketParams, 0, 600_000e6, 0, lender, "", "");
    }
}
circle-exclamation

chevron-rightDesign Tipshashtag
  • Keep hooks minimal. They execute on every hooked operation and add gas cost. Avoid complex storage reads or external calls when possible.

  • Hooks cannot modify Lotus state. They are invoked via staticcall and can only approve or reject by returning the selector or reverting.

  • Use BaseLotusHook for defaults. Override only the hooks you need; leave the rest as pass-through.

  • Test thoroughly. Always test both the allow and deny paths.

  • Consider upgradability. Hooks are set per-market and can be replaced by the admin, but users with active positions are affected immediately. If the hook's rules need to change, design the hook with configurable parameters (like setFactor or setMaxSupply) rather than requiring a full redeployment.

See Also

  • Reference → Hooks for the full API

  • Learn → Hooks for conceptual background

  • Reference → Admin for setMarketHook documentation

Last updated