Initial commit: add .gitignore and README

This commit is contained in:
defiQUG
2026-02-09 21:51:54 -08:00
commit 7003349717
127 changed files with 17576 additions and 0 deletions

View File

@@ -0,0 +1,125 @@
import { describe, it, expect } from "vitest";
import { validateStrategy, loadStrategy } from "../../src/strategy.js";
import { StrategyCompiler } from "../../src/planner/compiler.js";
import { writeFileSync, unlinkSync } from "fs";
import { join } from "path";
describe("Error Handling", () => {
it("should handle invalid strategy JSON", () => {
const invalidStrategy = {
name: "Invalid",
// Missing required fields
};
const validation = validateStrategy(invalidStrategy as any);
expect(validation.valid).toBe(false);
expect(validation.errors.length).toBeGreaterThan(0);
});
it("should handle missing blind values", () => {
const strategy = {
name: "Missing Blinds",
chain: "mainnet",
blinds: [
{
name: "amount",
type: "uint256",
},
],
steps: [
{
id: "step1",
action: {
type: "aaveV3.supply",
asset: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
amount: { blind: "amount" },
},
},
],
};
// Strategy should be valid but execution would fail without blind values
const validation = validateStrategy(strategy as any);
expect(validation.valid).toBe(true);
});
it("should handle protocol adapter failures gracefully", async () => {
const strategy = {
name: "Invalid Protocol",
chain: "invalid-chain",
steps: [
{
id: "step1",
action: {
type: "aaveV3.supply",
asset: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
amount: "1000000",
},
},
],
};
const compiler = new StrategyCompiler("invalid-chain");
// Should handle missing adapter gracefully
await expect(compiler.compile(strategy as any)).rejects.toThrow();
});
it("should handle guard failures", async () => {
const strategy = {
name: "Guard Failure",
chain: "mainnet",
guards: [
{
type: "maxGas",
params: {
maxGasLimit: "1000", // Very low limit
},
onFailure: "revert",
},
],
steps: [
{
id: "step1",
action: {
type: "aaveV3.supply",
asset: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
amount: "1000000",
},
},
],
};
// Guard should fail and strategy should not execute
const validation = validateStrategy(strategy as any);
expect(validation.valid).toBe(true);
});
it("should handle unsupported action types", async () => {
const strategy = {
name: "Unsupported Action",
chain: "mainnet",
steps: [
{
id: "step1",
action: {
type: "unsupported.action",
// Invalid action
},
},
],
};
const compiler = new StrategyCompiler("mainnet");
await expect(compiler.compile(strategy as any)).rejects.toThrow(
"Unsupported action type"
);
});
it("should handle execution failures gracefully", async () => {
// This would require a mock execution environment
// For now, just verify error handling structure exists
expect(true).toBe(true);
});
});

View File

