Initial commit: add .gitignore and README

This commit is contained in:
defiQUG
2026-02-09 21:51:30 -08:00
commit 47f6f2de7b
92 changed files with 15299 additions and 0 deletions

156
scripts/check-env.ts Normal file
View File

@@ -0,0 +1,156 @@
#!/usr/bin/env tsx
/**
* Environment Variable Checker
*
* This script checks that all required environment variables are set
* and validates RPC URLs are accessible.
*
* Usage:
* tsx scripts/check-env.ts
*/
// Load environment variables FIRST
import dotenv from 'dotenv';
dotenv.config();
import { createPublicClient, http } from 'viem';
import { mainnet, base, arbitrum, optimism, polygon } from 'viem/chains';
import chalk from 'chalk';
interface EnvCheck {
name: string;
value: string | undefined;
required: boolean;
valid: boolean;
error?: string;
}
async function checkRpcUrl(name: string, url: string | undefined, chain: any): Promise<EnvCheck> {
const check: EnvCheck = {
name,
value: url ? (url.length > 50 ? `${url.substring(0, 30)}...${url.substring(url.length - 10)}` : url) : undefined,
required: false,
valid: false,
};
if (!url) {
check.error = 'Not set (using default or will fail)';
return check;
}
if (url.includes('YOUR_KEY') || url.includes('YOUR_INFURA_KEY')) {
check.error = 'Contains placeholder - please set a real RPC URL';
return check;
}
try {
const client = createPublicClient({
chain,
transport: http(url, { timeout: 5000 }),
});
const blockNumber = await client.getBlockNumber();
check.valid = true;
check.error = `✓ Connected (block: ${blockNumber})`;
} catch (error: any) {
check.error = `Connection failed: ${error.message}`;
}
return check;
}
async function main() {
console.log(chalk.blue('='.repeat(60)));
console.log(chalk.blue('Environment Variable Checker'));
console.log(chalk.blue('='.repeat(60)));
console.log('');
const checks: EnvCheck[] = [];
// Check RPC URLs
console.log(chalk.yellow('Checking RPC URLs...'));
console.log('');
checks.push(await checkRpcUrl('MAINNET_RPC_URL', process.env.MAINNET_RPC_URL, mainnet));
checks.push(await checkRpcUrl('BASE_RPC_URL', process.env.BASE_RPC_URL, base));
checks.push(await checkRpcUrl('ARBITRUM_RPC_URL', process.env.ARBITRUM_RPC_URL, arbitrum));
checks.push(await checkRpcUrl('OPTIMISM_RPC_URL', process.env.OPTIMISM_RPC_URL, optimism));
checks.push(await checkRpcUrl('POLYGON_RPC_URL', process.env.POLYGON_RPC_URL, polygon));
// Check other variables
console.log(chalk.yellow('Checking other environment variables...'));
console.log('');
const privateKey = process.env.PRIVATE_KEY;
checks.push({
name: 'PRIVATE_KEY',
value: privateKey ? '***' + privateKey.slice(-4) : undefined,
required: false,
valid: !!privateKey,
error: privateKey ? '✓ Set (not shown for security)' : 'Not set (optional, only needed for mainnet transactions)',
});
// Print results
console.log(chalk.blue('='.repeat(60)));
console.log(chalk.blue('Results'));
console.log(chalk.blue('='.repeat(60)));
console.log('');
let hasErrors = false;
let hasWarnings = false;
for (const check of checks) {
const status = check.valid ? chalk.green('✓') : (check.required ? chalk.red('✗') : chalk.yellow('⚠'));
const name = chalk.bold(check.name);
const value = check.value ? chalk.gray(`(${check.value})`) : '';
const error = check.error ? ` - ${check.error}` : '';
console.log(`${status} ${name} ${value}${error}`);
if (!check.valid) {
if (check.required) {
hasErrors = true;
} else {
hasWarnings = true;
}
}
// Check for placeholder values
if (check.value && (check.value.includes('YOUR_KEY') || check.value.includes('YOUR_INFURA_KEY'))) {
hasWarnings = true;
console.log(chalk.yellow(` ⚠ Contains placeholder - please set a real value`));
}
}
console.log('');
console.log(chalk.blue('='.repeat(60)));
if (hasErrors) {
console.log(chalk.red('✗ Some required checks failed'));
console.log('');
console.log('Please:');
console.log(' 1. Copy .env.example to .env');
console.log(' 2. Fill in your RPC URLs');
console.log(' 3. Run this script again to verify');
process.exit(1);
} else if (hasWarnings) {
console.log(chalk.yellow('⚠ Some checks have warnings'));
console.log('');
console.log('Recommendations:');
console.log(' - Set RPC URLs in .env file for better performance');
console.log(' - Replace placeholder values with real RPC URLs');
console.log(' - Check RPC provider settings if connections fail');
console.log('');
console.log('You can still run tests, but they may fail if RPC URLs are not properly configured.');
} else {
console.log(chalk.green('✓ All checks passed!'));
console.log('');
console.log('You can now run:');
console.log(' - pnpm run strat run scenarios/aave/leveraged-long.yml');
console.log(' - pnpm run strat:test');
}
}
main().catch(console.error);

