Skip to content

Commit

Permalink
Ruleset Unit Tests and tweaks (#445)
Browse files Browse the repository at this point in the history
* unit tests for ruleset

* remove comment

* add tests

* progress

* test progress; running into Max Misttrack rate limit

* misttrack api, wip

* save wip

* Adds snapshot usage

* cleanup

* Create khaki-queens-brush.md

* post merge

* unnecessary yarn dev

* Remove log

* Update yarn.lock

* PR Feedback

* yarn install
  • Loading branch information
panieldark authored Sep 13, 2023
1 parent 15dbe9d commit 016af4b
Show file tree
Hide file tree
Showing 12 changed files with 3,893 additions and 242 deletions.
5 changes: 5 additions & 0 deletions .changeset/khaki-queens-brush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nocturne-xyz/deposit-screener": minor
---

Add unit tests, update internals
2 changes: 1 addition & 1 deletion actors/deposit-screener/.env.dev
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ SUBGRAPH_URL=http://localhost:8000/subgraphs/name/nocturne
RPC_URL=http://127.0.0.1:8545

TX_SIGNER_KEY=0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e
ATTESTATION_SIGNER_KEY=0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e
ATTESTATION_SIGNER_KEY=0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e
2 changes: 1 addition & 1 deletion actors/deposit-screener/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"prettier:check": "prettier --check ./src",
"prettier:write": "prettier --write ./src",
"screener-cli": "npx ts-node src/cli/index.ts",
"test:unit": ""
"test:unit": "mocha --timeout 20000 -r ts-node/register test/** --exclude test/snapshots/** -r dotenv/config"
},
"dependencies": {
"@nocturne-xyz/config": "workspace:^",
Expand Down
54 changes: 0 additions & 54 deletions actors/deposit-screener/scripts/testMisttrackCallAndRuleset.ts

This file was deleted.

102 changes: 0 additions & 102 deletions actors/deposit-screener/scripts/testRulesets.ts

This file was deleted.

101 changes: 61 additions & 40 deletions actors/deposit-screener/src/screening/checks/RuleSet.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { ScreeningDepositRequest } from "..";
import { API_CALLS, ApiCallKeys, ApiMap, CallReturnData } from "./apiCalls";
import {
API_CALL_MAP,
ApiCallNames,
ApiCallToReturnType,
CallReturnData,
} from "./apiCalls";
export interface Rejection {
type: "Rejection";
reason: string;
Expand Down Expand Up @@ -34,41 +39,44 @@ const ACTION_NOT_TRIGGERED = {
type: "ActionNotTriggered",
} as const;

export interface RuleParams<K extends keyof ApiMap> {
export interface RuleParams<C extends ApiCallNames> {
name: string;
call: K;
threshold: (data: ApiMap[K]) => boolean;
call: C;
threshold: (data: ApiCallToReturnType[C]) => boolean;
action: Rejection | DelayAction;
}

export type PartialRuleParams<K extends keyof ApiMap> = Omit<
RuleParams<K>,
export type PartialRuleParams<C extends ApiCallNames> = Omit<
RuleParams<C>,
"action"
>;

export interface CombinedRulesParams<T extends ReadonlyArray<keyof ApiMap>> {
partials: { readonly [K in keyof T]: PartialRuleParams<T[K]> };
export interface CombinedRulesParams<T extends ReadonlyArray<ApiCallNames>> {
// Essentially a readonly object type, i.e, [PartialRuleParams, PartialRuleParams, ...]
partialRules: { readonly [C in keyof T]: PartialRuleParams<T[C]> };
action: Rejection | DelayAction;
applyIf: "Any" | "All";
}

export type CachedApiCallData = Partial<Record<ApiCallNames, CallReturnData>>;

export interface RuleLike {
next: RuleLike | null;
name: string;
check: (
deposit: ScreeningDepositRequest,
cache: Record<ApiCallKeys, ApiMap[ApiCallKeys]>
cache: CachedApiCallData
) => Promise<Rejection | DelayAction | typeof ACTION_NOT_TRIGGERED>;
}

export class Rule<K extends keyof ApiMap> implements RuleLike {
export class Rule<C extends ApiCallNames> implements RuleLike {
public next: RuleLike | null = null;
public readonly name: RuleParams<K>["name"];
private threshold: RuleParams<K>["threshold"];
private call: RuleParams<K>["call"];
private action: RuleParams<K>["action"];
public readonly name: RuleParams<C>["name"];
private threshold: RuleParams<C>["threshold"];
private call: RuleParams<C>["call"];
private action: RuleParams<C>["action"];

constructor({ name, call, threshold, action }: RuleParams<K>) {
constructor({ name, call, threshold, action }: RuleParams<C>) {
this.name = name;
this.call = call;
this.threshold = threshold;
Expand All @@ -77,54 +85,63 @@ export class Rule<K extends keyof ApiMap> implements RuleLike {

async check(
deposit: ScreeningDepositRequest,
cache: Record<ApiCallKeys, ApiMap[ApiCallKeys]>
cache: CachedApiCallData
): Promise<Rejection | DelayAction | typeof ACTION_NOT_TRIGGERED> {
if (!cache[this.call]) {
cache[this.call] = await API_CALLS[this.call](deposit);
cache[this.call] = await API_CALL_MAP[this.call](deposit);
}
const data = cache[this.call] as ApiMap[K];
const data = cache[this.call] as ApiCallToReturnType[C];

return this.threshold(data) ? this.action : ACTION_NOT_TRIGGERED;
}
}

export class CompositeRule<T extends ReadonlyArray<keyof ApiMap>>
export class CompositeRule<T extends ReadonlyArray<ApiCallNames>>
implements RuleLike
{
public next: RuleLike | null = null;
public readonly name: string;
private partials: CombinedRulesParams<T>["partials"];
private partialRules: CombinedRulesParams<T>["partialRules"];
private action: CombinedRulesParams<T>["action"];
private predicateFn: "some" | "every"; // corresponds to Array.prototype.some and Array.prototype.every

constructor(params: CombinedRulesParams<T>) {
this.name = `Composite(${params.partials.map((r) => r.name).join(", ")})`;
this.partials = params.partials;
this.name = `Composite(${params.partialRules
.map((r) => r.name)
.join(", ")}):${params.applyIf}`;
this.partialRules = params.partialRules;
this.action = params.action;
this.predicateFn = params.applyIf === "Any" ? "some" : "every";
}

async check(
deposit: ScreeningDepositRequest,
cache: Record<ApiCallKeys, CallReturnData>
cache: CachedApiCallData
): Promise<Rejection | DelayAction | typeof ACTION_NOT_TRIGGERED> {
const shouldApply = this.partials[this.predicateFn](async (partial) => {
if (!cache[partial.call]) {
cache[partial.call] = await API_CALLS[partial.call](deposit);
}
const data = cache[partial.call] as ApiMap[typeof partial.call];
return partial.threshold(data);
});
return shouldApply ? this.action : ACTION_NOT_TRIGGERED;
const results = await Promise.all(
this.partialRules.map(async (partial) => {
if (!cache[partial.call]) {
cache[partial.call] = await API_CALL_MAP[partial.call](deposit);
}
const data = cache[
partial.call
] as ApiCallToReturnType[typeof partial.call];

return partial.threshold(data);
})
);
const shouldApplyRule = results[this.predicateFn]((_) => _);
return shouldApplyRule ? this.action : ACTION_NOT_TRIGGERED;
}
}

export class RuleSet {
private head: RuleLike | null = null;
private tail: RuleLike | null = null;
private delaySeconds;
private readonly baseDelaySeconds;

constructor({ baseDelaySeconds = 0 }: { baseDelaySeconds?: number } = {}) {
this.delaySeconds = baseDelaySeconds;
this.baseDelaySeconds = baseDelaySeconds;
}

private _add(ruleLike: RuleLike) {
Expand All @@ -136,27 +153,31 @@ export class RuleSet {
this.tail = ruleLike;
}

add<K extends keyof ApiMap>(ruleParams: RuleParams<K>): RuleSet {
add<C extends ApiCallNames>(ruleParams: RuleParams<C>): RuleSet {
const rule = new Rule(ruleParams);
this._add(rule);
return this;
}

combineAndAdd<T extends ReadonlyArray<keyof ApiMap>>(
combineAndAdd<T extends ReadonlyArray<ApiCallNames>>(
compositeParams: CombinedRulesParams<T>
): RuleSet {
const composite = new CompositeRule(compositeParams);
this._add(composite);
return this;
}

async check(deposit: ScreeningDepositRequest): Promise<Rejection | Delay> {
async check(
deposit: ScreeningDepositRequest,
cache: CachedApiCallData = {}
): Promise<Rejection | Delay> {
let delaySeconds = this.baseDelaySeconds;
let currRule = this.head;
const cache: Record<string, CallReturnData> = {};
const rulesLogList: {
ruleName: string;
result: Awaited<ReturnType<RuleLike["check"]>>;
}[] = [];

while (currRule !== null) {
const result = await currRule.check(deposit, cache);
rulesLogList.push({
Expand All @@ -167,14 +188,14 @@ export class RuleSet {
console.log(`Screener execution for deposit:`, deposit, rulesLogList);
return result;
} else if (result.type === "Delay") {
this.delaySeconds = APPLY_DELAY_OPERATION[result.operation](
this.delaySeconds,
delaySeconds = APPLY_DELAY_OPERATION[result.operation](
delaySeconds,
result.operation === "Add" ? result.valueSeconds : result.factor
);
}
currRule = currRule.next;
}
console.log(`Screener execution for deposit:`, deposit, rulesLogList);
return { type: "Delay", timeSeconds: this.delaySeconds };
return { type: "Delay", timeSeconds: delaySeconds };
}
}
Loading

0 comments on commit 016af4b

Please sign in to comment.