Initial commit: add .gitignore and README
This commit is contained in:
125
tests/integration/errors.test.ts
Normal file
125
tests/integration/errors.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
|
||||
60
tests/integration/execution.test.ts
Normal file
60
tests/integration/execution.test.ts
Normal 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");
|
||||
});
|
||||
});
|
||||
|
||||
135
tests/integration/flash-loan.test.ts
Normal file
135
tests/integration/flash-loan.test.ts
Normal 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
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
144
tests/integration/full-execution.test.ts
Normal file
144
tests/integration/full-execution.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
|
||||
101
tests/integration/guards.test.ts
Normal file
101
tests/integration/guards.test.ts
Normal 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");
|
||||
});
|
||||
});
|
||||
|
||||
234
tests/integration/protocol-integration.test.ts
Normal file
234
tests/integration/protocol-integration.test.ts
Normal 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);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user