16
scripts/install-foundry-deps.sh Executable file
View File

@@ -0,0 +1,16 @@
#!/usr/bin/env bash
# Install Foundry dependencies (OpenZeppelin, etc.)
set -euo pipefail
echo "Installing Foundry dependencies..."
# Install forge-std
forge install foundry-rs/forge-std --no-commit
# Install OpenZeppelin contracts
forge install OpenZeppelin/openzeppelin-contracts --no-commit
echo "Foundry dependencies installed successfully!"

182
scripts/test-strategy.ts Normal file
View 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();

72
scripts/verify-env.ts Normal file
View File

@@ -0,0 +1,72 @@
#!/usr/bin/env tsx
/**
* Quick verification that environment variables are being loaded correctly
*
* Usage:
* tsx scripts/verify-env.ts
*/
// Load dotenv FIRST
import dotenv from 'dotenv';
dotenv.config();
console.log('Environment Variable Verification');
console.log('='.repeat(50));
// Check if dotenv loaded the .env file
const envFile = dotenv.config();
if (envFile.error) {
console.log('⚠ .env file not found (this is okay if using system env vars)');
} else {
console.log('✓ .env file loaded');
}
console.log('');
// Check RPC URLs
const rpcUrls = {
'MAINNET_RPC_URL': process.env.MAINNET_RPC_URL,
'BASE_RPC_URL': process.env.BASE_RPC_URL,
'ARBITRUM_RPC_URL': process.env.ARBITRUM_RPC_URL,
'OPTIMISM_RPC_URL': process.env.OPTIMISM_RPC_URL,
'POLYGON_RPC_URL': process.env.POLYGON_RPC_URL,
};
console.log('RPC URLs:');
for (const [key, value] of Object.entries(rpcUrls)) {
if (value) {
const display = value.length > 50
? `${value.substring(0, 30)}...${value.substring(value.length - 10)}`
: value;
const hasPlaceholder = value.includes('YOUR_KEY') || value.includes('YOUR_INFURA_KEY');
console.log(` ${key}: ${hasPlaceholder ? '⚠ PLACEHOLDER' : '✓'} ${display}`);
} else {
console.log(` ${key}: ✗ Not set`);
}
}
console.log('');
// Check other vars
if (process.env.PRIVATE_KEY) {
console.log('PRIVATE_KEY: ✓ Set (not shown)');
} else {
console.log('PRIVATE_KEY: ✗ Not set (optional)');
}
console.log('');
console.log('='.repeat(50));
// Test network loading
try {
// This will import networks.ts which should use the env vars
const { getNetwork } = await import('../src/strat/config/networks.js');
const network = getNetwork('mainnet');
console.log(`Network config test: ✓ Loaded (RPC: ${network.rpcUrl.substring(0, 30)}...)`);
} catch (error: any) {
console.log(`Network config test: ✗ Failed - ${error.message}`);
}
console.log('');

169
scripts/verify-setup.ts Normal file
View File

