Files
237-combo/docs/STRATEGY_TESTING.md
2026-02-09 21:51:30 -08:00

14 KiB

🧪 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

# Install dependencies
pnpm install

▶️ Run a Scenario

# 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:

# 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.

pnpm run strat fork up --network mainnet --block 18500000

▶️ run

Run a scenario file.

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.

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.

pnpm run strat failures [protocol]

📊 compare

Compare two run reports.

pnpm run strat compare out/run1.json out/run2.json

📝 Writing Scenarios

Scenarios are defined in YAML or JSON format:

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:

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

// 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:

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

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:

- 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.:

- name: Advance time
  action: failure.timeTravel
  args:
    seconds: 86400  # 1 day

💧 Liquidity Shocks

Move liquidity to test pool utilization:

- name: Liquidity shock
  action: failure.liquidityShock
  args:
    token: WETH
    whale: 0x...
    amount: "1000"

🎲 Fuzzing

Fuzz testing runs scenarios with parameterized inputs:

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