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/UniswapRouter.json" as Uniswap;
permission YieldManager -> 1.0.0 {
parameters: {
pool: Aave,
router: address,
account: address,
borrowAmount: uint256,
minAmountOut: uint256,
minHealthFactor: uint256
}
// ...Branching Logic
The bot performs three distinct types of actions. Use explicit control flow so each allowed selector is visible to reviewers.
fn main() -> bool {
// Path 1: Rebalance
// Path 2: Harvest & Swap
// Path 3: Emergency Unwind
return false;
}
}Path 1: Rebalance
Allow supplying collateral, borrowing, or repaying assets, but require the evaluated Aave health factor to stay above the configured floor.
if (context.target == parameters.pool) {
if (context.selector == Aave.supply) {
if (Aave.supply.onBehalfOf != parameters.account) {
return false;
}
} else if (context.selector == Aave.borrow) {
if (Aave.borrow.onBehalfOf != parameters.account) {
return false;
}
if (Aave.borrow.amount != parameters.borrowAmount) {
return false;
}
} else if (context.selector == Aave.repay) {
if (Aave.repay.onBehalfOf != parameters.account) {
return false;
}
} else {
return false;
}
let healthFactor = parameters.pool.getUserAccountData(parameters.account).healthFactor;
return healthFactor >= parameters.minHealthFactor;
}Path 2: Harvest & Swap
Allow claiming rewards and swapping them to stablecoins.
if (context.target == parameters.router) {
if (context.selector == Uniswap.exactInputSingle) {
if (Uniswap.exactInputSingle.params.recipient != parameters.account) {
return false;
}
return Uniswap.exactInputSingle.params.amountOutMinimum >= parameters.minAmountOut;
}
}Path 3: Emergency Unwind
Permit controlled withdrawals only to the managed account. The complete policy below still applies the same health-factor floor after matching the allowed Aave action.
if (context.target == parameters.pool) {
if (context.selector == Aave.withdraw) {
return Aave.withdraw.to == parameters.account;
}
}Complete Policy
import "abis/AavePool.json" as Aave;
import "abis/UniswapRouter.json" as Uniswap;
permission YieldManager -> 1.0.0 {
parameters: {
authorizer: address,
pool: Aave,
router: address,
account: address,
borrowAmount: uint256,
minAmountOut: uint256,
minHealthFactor: uint256
}
pub fn authorize() -> bool {
return authorizedBy(parameters.authorizer);
}
fn main() -> bool {
if (context.target == parameters.pool) {
if (context.selector == Aave.supply) {
if (Aave.supply.onBehalfOf != parameters.account) {
return false;
}
} else if (context.selector == Aave.borrow) {
if (Aave.borrow.onBehalfOf != parameters.account) {
return false;
}
if (Aave.borrow.amount != parameters.borrowAmount) {
return false;
}
} else if (context.selector == Aave.repay) {
if (Aave.repay.onBehalfOf != parameters.account) {
return false;
}
} else if (context.selector == Aave.withdraw) {
if (Aave.withdraw.to != parameters.account) {
return false;
}
} else {
return false;
}
let healthFactor = parameters.pool.getUserAccountData(parameters.account).healthFactor;
return healthFactor >= parameters.minHealthFactor;
}
if (context.target == parameters.router) {
if (context.selector == Uniswap.exactInputSingle) {
if (Uniswap.exactInputSingle.params.recipient != parameters.account) {
return false;
}
return Uniswap.exactInputSingle.params.amountOutMinimum >= parameters.minAmountOut;
}
}
return false;
}
}Optimization Tip
Place cheap selector and target checks before external reads. The interpreter short-circuits on explicit return false paths, saving gas on rejected calls.