Initial commit: add .gitignore and README
This commit is contained in:
182
scripts/test-strategy.ts
Normal file
182
scripts/test-strategy.ts
Normal file
@@ -0,0 +1,182 @@
|
||||
#!/usr/bin/env tsx
|
||||
|
||||
/**
|
||||
* Test script for DeFi strategy testing
|
||||
*
|
||||
* This script can be used to test the strategy framework with a real fork
|
||||
*
|
||||
* Usage:
|
||||
* tsx scripts/test-strategy.ts
|
||||
*
|
||||
* Environment variables:
|
||||
* MAINNET_RPC_URL - RPC URL for mainnet fork (required)
|
||||
* TEST_SCENARIO - Path to scenario file (default: scenarios/aave/leveraged-long.yml)
|
||||
* TEST_NETWORK - Network name (default: mainnet)
|
||||
*/
|
||||
|
||||
// Load environment variables FIRST, before any other imports that might use them
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
|
||||
import { readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { ForkOrchestrator } from '../src/strat/core/fork-orchestrator.js';
|
||||
import { ScenarioRunner } from '../src/strat/core/scenario-runner.js';
|
||||
import { loadScenario } from '../src/strat/dsl/scenario-loader.js';
|
||||
import { AaveV3Adapter } from '../src/strat/adapters/aave-v3-adapter.js';
|
||||
import { UniswapV3Adapter } from '../src/strat/adapters/uniswap-v3-adapter.js';
|
||||
import { CompoundV3Adapter } from '../src/strat/adapters/compound-v3-adapter.js';
|
||||
import { Erc20Adapter } from '../src/strat/adapters/erc20-adapter.js';
|
||||
import { FailureInjector } from '../src/strat/core/failure-injector.js';
|
||||
import { JsonReporter } from '../src/strat/reporters/json-reporter.js';
|
||||
import { HtmlReporter } from '../src/strat/reporters/html-reporter.js';
|
||||
import { getNetwork } from '../src/strat/config/networks.js';
|
||||
import type { ProtocolAdapter } from '../src/strat/types.js';
|
||||
|
||||
async function main() {
|
||||
const scenarioPath = process.env.TEST_SCENARIO || 'scenarios/aave/leveraged-long.yml';
|
||||
const networkName = process.env.TEST_NETWORK || 'mainnet';
|
||||
|
||||
// Get RPC URL from env - try network-specific first, then MAINNET_RPC_URL
|
||||
const networkEnvVar = `${networkName.toUpperCase()}_RPC_URL`;
|
||||
let rpcUrl = process.env[networkEnvVar] || process.env.MAINNET_RPC_URL;
|
||||
|
||||
if (!rpcUrl) {
|
||||
console.error('ERROR: RPC URL not found');
|
||||
console.error(` Please set ${networkEnvVar} or MAINNET_RPC_URL in your .env file`);
|
||||
console.error(' Or create .env from .env.example and fill in your RPC URLs');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (rpcUrl.includes('YOUR_KEY') || rpcUrl.includes('YOUR_INFURA_KEY')) {
|
||||
console.error('ERROR: RPC URL contains placeholder');
|
||||
console.error(' Please set a real RPC URL in your .env file');
|
||||
console.error(` Current: ${rpcUrl.substring(0, 50)}...`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('='.repeat(60));
|
||||
console.log('DeFi Strategy Testing - Test Script');
|
||||
console.log('='.repeat(60));
|
||||
console.log(`Scenario: ${scenarioPath}`);
|
||||
console.log(`Network: ${networkName}`);
|
||||
console.log(`RPC: ${rpcUrl.substring(0, 30)}...`);
|
||||
console.log('');
|
||||
|
||||
try {
|
||||
// Load scenario
|
||||
console.log('Loading scenario...');
|
||||
const scenario = loadScenario(scenarioPath);
|
||||
console.log(`✓ Loaded scenario with ${scenario.steps.length} steps`);
|
||||
|
||||
// Setup network
|
||||
const network = getNetwork(networkName);
|
||||
network.rpcUrl = rpcUrl;
|
||||
|
||||
// Start fork
|
||||
console.log('Starting fork...');
|
||||
const fork = new ForkOrchestrator(network, rpcUrl);
|
||||
await fork.start();
|
||||
console.log('✓ Fork started');
|
||||
|
||||
// Register adapters
|
||||
console.log('Registering adapters...');
|
||||
const adapters = new Map<string, ProtocolAdapter>();
|
||||
adapters.set('erc20', new Erc20Adapter());
|
||||
adapters.set('aave-v3', new AaveV3Adapter());
|
||||
adapters.set('uniswap-v3', new UniswapV3Adapter());
|
||||
adapters.set('compound-v3', new CompoundV3Adapter());
|
||||
|
||||
// Register failure injector
|
||||
const failureInjector = new FailureInjector(fork);
|
||||
adapters.set('failure', {
|
||||
name: 'failure',
|
||||
discover: async () => ({}),
|
||||
actions: {
|
||||
oracleShock: (ctx, args) => failureInjector.oracleShock(ctx, args),
|
||||
timeTravel: (ctx, args) => failureInjector.timeTravel(ctx, args),
|
||||
setTimestamp: (ctx, args) => failureInjector.setTimestamp(ctx, args),
|
||||
liquidityShock: (ctx, args) => failureInjector.liquidityShock(ctx, args),
|
||||
setBaseFee: (ctx, args) => failureInjector.setBaseFee(ctx, args),
|
||||
pauseReserve: (ctx, args) => failureInjector.pauseReserve(ctx, args),
|
||||
capExhaustion: (ctx, args) => failureInjector.capExhaustion(ctx, args),
|
||||
},
|
||||
views: {},
|
||||
});
|
||||
console.log('✓ Adapters registered');
|
||||
|
||||
// Create snapshot
|
||||
console.log('Creating snapshot...');
|
||||
const snapshotId = await fork.snapshot('test_start');
|
||||
console.log(`✓ Snapshot created: ${snapshotId}`);
|
||||
|
||||
// Run scenario
|
||||
console.log('');
|
||||
console.log('Running scenario...');
|
||||
console.log('-'.repeat(60));
|
||||
const runner = new ScenarioRunner(fork, adapters, network);
|
||||
const report = await runner.run(scenario);
|
||||
console.log('-'.repeat(60));
|
||||
|
||||
// Print summary
|
||||
console.log('');
|
||||
console.log('='.repeat(60));
|
||||
console.log('Run Summary');
|
||||
console.log('='.repeat(60));
|
||||
console.log(`Status: ${report.passed ? '✓ PASSED' : '✗ FAILED'}`);
|
||||
console.log(`Steps: ${report.steps.length}`);
|
||||
console.log(`Duration: ${((report.endTime! - report.startTime) / 1000).toFixed(2)}s`);
|
||||
console.log(`Total Gas: ${report.metadata.totalGas.toString()}`);
|
||||
if (report.error) {
|
||||
console.log(`Error: ${report.error}`);
|
||||
}
|
||||
|
||||
// Generate reports
|
||||
const outputDir = 'out';
|
||||
const timestamp = Date.now();
|
||||
const jsonPath = join(outputDir, `test-run-${timestamp}.json`);
|
||||
const htmlPath = join(outputDir, `test-report-${timestamp}.html`);
|
||||
|
||||
console.log('');
|
||||
console.log('Generating reports...');
|
||||
JsonReporter.generate(report, jsonPath);
|
||||
HtmlReporter.generate(report, htmlPath);
|
||||
console.log(`✓ JSON report: ${jsonPath}`);
|
||||
console.log(`✓ HTML report: ${htmlPath}`);
|
||||
|
||||
// Print step details
|
||||
console.log('');
|
||||
console.log('Step Results:');
|
||||
for (const step of report.steps) {
|
||||
const status = step.result.success ? '✓' : '✗';
|
||||
const duration = (step.duration / 1000).toFixed(2);
|
||||
console.log(` ${status} ${step.stepName} (${duration}s)`);
|
||||
if (!step.result.success) {
|
||||
console.log(` Error: ${step.result.error}`);
|
||||
}
|
||||
if (step.assertions && step.assertions.length > 0) {
|
||||
const passed = step.assertions.filter(a => a.passed).length;
|
||||
const total = step.assertions.length;
|
||||
console.log(` Assertions: ${passed}/${total} passed`);
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
await fork.revert(snapshotId);
|
||||
await fork.stop();
|
||||
|
||||
console.log('');
|
||||
console.log('='.repeat(60));
|
||||
console.log('Test completed');
|
||||
|
||||
process.exit(report.passed ? 0 : 1);
|
||||
} catch (error: any) {
|
||||
console.error('');
|
||||
console.error('ERROR:', error.message);
|
||||
console.error(error.stack);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
Reference in New Issue
Block a user