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 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 or borrowing assets, but enforce a health check post-execution.
if (context.target == parameters.pool) {
if (context.selector == Aave.supply) {
return Aave.supply.onBehalfOf == parameters.account;
} else if (context.selector == Aave.borrow) {
return Aave.borrow.amount == parameters.maxBorrowAmount;
} else if (context.selector == Aave.repay) {
return Aave.repay.onBehalfOf == parameters.account;
}
}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
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.
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/UniswapV3Router.json" as Uniswap;
permission YieldManager -> 1.0.0 {
parameters: {
pool: address,
router: address,
account: address,
maxBorrowAmount: uint256,
minAmountOut: uint256
}
fn main() -> bool {
if (context.target == parameters.pool) {
if (context.selector == Aave.supply) {
return Aave.supply.onBehalfOf == parameters.account;
} else if (context.selector == Aave.borrow) {
return Aave.borrow.amount == parameters.maxBorrowAmount;
} else if (context.selector == Aave.repay) {
return Aave.repay.onBehalfOf == parameters.account;
} else if (context.selector == Aave.withdraw) {
return Aave.withdraw.to == parameters.account;
}
}
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 the most frequent operation (e.g., Rebalance) first in the any block. The interpreter short-circuits as soon as a condition matches, saving gas.