Skip to content

Commit 5951059

Browse files
adachi-440claude
andcommitted
ci: exercise the liquidity pull against a freshly deployed chain
The deploy+backtest job replayed only `calm`, which carries no stress events, so nothing in CI touched the one part of the overlay that writes to the *venues* rather than to the price — even though `config/regimes/**` and `sdk/src/protocols/**` are already in its path filter, so it was running and proving nothing about this. That gap is not theoretical. Everything in issue #52 was verified against a state dump generated in August from a different commit, and the Balancer join had to be rebuilt because this deployment's WeightedPool does not implement ALL_TOKENS_IN_FOR_EXACT_BPT_OUT — a mismatch invisible until a real pool refuses the call. This job is the only place that puts the withdrawal in front of venues the deployer just built. 80 blocks rather than the 12 used elsewhere: the pull's trapezoid is 21 blocks and its start is drawn from windowFrac's upper bound, so a shorter run cannot hold the window and fails fast instead of silently mis-composing the pair. Asserts what the exit code cannot: that depth was withdrawn from all three venues, that no write failed or reverted, and that every venue came back to its seeded depth. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 94d1317 commit 5951059

1 file changed

Lines changed: 34 additions & 0 deletions

File tree

.github/workflows/deploy-backtest.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,40 @@ jobs:
9292
- name: Backtest against the dump
9393
run: npm run backtest -- --regime calm --seed 101 --blocks 12 --seconds 90
9494

95+
# The liquidity pull (issue #52) is the one part of the stress overlay that writes to the
96+
# venues instead of to the price, and `calm` does not carry it. It is also the part most
97+
# exposed to what the deployer actually produced: the Balancer join had to be rebuilt because
98+
# this deployment's WeightedPool does not implement ALL_TOKENS_IN_FOR_EXACT_BPT_OUT, and that
99+
# class of mismatch is invisible until a real pool refuses the call.
100+
#
101+
# 80 blocks, not the 12 used above: the pull's trapezoid is 21 blocks and its start is drawn
102+
# from windowFrac's upper bound (0.7), so a shorter run cannot hold the window and fails fast
103+
# rather than silently mis-composing the pair (see EventSchedule's alignWith check).
104+
- name: Replay the crash regime (liquidity pull across all three venues)
105+
run: |
106+
npm run backtest -- --regime crash --seed 606 --blocks 80
107+
node -e '
108+
const { readdirSync, readFileSync } = require("node:fs");
109+
const dir = readdirSync("runs").filter((d) => /^\d{4}-/.test(d)).sort().at(-1);
110+
const events = readFileSync(`runs/${dir}/events.jsonl`, "utf8")
111+
.split("\n").filter(Boolean).map((l) => JSON.parse(l));
112+
const pulls = events.filter((e) => e.type === "stress_liquidity_pull");
113+
const venues = new Set(pulls.map((e) => e.venue));
114+
for (const v of ["uniswap", "balancer", "curve"])
115+
if (!venues.has(v)) throw new Error(`no depth was withdrawn from ${v}`);
116+
const bad = events.filter((e) =>
117+
["stress_liquidity_pull_failed", "stress_liquidity_pull_reverted",
118+
"stress_liquidity_task_failed", "stress_liquidity_teardown_failed"].includes(e.type));
119+
if (bad.length) throw new Error(`liquidity pull writes failed: ${JSON.stringify(bad.slice(0, 3))}`);
120+
// The window has to end with the venues where it started, or the next run on a shared
121+
// chain trades a pool this event drained.
122+
const incomplete = events.filter((e) => e.type === "stress_liquidity_restore_incomplete");
123+
if (incomplete.length) throw new Error(`depth was not restored: ${JSON.stringify(incomplete)}`);
124+
const restored = events.filter((e) => e.type === "stress_liquidity_restored");
125+
if (restored.length < 3) throw new Error(`expected a restore per venue, got ${restored.length}`);
126+
console.log(`liquidity pull ok: ${pulls.length} writes across ${[...venues].join(", ")}`);
127+
'
128+
95129
# The matrix path is what the competition actually runs, and it is a different code path from
96130
# a single --regime run: it writes matrix.json / standings.json and has to survive a scenario
97131
# that fails without abandoning the rest. Two scenarios is enough to exercise the loop.

0 commit comments

Comments
 (0)