Skip to content

Commit

Permalink
Static constraints and other scen improvements (compound-finance#700)
Browse files Browse the repository at this point in the history
* Starting point for static constraints and other scen optimizations

* Getting there

* Little closer

* Static constraints somewhat working

Simplify and clean up further

* Seems right; little more cleanup

* Throw in an approximation fix

* Skip forge liquidation tests
  • Loading branch information
jflatow authored Mar 3, 2023
1 parent d6b7e9b commit 4d69c8a
Show file tree
Hide file tree
Showing 22 changed files with 298 additions and 656 deletions.
3 changes: 3 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ out = 'forge/out'
libs = ['node_modules', 'forge/lib']
test = 'forge/test'
cache_path = 'forge/cache'

# disable for CI (used for debugging liquidity issues)
no_match_test = "test.*SwapVia"
132 changes: 84 additions & 48 deletions plugins/scenario/Loader.ts
Original file line number Diff line number Diff line change
@@ -1,74 +1,110 @@
import fg from 'fast-glob';
import * as path from 'path';
import { Scenario, ScenarioFlags, Property, Initializer, Forker, Constraint, Transformer } from './Scenario';
import {
Scenario,
ScenarioEnv,
ScenarioFlags,
Property,
Initializer,
StaticConstraint,
DynamicConstraint,
Transformer
} from './Scenario';

class Loader<T, U, R> {
export interface ScenarioBuilder<T, U, R> {
(name: string, requirements: R, property: Property<T, U>): void;
only: (name: string, requirements: R, property: Property<T, U>) => void;
skip: (name: string, requirements: R, property: Property<T, U>) => void;
}

export class Loader<T, U, R> {
scenarios: { [name: string]: Scenario<T, U, R> };
constraints?: StaticConstraint<T>[];
initializer?: Initializer<T>;
transformer?: Transformer<T, U>;

static instance: any;

static get<T, U, R>(): Loader<T, U, R> {
if (!this.instance)
throw new Error('Loader not initialized');
return this.instance;
}

static async load<T, U, R>(glob = 'scenario/**.ts'): Promise<Loader<T, U, R>> {
if (this.instance)
throw new Error('Loader already initialized');
return await (this.instance = new Loader() as Loader<T, U, R>).load(glob);
}

async load(glob = 'scenario/**.ts'): Promise<this> {
for (let entry of await fg(glob))
await import(path.join(process.cwd(), entry));
return this;
}

constructor() {
this.scenarios = {};
}

configure(
constraints: StaticConstraint<T>[],
initializer: Initializer<T>,
transformer: Transformer<T, U>,
): this {
this.constraints = constraints;
this.initializer = initializer;
this.transformer = transformer;
return this;
}

env(): ScenarioEnv<T, U> {
if (!this.constraints || !this.initializer || !this.transformer)
throw new Error('Loader not configured');
return this as ScenarioEnv<T, U>;
}

scenarioFun(
constraints: DynamicConstraint<T, R>[]
): ScenarioBuilder<T, U, R> {
const addScenarioWithOpts =
(flags: ScenarioFlags) => (name: string, requirements: R, property: Property<T, U>) => {
this.addScenario(name, constraints, requirements, property, flags);
};
return Object.assign(addScenarioWithOpts(null), {
only: addScenarioWithOpts('only'),
skip: addScenarioWithOpts('skip'),
});
}

addScenario(
name: string,
constraints: DynamicConstraint<T, R>[],
requirements: R,
property: Property<T, U>,
initializer: Initializer<T>,
transformer: Transformer<T, U>,
forker: Forker<T>,
constraints: Constraint<T, R>[],
flags: ScenarioFlags = null
) {
if (this.scenarios[name]) {
if (this.scenarios[name])
throw new Error(`Duplicate scenarios by name: ${name}`);
}
this.scenarios[name] = new Scenario<T, U, R>(
name,
constraints,
requirements,
property,
initializer,
transformer,
forker,
constraints,
this.env(),
flags
);
}

getScenarios(): { [name: string]: Scenario<T, U, R> } {
return this.scenarios;
}
}

let loader: any;

function setupLoader<T, U, R>() {
if (loader) {
throw new Error('Loader already initialized');
}

loader = new Loader<T, U, R>();
}

export function getLoader<T, U, R>(): Loader<T, U, R> {
if (!loader) {
throw new Error('Loader not initialized');
}

return <Loader<T, U, R>>loader;
}

export async function loadScenarios<T, U, R>(glob: string): Promise<{ [name: string]: Scenario<T, U, R> }> {
setupLoader<T, U, R>();

const entries = await fg(glob); // Grab all potential scenario files

for (let entry of entries) {
let entryPath = path.join(process.cwd(), entry);

/* Import scenario file */
await import(entryPath);
/* Import complete */
splitScenarios(): [Scenario<T, U, R>[], Scenario<T, U, R>[]] {
const scenarios = Object.values(this.scenarios);
const rest = scenarios.filter(s => s.flags === null);
const only = scenarios.filter(s => s.flags === 'only');
const skip = scenarios.filter(s => s.flags === 'skip');
if (only.length > 0) {
return [only, skip.concat(rest)];
} else {
return [rest, skip];
}
}

return loader.getScenarios();
}
28 changes: 25 additions & 3 deletions plugins/scenario/worker/Report.ts → plugins/scenario/Report.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
import { Result } from './Parent';
import { diff as showDiff } from 'jest-diff';
import * as fs from 'fs/promises';

export interface Result {
base: string;
file: string;
scenario: string;
gasUsed?: number;
numSolutionSets?: number;
elapsed?: number;
error?: Error;
trace?: string;
diff?: { actual: any, expected: any };
skipped?: boolean;
}

export interface ConsoleFormatOptions { }
export interface JsonFormatOptions {
output?: string;
Expand All @@ -20,6 +32,15 @@ export function pluralize(n, singular, plural = null) {
}
}

export function defaultFormat(): FormatConfig {
return {
console: {},
json: {
output: 'scenario-results.json'
}
};
}

async function showReportConsole(results: Result[], _consoleOptions: ConsoleFormatOptions, _startTime: number, _endTime: number) {
const statsPer: object = {}, getStats = (base) => {
return statsPer[base] = statsPer[base] || {
Expand Down Expand Up @@ -162,11 +183,12 @@ async function showJsonReport(results: Result[], jsonOptions: JsonFormatOptions,
}
}

export async function showReport(results: Result[], format: FormatConfig, startTime: number, endTime: number) {
export async function showReport(results: Result[], startTime: number, endTime: number, formatConfig?: FormatConfig) {
const format = formatConfig ?? defaultFormat();
if (format.console) {
await showReportConsole(results, format.console, startTime, endTime);
}
if (format.json) {
await showJsonReport(results, format.json, startTime, endTime);
}
}
}
Loading

0 comments on commit 4d69c8a

Please sign in to comment.