Files
237-combo/docs/STRATEGY_TESTING.md

588 lines
14 KiB
Markdown
Raw Permalink Normal View History

# 🧪 DeFi Strategy Testing Framework
> A comprehensive CLI tool for testing DeFi strategies against local mainnet forks with support for success paths and controlled failure scenarios.
---
## 📋 Overview
The DeFi Strategy Testing Framework allows you to:
- ✅ Run **repeatable, deterministic simulations** of DeFi strategies on local mainnet forks
- 💥 Test both **success** and **failure** cases: liquidations, oracle shocks, cap limits, slippage, approvals, paused assets, etc.
- ✅ Provide **clear pass/fail assertions** (e.g., Aave Health Factor >= 1 after each step; exact token deltas; gas ceilings)
- 📊 Produce **auditable reports** (JSON + HTML) suitable for CI
- 🎲 **Fuzz test** strategies with parameterized inputs
- 🐋 **Automatically fund** test accounts via whale impersonation
---
## 🏗️ Architecture
```
/defi-strat-cli
/src/strat
/core # 🔧 Engine: fork control, scenario runner, assertions, reporting
- fork-orchestrator.ts # 🍴 Fork management (Anvil/Hardhat)
- scenario-runner.ts # ▶️ Executes scenarios step by step
- assertion-evaluator.ts # ✅ Evaluates assertions
- failure-injector.ts # 💥 Injects failure scenarios
- fuzzer.ts # 🎲 Fuzz testing with parameterized inputs
- whale-registry.ts # 🐋 Whale addresses for token funding
/adapters # 🔌 Protocol adapters
/aave-v3-adapter.ts # 🏦 Aave v3 operations
/uniswap-v3-adapter.ts # 🔄 Uniswap v3 swaps
/compound-v3-adapter.ts # 🏛️ Compound v3 operations
/erc20-adapter.ts # 💰 ERC20 token operations
/dsl # 📝 Strategy/Scenario schema + loader
- scenario-loader.ts # 📄 YAML/JSON parser
/reporters # 📊 Report generators
- json-reporter.ts # 📄 JSON reports
- html-reporter.ts # 🌐 HTML reports
- junit-reporter.ts # 🔧 JUnit XML for CI
/config # ⚙️ Configuration
- networks.ts # 🌐 Network configurations
- oracle-feeds.ts # 🔮 Oracle feed addresses
/scenarios # 📚 Example strategies
/aave
- leveraged-long.yml
- liquidation-drill.yml
/compound3
- supply-borrow.yml
```
---
## 🚀 Quick Start
### 📦 Installation
```bash
# Install dependencies
pnpm install
```
### ▶️ Run a Scenario
```bash
# Run a scenario
pnpm run strat run scenarios/aave/leveraged-long.yml
# Run with custom network
pnpm run strat run scenarios/aave/leveraged-long.yml --network base
# Generate reports
pnpm run strat run scenarios/aave/leveraged-long.yml \
--report out/run.json \
--html out/report.html \
--junit out/junit.xml
```
### 🧪 Test Script
For comprehensive testing with a real fork:
```bash
# Set your RPC URL
export MAINNET_RPC_URL=https://mainnet.infura.io/v3/YOUR_KEY
# Run test script
pnpm run strat:test
```
---
## 🖥️ CLI Commands
### 🍴 `fork up`
Start or attach to a fork instance.
```bash
pnpm run strat fork up --network mainnet --block 18500000
```
### ▶️ `run`
Run a scenario file.
```bash
pnpm run strat run <scenario-file> [options]
```
| Option | Description | Default |
|--------|-------------|---------|
| `--network <network>` | Network name or chain ID | `mainnet` |
| `--report <file>` | Output JSON report path | - |
| `--html <file>` | Output HTML report path | - |
| `--junit <file>` | Output JUnit XML report path | - |
| `--rpc <url>` | Custom RPC URL | - |
### 🎲 `fuzz`
Fuzz test a scenario with parameterized inputs.
```bash
pnpm run strat fuzz scenarios/aave/leveraged-long.yml --iters 100 --seed 42
```
| Option | Description | Default |
|--------|-------------|---------|
| `--iters <number>` | Number of iterations | `100` |
| `--seed <number>` | Random seed for reproducibility | - |
| `--report <file>` | Output JSON report path | - |
### 💥 `failures`
List available failure injection methods.
```bash
pnpm run strat failures [protocol]
```
### 📊 `compare`
Compare two run reports.
```bash
pnpm run strat compare out/run1.json out/run2.json
```
---
## 📝 Writing Scenarios
Scenarios are defined in YAML or JSON format:
```yaml
version: 1
network: mainnet
protocols: [aave-v3, uniswap-v3]
assumptions:
baseCurrency: USD
slippageBps: 30
minHealthFactor: 1.05
accounts:
trader:
funded:
- token: WETH
amount: "5"
steps:
- name: Approve WETH to Aave Pool
action: erc20.approve
args:
token: WETH
spender: aave-v3:Pool
amount: "max"
- name: Supply WETH
action: aave-v3.supply
args:
asset: WETH
amount: "5"
onBehalfOf: $accounts.trader
assert:
- aave-v3.healthFactor >= 1.5
- name: Borrow USDC
action: aave-v3.borrow
args:
asset: USDC
amount: "6000"
rateMode: variable
- name: Swap USDC->WETH
action: uniswap-v3.exactInputSingle
args:
tokenIn: USDC
tokenOut: WETH
fee: 500
amountIn: "3000"
- name: Oracle shock (-12% WETH)
action: failure.oracleShock
args:
feed: CHAINLINK_WETH_USD
pctDelta: -12
- name: Check HF still safe
action: assert
args:
expression: "aave-v3.healthFactor >= 1.05"
```
---
## 🔌 Supported Actions
### 🏦 Aave v3
| Action | Description | Status |
|--------|-------------|--------|
| `aave-v3.supply` | Supply assets to Aave | ✅ |
| `aave-v3.withdraw` | Withdraw assets from Aave | ✅ |
| `aave-v3.borrow` | Borrow assets from Aave | ✅ |
| `aave-v3.repay` | Repay borrowed assets | ✅ |
| `aave-v3.flashLoanSimple` | Execute a flash loan | ✅ |
**Views:**
- `aave-v3.healthFactor`: Get user health factor
- `aave-v3.userAccountData`: Get full user account data
### 🏛️ Compound v3
| Action | Description | Status |
|--------|-------------|--------|
| `compound-v3.supply` | Supply collateral to Compound v3 | ✅ |
| `compound-v3.withdraw` | Withdraw collateral or base asset | ✅ |
| `compound-v3.borrow` | Borrow base asset (withdraws base asset) | ✅ |
| `compound-v3.repay` | Repay debt (supplies base asset) | ✅ |
**Views:**
- `compound-v3.borrowBalance`: Get borrow balance
- `compound-v3.collateralBalance`: Get collateral balance for an asset
### 🔄 Uniswap v3
| Action | Description | Status |
|--------|-------------|--------|
| `uniswap-v3.exactInputSingle` | Execute an exact input swap | ✅ |
| `uniswap-v3.exactOutputSingle` | Execute an exact output swap | ✅ |
### 💰 ERC20
| Action | Description | Status |
|--------|-------------|--------|
| `erc20.approve` | Approve token spending | ✅ |
**Views:**
- `erc20.balanceOf`: Get token balance
### 💥 Failure Injection
| Action | Description | Status |
|--------|-------------|--------|
| `failure.oracleShock` | Inject an oracle price shock (attempts storage manipulation) | ✅ |
| `failure.timeTravel` | Advance time | ✅ |
| `failure.setTimestamp` | Set block timestamp | ✅ |
| `failure.liquidityShock` | Move liquidity | ✅ |
| `failure.setBaseFee` | Set gas price | ✅ |
| `failure.pauseReserve` | Pause a reserve (Aave) | ✅ |
| `failure.capExhaustion` | Simulate cap exhaustion | ✅ |
---
## ✅ Assertions
Assertions can be added to any step:
```yaml
steps:
- name: Check health factor
action: assert
args:
expression: "aave-v3.healthFactor >= 1.05"
```
### Supported Operators
| Operator | Description | Example |
|----------|-------------|---------|
| `>=` | Greater than or equal | `aave-v3.healthFactor >= 1.05` |
| `<=` | Less than or equal | `amount <= 1000` |
| `>` | Greater than | `balance > 0` |
| `<` | Less than | `gasUsed < 1000000` |
| `==` | Equal to | `status == "success"` |
| `!=` | Not equal to | `error != null` |
---
## 📊 Reports
### 📄 JSON Report
Machine-readable JSON format with full run details.
**Features:**
- ✅ Complete step-by-step execution log
- ✅ Assertion results
- ✅ Gas usage metrics
- ✅ Error messages and stack traces
- ✅ State deltas
### 🌐 HTML Report
Human-readable HTML report with:
- ✅ Run summary (pass/fail status, duration, gas)
- ✅ Step-by-step execution details
- ✅ Assertion results with visual indicators
- ✅ Gas usage charts
- ✅ Error messages with syntax highlighting
### 🔧 JUnit XML
CI-friendly XML format for integration with test runners.
**Features:**
- ✅ Compatible with Jenkins, GitLab CI, GitHub Actions
- ✅ Test suite and case structure
- ✅ Pass/fail status
- ✅ Error messages and stack traces
---
## 🍴 Fork Orchestration
The framework supports:
| Backend | Status | Features |
|---------|--------|----------|
| **Anvil** (Foundry) | ✅ | Fast, rich custom RPC methods |
| **Hardhat** | ✅ | Wider familiarity |
| **Tenderly** | 🚧 Coming soon | Optional remote simulation backend |
### 🎯 Fork Features
-**Snapshot/revert** - Fast test loops
- 🐋 **Account impersonation** - Fund/borrow from whales
-**Time travel** - Advance time, set timestamp
- 💾 **Storage manipulation** - Oracle overrides
-**Gas price control** - Test gas scenarios
---
## 🐋 Token Funding
The framework automatically funds test accounts via whale impersonation. Known whale addresses are maintained in the whale registry for common tokens.
### How It Works
1. 📋 Look up whale address from registry
2. 🎭 Impersonate whale on the fork
3. 💸 Transfer tokens to test account
4. ✅ Verify balance
### Adding New Whales
```typescript
// src/strat/core/whale-registry.ts
export const WHALE_REGISTRY: Record<number, Record<string, Address>> = {
1: {
YOUR_TOKEN: '0x...' as Address,
},
};
```
---
## 🔌 Protocol Adapters
### Adding a New Adapter
Implement the `ProtocolAdapter` interface:
```typescript
export interface ProtocolAdapter {
name: string;
discover(network: Network): Promise<RuntimeAddresses>;
actions: Record<string, (ctx: StepContext, args: any) => Promise<StepResult>>;
invariants?: Array<(ctx: StepContext) => Promise<void>>;
views?: Record<string, (ctx: ViewContext, args?: any) => Promise<any>>;
}
```
### Example Implementation
```typescript
export class MyProtocolAdapter implements ProtocolAdapter {
name = 'my-protocol';
async discover(network: Network): Promise<RuntimeAddresses> {
return {
contract: '0x...',
};
}
actions = {
myAction: async (ctx: StepContext, args: any): Promise<StepResult> => {
// Implement action
return { success: true };
},
};
views = {
myView: async (ctx: ViewContext): Promise<any> => {
// Implement view
return value;
},
};
}
```
---
## 💥 Failure Injection
### 🔮 Oracle Shocks
Inject price changes to test liquidation scenarios. The framework attempts to modify Chainlink aggregator storage:
```yaml
- name: Oracle shock
action: failure.oracleShock
args:
feed: CHAINLINK_WETH_USD
pctDelta: -12 # -12% price drop
# aggregatorAddress: 0x... # Optional, auto-resolved if not provided
```
> ⚠️ **Note:** Oracle storage manipulation requires precise slot calculation and may not work on all forks. The framework will attempt the manipulation and log warnings if it fails.
### ⏰ Time Travel
Advance time for interest accrual, maturity, etc.:
```yaml
- name: Advance time
action: failure.timeTravel
args:
seconds: 86400 # 1 day
```
### 💧 Liquidity Shocks
Move liquidity to test pool utilization:
```yaml
- name: Liquidity shock
action: failure.liquidityShock
args:
token: WETH
whale: 0x...
amount: "1000"
```
---
## 🎲 Fuzzing
Fuzz testing runs scenarios with parameterized inputs:
```bash
pnpm run strat fuzz scenarios/aave/leveraged-long.yml --iters 100 --seed 42
```
### What Gets Fuzzed
| Parameter | Variation | Description |
|-----------|-----------|-------------|
| Amounts | ±20% | Randomly vary token amounts |
| Oracle shocks | Within range | Vary oracle shock percentages |
| Fee tiers | Random selection | Test different fee tiers |
| Slippage | Variable | Vary slippage parameters |
### Features
- ✅ Each iteration runs on a fresh snapshot
- ✅ Failures don't affect subsequent runs
- ✅ Reproducible with seed parameter
- ✅ Detailed report for all iterations
---
## 🌐 Network Support
| Network | Chain ID | Status |
|---------|----------|--------|
| Ethereum Mainnet | 1 | ✅ |
| Base | 8453 | ✅ |
| Arbitrum One | 42161 | ✅ |
| Optimism | 10 | ✅ |
| Polygon | 137 | ✅ |
> 💡 Or use chain IDs directly: `--network 1` for mainnet.
---
## 🔐 Security & Safety
> ⚠️ **IMPORTANT**: This tool is for **local forks and simulations only**. Do **not** use real keys or send transactions on mainnet from this tool.
Testing "oracle shocks", liquidations, and admin toggles are **defensive simulations** to validate strategy resilience, **not** instructions for real-world exploitation.
---
## 📚 Examples
See the `scenarios/` directory for example scenarios:
| Scenario | Description | Path |
|----------|-------------|------|
| **Leveraged Long** | Leveraged long strategy with Aave and Uniswap | `aave/leveraged-long.yml` |
| **Liquidation Drill** | Test liquidation scenarios with oracle shocks | `aave/liquidation-drill.yml` |
| **Supply & Borrow** | Compound v3 supply and borrow example | `compound3/supply-borrow.yml` |
---
## 🔧 Troubleshooting
### ❌ Token Funding Fails
If token funding fails, check:
1. ✅ Whale address has sufficient balance on the fork
2. ✅ Fork supports account impersonation (Anvil)
3. ✅ RPC endpoint allows custom methods
### ❌ Oracle Shocks Don't Work
Oracle storage manipulation is complex and may fail if:
1. ❌ Storage slot calculation is incorrect
2. ❌ Fork doesn't support storage manipulation
3. ❌ Aggregator uses a different storage layout
> 💡 The framework will log warnings and continue - verify price changes manually if needed.
### ❌ Fork Connection Issues
If the fork fails to start:
1. ✅ Check RPC URL is correct and accessible
2. ✅ Verify network configuration
3. ✅ Check if fork block number is valid
---
## 🚀 Future Enhancements
- [ ] 🎯 Tenderly backend integration
- [ ] ⛽ Gas profiling & diffing
- [ ] 📊 Risk margin calculators
- [ ] 📈 HTML charts for HF over time
- [ ] 🔌 More protocol adapters (Maker, Curve, Balancer, etc.)
- [ ] ⚡ Parallel execution of scenarios
- [ ] 📝 Scenario templates and generators
---
## 🤝 Contributing
Contributions welcome! Please:
1. 🍴 Fork the repository
2. 🌿 Create a feature branch
3. ✏️ Make your changes
4. 🧪 Add tests
5. 📤 Submit a pull request
---
## 📄 License
MIT