Matador Docs
Tutorials

Advanced Policy Authoring

Build a complex, multi-branched security policy for a Yield Farming Bot.

In this tutorial, we will construct a production-grade policy for an automated Yield Farming Bot.

The Scenario

We have a bot that manages a delta-neutral position on Aave. It needs permission to:

  1. Rebalance: Adjust the borrow/supply ratio to maintain a target Health Factor.
  2. Harvest: Claim rewards (e.g., AAVE tokens) and swap them to USDC.
  3. Emergency: Unwind the position if the market crashes.

Constraints:

  • The bot cannot withdraw collateral to its own wallet (only to the Smart Account).
  • Swaps must use the official Uniswap Router.
  • Flash loans are strictly forbidden.

Implementation

Imports & Parameters

We start by importing the necessary ABIs and defining our runtime parameters.

import "abis/AavePool.json" as Aave;
import "abis/UniswapV3Router.json" as Uniswap;
import "abis/ERC20.json" as Token;

permission YieldManager -> 1.0.0 {
    parameters: {
        pool: address,
        router: address,
        rewardsController: address,
        minHealthFactor: uint256
    }
    // ...

Branching Logic (The any Block)

The bot performs three distinct types of actions. We use an any block to allow one of these paths to succeed per transaction.

    when: {
        any {
            // Path 1: Rebalance (Supply/Borrow)
            // Path 2: Harvest & Swap
            // Path 3: Emergency Unwind
        }
    }
}

Path 1: Rebalance

Allow supplying collateral or borrowing assets, but enforce a health check post-execution.

all {
    context.target == parameters.pool,
    any {
        Aave.supply,
        Aave.borrow,
        Aave.repay
    },
    // Enforce health factor > 1.5 after the operation
    Aave.getUserAccountData(user: context.account).healthFactor >= parameters.minHealthFactor
}

Path 2: Harvest & Swap

Allow claiming rewards and swapping them to stablecoins.

all {
    // Sub-branch for Claiming
    any {
        all {
            context.target == parameters.rewardsController,
            Token.claimRewards(to: context.account) // Force claim to self
        },
        // Sub-branch for Swapping
        all {
            context.target == parameters.router,
            Uniswap.exactInputSingle,
            context.args.params.recipient == context.account
        }
    }
}

Path 3: Emergency Unwind

If the health factor drops below a critical threshold (e.g. 1.1), allow any action on the Aave pool (including full withdrawal) to save the position.

all {
    context.target == parameters.pool,
    // Check current on-chain state
    Aave.getUserAccountData(user: context.account).healthFactor < 1100000000000000000
}

Complete Policy

import "abis/AavePool.json" as Aave;
import "abis/UniswapV3Router.json" as Uniswap;

permission YieldManager -> 1.0.0 {
    parameters: {
        pool: address,
        router: address,
        minHealthFactor: uint256
    }
    when: {
        any {
            // 1. Safe Rebalance
            all {
                context.target == parameters.pool,
                any { Aave.supply, Aave.borrow, Aave.repay },
                Aave.getUserAccountData(user: context.account).healthFactor >= parameters.minHealthFactor
            },
            // 2. Safe Swap
            all {
                context.target == parameters.router,
                Uniswap.exactInputSingle,
                context.args.params.recipient == context.account
            },
            // 3. Panic Mode
            all {
                context.target == parameters.pool,
                Aave.getUserAccountData(user: context.account).healthFactor < 1100000000000000000
            }
        }
    }
}

Optimization Tip

Place the most frequent operation (e.g., Rebalance) first in the any block. The interpreter short-circuits as soon as a condition matches, saving gas.

On this page