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:
- Rebalance: Adjust the borrow/supply ratio to maintain a target Health Factor.
- Harvest: Claim rewards (e.g., AAVE tokens) and swap them to USDC.
- 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.