Skip to main content

Understanding and Implementing Bundlers in Solidity

This tutorial will walk you through the implementation of a Morpho Bundler contract in Solidity, explaining key concepts and functionalities.

Overview

The MorphoBundler contract is designed to bundle multiple Morpho operations into a single transaction, improving efficiency and user experience.

Key Components

  1. Imports and Inheritance

    import {IMorphoBundler} from "./interfaces/IMorphoBundler.sol";
    import {MarketParams, Signature, Authorization, IMorpho} from "../lib/morpho-blue/src/interfaces/IMorpho.sol";
    import {ErrorsLib} from "./libraries/ErrorsLib.sol";
    import {SafeTransferLib, ERC20} from "../lib/solmate/src/utils/SafeTransferLib.sol";
    import {BaseBundler} from "./BaseBundler.sol";

    abstract contract MorphoBundler is BaseBundler, IMorphoBundler {
    // Contract body
    }
  2. State Variables

    IMorpho public immutable MORPHO;
  3. Constructor

    constructor(address morpho) {
    require(morpho != address(0), ErrorsLib.ZERO_ADDRESS);
    MORPHO = IMorpho(morpho);
    }

Key Functions

  1. morphoSupply: Supply assets to a Morpho market
  2. morphoSupplyCollateral: Supply collateral to a Morpho market
  3. morphoBorrow: Borrow assets from a Morpho market
  4. morphoRepay: Repay borrowed assets
  5. morphoWithdraw: Withdraw supplied assets
  6. morphoWithdrawCollateral: Withdraw supplied collateral
  7. morphoLiquidate: Perform a liquidation
  8. morphoFlashLoan: Execute a flash loan

Example: morphoSupply Function

function morphoSupply(
MarketParams calldata marketParams,
uint256 assets,
uint256 shares,
uint256 slippageAmount,
address onBehalf,
bytes calldata data
) external payable protected {
require(onBehalf != address(this), ErrorsLib.BUNDLER_ADDRESS);

if (assets == type(uint256).max) assets = ERC20(marketParams.loanToken).balanceOf(address(this));

_approveMaxTo(marketParams.loanToken, address(MORPHO));

(uint256 suppliedAssets, uint256 suppliedShares) = MORPHO.supply(marketParams, assets, shares, onBehalf, data);

if (assets > 0) require(suppliedShares >= slippageAmount, ErrorsLib.SLIPPAGE_EXCEEDED);
else require(suppliedAssets <= slippageAmount, ErrorsLib.SLIPPAGE_EXCEEDED);
}

This function demonstrates how to supply assets to a Morpho market, handling maximum asset amounts, approvals, and slippage checks.

Best Practices

  1. Use require statements for input validation

  2. Implement slippage protection

  3. Use payable and protected modifiers for flexibility and security

  4. Leverage the _approveMaxTo function for token approvals

  5. Ensure No Residual Funds: By design, users can temporarily send funds in the Bundler3 contract during a multicall execution, allowing them to orchestrate complex operations. However, this flexibility means integrators must carefully verify that no funds remain in the Bundler3 contract after the transaction completes. Funds left in the contract could be at risk or inaccessible. It's the integrator's responsibility to:

    • Track token balances before and after operations
    • Include final calls to transfer any remaining tokens to the intended recipient
    • Implement balance checks to ensure complete fund extraction
    • Consider adding explicit "skim" operations at the end of complex flows

    Remember that while the bundler2 (known as bundler) contract can temporarily hold funds during execution, it's not designed to be a permanent store of value. Always ensure your integration includes proper cleanup steps.

By understanding and implementing these concepts, you can create efficient and secure bundler contracts for Morpho.