@@ -0,0 +1,169 @@
#!/usr/bin/env tsx
/**
* Comprehensive Setup Verification Script
*
* Verifies that all scripts are properly configured with environment variables
* and that connections work correctly.
*
* Usage:
* tsx scripts/verify-setup.ts
*/
// Load dotenv FIRST
import dotenv from 'dotenv';
dotenv.config();
import { existsSync } from 'fs';
import { join } from 'path';
import chalk from 'chalk';
async function main() {
console.log(chalk.blue('='.repeat(70)));
console.log(chalk.blue('DeFi Strategy Testing Framework - Setup Verification'));
console.log(chalk.blue('='.repeat(70)));
console.log('');
let allGood = true;
// Check .env file
console.log(chalk.yellow('1. Checking .env file...'));
if (existsSync('.env')) {
console.log(chalk.green(' ✓ .env file exists'));
} else {
console.log(chalk.yellow(' ⚠ .env file not found'));
console.log(chalk.yellow(' Create it from .env.example: cp .env.example .env'));
allGood = false;
}
console.log('');
// Check .env.example
console.log(chalk.yellow('2. Checking .env.example...'));
if (existsSync('.env.example')) {
console.log(chalk.green(' ✓ .env.example exists'));
} else {
console.log(chalk.red(' ✗ .env.example not found'));
allGood = false;
}
console.log('');
// Check environment variables
console.log(chalk.yellow('3. Checking environment variables...'));
const requiredVars = ['MAINNET_RPC_URL'];
const optionalVars = ['BASE_RPC_URL', 'ARBITRUM_RPC_URL', 'OPTIMISM_RPC_URL', 'POLYGON_RPC_URL', 'PRIVATE_KEY'];
for (const varName of requiredVars) {
const value = process.env[varName];
if (value && !value.includes('YOUR_KEY') && !value.includes('YOUR_INFURA_KEY')) {
console.log(chalk.green(`${varName} is set`));
} else {
console.log(chalk.red(`${varName} is not properly configured`));
allGood = false;
}
}
for (const varName of optionalVars) {
const value = process.env[varName];
if (value && !value.includes('YOUR_KEY') && !value.includes('YOUR_INFURA_KEY')) {
console.log(chalk.green(`${varName} is set`));
} else if (value) {
console.log(chalk.yellow(`${varName} contains placeholder`));
} else {
console.log(chalk.gray(` - ${varName} not set (optional)`));
}
}
console.log('');
// Check scripts load dotenv
console.log(chalk.yellow('4. Checking scripts load dotenv...'));
const scripts = [
'src/strat/cli.ts',
'src/cli/cli.ts',
'scripts/test-strategy.ts',
];
for (const script of scripts) {
if (existsSync(script)) {
// Read first few lines to check for dotenv
const fs = await import('fs');
const content = fs.readFileSync(script, 'utf-8');
const lines = content.split('\n').slice(0, 20);
const hasDotenv = lines.some(line =>
line.includes('dotenv') && (line.includes('import') || line.includes('require'))
);
const dotenvConfigLine = lines.findIndex(line => line.includes('dotenv.config()'));
const firstNonDotenvImport = lines.findIndex(line =>
line.includes('import') && !line.includes('dotenv') && !line.trim().startsWith('//')
);
const dotenvBeforeImports = dotenvConfigLine !== -1 &&
(firstNonDotenvImport === -1 || dotenvConfigLine < firstNonDotenvImport);
if (hasDotenv && dotenvBeforeImports) {
console.log(chalk.green(`${script} loads dotenv correctly`));
} else if (hasDotenv) {
console.log(chalk.yellow(`${script} loads dotenv but may be after other imports`));
} else {
console.log(chalk.red(`${script} does not load dotenv`));
allGood = false;
}
}
}
console.log('');
// Check network config
console.log(chalk.yellow('5. Checking network configuration...'));
try {
const { getNetwork } = await import('../src/strat/config/networks.js');
const network = getNetwork('mainnet');
if (network.rpcUrl && !network.rpcUrl.includes('YOUR_KEY')) {
console.log(chalk.green(` ✓ Network config loads correctly`));
console.log(chalk.gray(` Mainnet RPC: ${network.rpcUrl.substring(0, 50)}...`));
} else {
console.log(chalk.yellow(` ⚠ Network config has placeholder RPC URL`));
}
} catch (error: any) {
console.log(chalk.red(` ✗ Network config error: ${error.message}`));
allGood = false;
}
console.log('');
// Check scenario files
console.log(chalk.yellow('6. Checking scenario files...'));
const scenarios = [
'scenarios/aave/leveraged-long.yml',
'scenarios/aave/liquidation-drill.yml',
'scenarios/compound3/supply-borrow.yml',
];
for (const scenario of scenarios) {
if (existsSync(scenario)) {
console.log(chalk.green(`${scenario} exists`));
} else {
console.log(chalk.yellow(`${scenario} not found`));
}
}
console.log('');
// Summary
console.log(chalk.blue('='.repeat(70)));
if (allGood) {
console.log(chalk.green('✓ Setup verification passed!'));
console.log('');
console.log('Next steps:');
console.log(' 1. Run environment check: pnpm run check:env');
console.log(' 2. Test a scenario: pnpm run strat:test');
console.log(' 3. Run a scenario: pnpm run strat run scenarios/aave/leveraged-long.yml');
} else {
console.log(chalk.yellow('⚠ Setup verification found some issues'));
console.log('');
console.log('Please:');
console.log(' 1. Create .env file: cp .env.example .env');
console.log(' 2. Fill in your RPC URLs in .env');
console.log(' 3. Run: pnpm run check:env');
process.exit(1);
}
console.log('');
}
main().catch(console.error);