@@ -0,0 +1,60 @@
import { describe, it, expect } from "vitest";
import { StrategyCompiler } from "../../src/planner/compiler.js";
describe("Execution Integration", () => {
it("should compile a simple strategy", async () => {
const compiler = new StrategyCompiler("mainnet");
const strategy = {
name: "Test",
chain: "mainnet",
steps: [
{
id: "supply",
action: {
type: "aaveV3.supply",
asset: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
amount: "1000000",
},
},
],
};
const plan = await compiler.compile(strategy as any);
expect(plan.calls.length).toBeGreaterThan(0);
expect(plan.requiresFlashLoan).toBe(false);
});
it("should compile flash loan strategy", async () => {
const compiler = new StrategyCompiler("mainnet");
const strategy = {
name: "Flash Loan Test",
chain: "mainnet",
steps: [
{
id: "flashLoan",
action: {
type: "aaveV3.flashLoan",
assets: ["0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"],
amounts: ["1000000"],
},
},
{
id: "swap",
action: {
type: "uniswapV3.swap",
tokenIn: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
tokenOut: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
fee: 3000,
amountIn: "1000000",
exactInput: true,
},
},
],
};
const plan = await compiler.compile(strategy as any, "0x1234567890123456789012345678901234567890");
expect(plan.requiresFlashLoan).toBe(true);
expect(plan.flashLoanAsset).toBe("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
});
});

View File

@@ -0,0 +1,135 @@
import { describe, it, expect } from "vitest";
import { StrategyCompiler } from "../../src/planner/compiler.js";
describe("Flash Loan Integration", () => {
it("should compile flash loan with swap", async () => {
const strategy = {
name: "Flash Loan Swap",
chain: "mainnet",
steps: [
{
id: "flashLoan",
action: {
type: "aaveV3.flashLoan",
assets: ["0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"],
amounts: ["1000000"],
},
},
{
id: "swap",
action: {
type: "uniswapV3.swap",
tokenIn: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
tokenOut: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
fee: 3000,
amountIn: "1000000",
exactInput: true,
},
},
],
};
const compiler = new StrategyCompiler("mainnet");
const plan = await compiler.compile(
strategy as any,
"0x1234567890123456789012345678901234567890"
);
expect(plan.requiresFlashLoan).toBe(true);
expect(plan.flashLoanAsset).toBe(
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
);
expect(plan.flashLoanAmount).toBe(1000000n);
});
it("should compile flash loan with multiple operations", async () => {
const strategy = {
name: "Flash Loan Multi-Op",
chain: "mainnet",
steps: [
{
id: "flashLoan",
action: {
type: "aaveV3.flashLoan",
assets: ["0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"],
amounts: ["1000000"],
},
},
{
id: "swap1",
action: {
type: "uniswapV3.swap",
tokenIn: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
tokenOut: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
fee: 3000,
amountIn: "500000",
exactInput: true,
},
},
{
id: "swap2",
action: {
type: "uniswapV3.swap",
tokenIn: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
tokenOut: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
fee: 3000,
amountIn: "500000",
exactInput: true,
},
},
],
};
const compiler = new StrategyCompiler("mainnet");
const plan = await compiler.compile(
strategy as any,
"0x1234567890123456789012345678901234567890"
);
expect(plan.requiresFlashLoan).toBe(true);
// Both swaps should be in the callback
expect(plan.calls.length).toBeGreaterThan(0);
});
it("should validate flash loan repayment requirements", async () => {
const strategy = {
name: "Flash Loan Validation",
chain: "mainnet",
steps: [
{
id: "flashLoan",
action: {
type: "aaveV3.flashLoan",
assets: ["0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"],
amounts: ["1000000"],
},
},
{
id: "swap",
action: {
type: "uniswapV3.swap",
tokenIn: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
tokenOut: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
fee: 3000,
amountIn: "1000000",
exactInput: true,
},
},
],
};
const compiler = new StrategyCompiler("mainnet");
const plan = await compiler.compile(
strategy as any,
"0x1234567890123456789012345678901234567890"
);
// Flash loan should require repayment
expect(plan.requiresFlashLoan).toBe(true);
// Should have executeFlashLoan call
expect(plan.calls.some((c) => c.description.includes("flash loan"))).toBe(
true
);
});
});

View File

@@ -0,0 +1,144 @@
import { describe, it, expect } from "vitest";
import { StrategyCompiler } from "../../src/planner/compiler.js";
import { executeStrategy } from "../../src/engine.js";
import { loadStrategy, substituteBlinds } from "../../src/strategy.js";
import { readFileSync } from "fs";
import { join } from "path";
describe("Full Strategy Execution", () => {
it("should compile and execute recursive leverage strategy", async () => {
const strategyPath = join(
process.cwd(),
"strategies",
"sample.recursive.json"
);
if (!require("fs").existsSync(strategyPath)) {
// Skip if strategy file doesn't exist
return;
}
const strategy = loadStrategy(strategyPath);
// Substitute blind values for testing
const blindValues = {
collateralAmount: "1000000", // 1 USDC (6 decimals)
leverageFactor: "500000", // 0.5 USDC
};
const resolvedStrategy = substituteBlinds(strategy, blindValues);
const compiler = new StrategyCompiler(resolvedStrategy.chain);
const plan = await compiler.compile(resolvedStrategy);
expect(plan.calls.length).toBeGreaterThan(0);
});
it("should compile liquidation helper strategy", async () => {
const strategyPath = join(
process.cwd(),
"strategies",
"sample.liquidation.json"
);
if (!require("fs").existsSync(strategyPath)) {
return;
}
const strategy = loadStrategy(strategyPath);
const compiler = new StrategyCompiler(strategy.chain);
const plan = await compiler.compile(strategy);
expect(plan.requiresFlashLoan).toBe(true);
});
it("should compile stablecoin hedge strategy", async () => {
const strategyPath = join(
process.cwd(),
"strategies",
"sample.stablecoin-hedge.json"
);
if (!require("fs").existsSync(strategyPath)) {
return;
}
const strategy = loadStrategy(strategyPath);
const compiler = new StrategyCompiler(strategy.chain);
const plan = await compiler.compile(strategy);
expect(plan.calls.length).toBeGreaterThan(0);
});
it("should compile multi-protocol strategy", async () => {
const strategy = {
name: "Multi-Protocol",
chain: "mainnet",
steps: [
{
id: "supply",
action: {
type: "aaveV3.supply",
asset: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
amount: "1000000",
},
},
{
id: "swap",
action: {
type: "uniswapV3.swap",
tokenIn: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
tokenOut: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
fee: 3000,
amountIn: "500000",
exactInput: true,
},
},
],
};
const compiler = new StrategyCompiler("mainnet");
const plan = await compiler.compile(strategy as any);
expect(plan.calls.length).toBe(2);
});
it("should compile strategy with all guard types", async () => {
const strategy = {
name: "All Guards",
chain: "mainnet",
guards: [
{
type: "maxGas",
params: { maxGasLimit: "5000000" },
},
{
type: "slippage",
params: { maxBps: 50 },
},
],
steps: [
{
id: "step1",
guards: [
{
type: "oracleSanity",
params: {
token: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
maxDeviationBps: 500,
},
},
],
action: {
type: "aaveV3.supply",
asset: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
amount: "1000000",
},
},
],
};
const compiler = new StrategyCompiler("mainnet");
const plan = await compiler.compile(strategy as any);
expect(plan.calls.length).toBeGreaterThan(0);
});
});

View File

@@ -0,0 +1,101 @@
import { describe, it, expect } from "vitest";
import { evaluateGuard } from "../../src/planner/guards.js";
import { GuardContext } from "../../src/planner/guards.js";
import { Guard } from "../../src/strategy.schema.js";
import { PriceOracle } from "../../src/pricing/index.js";
import { AaveV3Adapter } from "../../src/adapters/aaveV3.js";
describe("Guard Integration", () => {
it("should evaluate multiple guards in sequence", async () => {
const guards: Guard[] = [
{
type: "maxGas",
params: {
maxGasLimit: "5000000",
},
},
{
type: "slippage",
params: {
maxBps: 50,
},
},
];
const context: GuardContext = {
chainName: "mainnet",
gasEstimate: {
total: 2000000n,
perCall: [1000000n],
},
};
for (const guard of guards) {
const result = await evaluateGuard(guard, context);
expect(result).toBeDefined();
expect(result.passed).toBeDefined();
}
});
it("should handle guard failure with revert action", async () => {
const guard: Guard = {
type: "maxGas",
params: {
maxGasLimit: "1000000", // Very low limit
},
onFailure: "revert",
};
const context: GuardContext = {
chainName: "mainnet",
gasEstimate: {
total: 2000000n, // Exceeds limit
perCall: [2000000n],
},
};
const result = await evaluateGuard(guard, context);
expect(result.passed).toBe(false);
expect(result.guard.onFailure).toBe("revert");
});
it("should handle guard failure with warn action", async () => {
const guard: Guard = {
type: "slippage",
params: {
maxBps: 10, // Very tight slippage
},
onFailure: "warn",
};
const context: GuardContext = {
chainName: "mainnet",
amountIn: 1000000n,
amountOut: 900000n, // 10% slippage
};
const result = await evaluateGuard(guard, context);
expect(result.passed).toBe(false);
expect(result.guard.onFailure).toBe("warn");
});
it("should handle missing context gracefully", async () => {
const guard: Guard = {
type: "oracleSanity",
params: {
token: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
maxDeviationBps: 500,
},
};
const context: GuardContext = {
chainName: "mainnet",
// Missing oracle
};
const result = await evaluateGuard(guard, context);
expect(result.passed).toBe(false);
expect(result.reason).toContain("not available");
});
});

View File

@@ -0,0 +1,234 @@
import { describe, it, expect, beforeAll } from "vitest";
import { StrategyCompiler } from "../../src/planner/compiler.js";
import { loadStrategy, substituteBlinds } from "../../src/strategy.js";
import { getChainConfig } from "../../src/config/chains.js";
import { JsonRpcProvider, Contract, getAddress } from "ethers";
import { join } from "path";
import * as dotenv from "dotenv";
// Load environment variables
dotenv.config();
describe("Protocol Integration Tests - Recursive Leverage Strategy", () => {
const RPC_MAINNET = process.env.RPC_MAINNET;
const RPC_POLYGON = process.env.RPC_POLYGON;
const RPC_BASE = process.env.RPC_BASE;
const RPC_OPTIMISM = process.env.RPC_OPTIMISM;
// Aave v3 Pool contract ABI (minimal for testing)
const AAVE_POOL_ABI = [
"function getReserveData(address asset) external view returns (tuple(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint40))",
"function getReservesList() external view returns (address[])",
];
describe("Mainnet Protocol Contracts", () => {
it.skipIf(!RPC_MAINNET)("should connect to Aave v3 Pool on mainnet", async () => {
const provider = new JsonRpcProvider(RPC_MAINNET);
const chainConfig = getChainConfig("mainnet");
if (!chainConfig.protocols.aaveV3?.pool) {
throw new Error("Aave v3 pool address not configured");
}
const poolAddress = getAddress(chainConfig.protocols.aaveV3.pool);
const poolContract = new Contract(poolAddress, AAVE_POOL_ABI, provider);
// Test connection by calling getReservesList
const reserves = await poolContract.getReservesList();
expect(reserves).toBeDefined();
expect(Array.isArray(reserves)).toBe(true);
expect(reserves.length).toBeGreaterThan(0);
});
it.skipIf(!RPC_MAINNET)("should verify Aave v3 Pool address is correct", async () => {
const provider = new JsonRpcProvider(RPC_MAINNET);
const chainConfig = getChainConfig("mainnet");
if (!chainConfig.protocols.aaveV3?.pool) {
throw new Error("Aave v3 pool address not configured");
}
const poolAddress = getAddress(chainConfig.protocols.aaveV3.pool);
// Verify contract exists by checking code
const code = await provider.getCode(poolAddress);
expect(code).not.toBe("0x");
expect(code.length).toBeGreaterThan(2);
});
it.skipIf(!RPC_MAINNET)("should compile recursive leverage strategy with real protocol addresses", async () => {
const strategyPath = join(
process.cwd(),
"strategies",
"sample.recursive.json"
);
if (!require("fs").existsSync(strategyPath)) {
return;
}
const strategy = loadStrategy(strategyPath);
// Substitute blind values for testing
const blindValues = {
collateralAmount: "1000000", // 1 USDC (6 decimals)
leverageFactor: "500000", // 0.5 USDC
};
const resolvedStrategy = substituteBlinds(strategy, blindValues);
const compiler = new StrategyCompiler(resolvedStrategy.chain);
const plan = await compiler.compile(resolvedStrategy);
expect(plan.calls.length).toBeGreaterThan(0);
// Verify the compiled calls target the correct Aave pool address
const chainConfig = getChainConfig("mainnet");
const aavePoolAddress = chainConfig.protocols.aaveV3?.pool;
if (aavePoolAddress) {
const checksummedPoolAddress = getAddress(aavePoolAddress);
const supplyCall = plan.calls.find(call =>
getAddress(call.to).toLowerCase() === checksummedPoolAddress.toLowerCase()
);
expect(supplyCall).toBeDefined();
expect(supplyCall?.description).toContain("Aave");
}
});
it.skipIf(!RPC_MAINNET)("should verify USDC is a valid reserve in Aave v3", async () => {
const provider = new JsonRpcProvider(RPC_MAINNET);
const chainConfig = getChainConfig("mainnet");
if (!chainConfig.protocols.aaveV3?.pool) {
throw new Error("Aave v3 pool address not configured");
}
const poolAddress = getAddress(chainConfig.protocols.aaveV3.pool);
const poolContract = new Contract(poolAddress, AAVE_POOL_ABI, provider);
// USDC address on mainnet
const USDC_ADDRESS = getAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
// Get reserve data for USDC
const reserveData = await poolContract.getReserveData(USDC_ADDRESS);
expect(reserveData).toBeDefined();
// Verify it's a valid reserve (liquidityIndex should be > 0)
const liquidityIndex = reserveData[0];
expect(liquidityIndex).toBeDefined();
expect(Number(liquidityIndex)).toBeGreaterThan(0);
});
it.skipIf(!RPC_MAINNET)("should verify Uniswap V3 Router address", async () => {
const provider = new JsonRpcProvider(RPC_MAINNET);
const chainConfig = getChainConfig("mainnet");
if (!chainConfig.protocols.uniswapV3?.router) {
throw new Error("Uniswap V3 router address not configured");
}
const routerAddress = getAddress(chainConfig.protocols.uniswapV3.router);
// Verify contract exists
const code = await provider.getCode(routerAddress);
expect(code).not.toBe("0x");
expect(code.length).toBeGreaterThan(2);
});
});
describe("Polygon Protocol Contracts", () => {
it.skipIf(!RPC_POLYGON)("should connect to Polygon RPC and verify chain config", async () => {
const provider = new JsonRpcProvider(RPC_POLYGON);
// Note: Polygon chain config may not be implemented yet
// This test verifies RPC connectivity only
// Verify we can connect
const blockNumber = await provider.getBlockNumber();
expect(blockNumber).toBeGreaterThan(0);
// Verify chain ID matches Polygon
const network = await provider.getNetwork();
expect(network.chainId).toBe(BigInt(137));
});
});
describe("Base Protocol Contracts", () => {
it.skipIf(!RPC_BASE)("should connect to Base RPC and verify chain config", async () => {
const provider = new JsonRpcProvider(RPC_BASE);
const chainConfig = getChainConfig("base");
// Verify we can connect
const blockNumber = await provider.getBlockNumber();
expect(blockNumber).toBeGreaterThan(0);
// Verify chain ID matches
const network = await provider.getNetwork();
expect(network.chainId).toBe(BigInt(8453));
});
});
describe("Optimism Protocol Contracts", () => {
it.skipIf(!RPC_OPTIMISM)("should connect to Optimism RPC and verify chain config", async () => {
const provider = new JsonRpcProvider(RPC_OPTIMISM);
const chainConfig = getChainConfig("optimism");
// Verify we can connect
const blockNumber = await provider.getBlockNumber();
expect(blockNumber).toBeGreaterThan(0);
// Verify chain ID matches
const network = await provider.getNetwork();
expect(network.chainId).toBe(BigInt(10));
});
});
describe("Recursive Leverage Strategy - Full Integration", () => {
it.skipIf(!RPC_MAINNET)("should compile and validate recursive leverage strategy against real contracts", async () => {
const strategyPath = join(
process.cwd(),
"strategies",
"sample.recursive.json"
);
if (!require("fs").existsSync(strategyPath)) {
return;
}
const provider = new JsonRpcProvider(RPC_MAINNET);
const strategy = loadStrategy(strategyPath);
const chainConfig = getChainConfig(strategy.chain);
// Substitute blind values
const blindValues = {
collateralAmount: "1000000", // 1 USDC
leverageFactor: "500000", // 0.5 USDC
};
const resolvedStrategy = substituteBlinds(strategy, blindValues);
// Compile strategy
const compiler = new StrategyCompiler(resolvedStrategy.chain);
const plan = await compiler.compile(resolvedStrategy);
expect(plan.calls.length).toBeGreaterThan(0);
// Verify all calls target valid contract addresses
for (const call of plan.calls) {
const callAddress = getAddress(call.to);
const code = await provider.getCode(callAddress);
expect(code).not.toBe("0x");
expect(code.length).toBeGreaterThan(2);
}
// Verify Aave pool address matches configuration
const aavePoolAddress = chainConfig.protocols.aaveV3?.pool;
if (aavePoolAddress) {
const checksummedPoolAddress = getAddress(aavePoolAddress);
const aaveCalls = plan.calls.filter(call =>
getAddress(call.to).toLowerCase() === checksummedPoolAddress.toLowerCase()
);
expect(aaveCalls.length).toBeGreaterThan(0);
}
});
});
});