Skip to content

feat(keepers): v2 expiry keeper, engine keeper instance, pager checks (#258) - #281

Merged
robertleifke merged 3 commits into
mainfrom
feat/v2-keepers
Oct 27, 2026
Merged

robertleifke merged 3 commits into
mainfrom
feat/v2-keepers

Conversation

@robertleifke

Copy link
Copy Markdown
Contributor

Part of #258: the rest of the v2 keepers after the forward publisher.

Based on #279 (feat/v2-forward-publisher). It contains that PR's commit, so merge #279 first. Only the second commit (cfe8824) is new here.

What changed

Expiry keeper v2 (services/perp-feeds/src/expiry-keeper.ts, run by forward-main with FUTURE_VERSION=v2):

  • coverPoolDeficit on v2 treats the pool as cash only. Net = the pool account's cash (the engine's accId / cashAsset). A loser pays what getPendingVM says it owes, in USDC. Losers are settled largest first while the cash is below -$1, at most SETTLE_PER_TICK a tick. A deficit with no loser left alerts future-pool-deficit-stranded once per episode, naming the base.
  • Nothing per base: no escrow leg, no "loser's cNGN first", no BASE_ASSET_TYPE/assetMap/spot feed read. Every v2 series on an engine shares pool CI: cover infra/ and alert when main goes red #37, so each series' keeper sees the same cash and settles its own losers.
  • Fixing and close-out are unchanged (same calls on v2). v1's path is untouched.
  • forward-main now runs the pool cover and expiry keeper in v2 mode too (feat(perp-feeds): v2 forward publisher mode, per series (#258) #279 had skipped them).

Engine keeper, second instance (services/perp-keeper):

  • PERP is optional. With none, nothing perp is read: cap, inherited perp, --settle-frozen refuses.
  • New CNGN_SPOT_FEED prices cNGN from the engine's spot feed instead of the perp index.
  • FUTURE takes a comma-separated list, one per series. Each cap is watched on its own (future-oi-cap-<address>). /health adds perp, futures, cngnSpotFeed.
  • A v1 config (one FUTURE, PERP, no CNGN_SPOT_FEED) reads as before.
  • contracts/risk-core/scripts/ops/engine-keeper-v2.env + numo-engine-keeper-v2.service (not installed). Values: MANAGER v2, POOL_ACCOUNT 37, FUTURE cNGN-30NOV26, START_BLOCK 53164124, port 9466, own SSM key /numo/keeper/engine_v2_keeper_key, DRY_RUN=true. KEEPER_ACCOUNT is left blank: the account doesn't exist yet.

Pager (check_perp_pager.py):

  • Reads each v2 series from FUTURE_V2_ARTIFACTS (comma-separated, default deployments/8453/MARGIN_ENGINE_V2.json; a missing artifact is skipped). Per series: one side's notional = contracts × contractSize × mark, plus margin rates and open/closed.
  • Per engine: the pool as cash only, with no reserve target and no escrow-cap check. New keys future-pool-borrowing-v2 and future-pool-deficit-stuck-v2 use the same $1 / $250 / 75 min thresholds as v1, and each engine has its own deficit timer.
  • sm-coverage now sums what every future needs from the one SecurityModule: v1's fitted rule plus sm_required_usd_linear per v2 series.
  • Optional ENGINE_V2_KEEPER_HEALTH_URL adds keeper-unhealthy-v2 / insolvent-account-v2 (perp-pager.env has it commented out).
  • The state file records futureManagers (v1 and v2) for propose_margin_engine_v2_batch.py's pager gate.

Anvil/fork suites:

  • services/perp-feeds/src/expiry-v2.anvil.e2e.test.ts: a real fixing on the v2 FutureAsset, for a cNGN and an EURC series.
  • services/perp-keeper/src/engine-keeper-v2.anvil.test.ts: the live v2 engine on a Base fork, configured from the v2 env. The vault opens the cap, one contract trades, margin is raised, and the keeper starts the auction and bids from a v2 account. scripts/e2e-engine.sh runs both engine suites.
  • The v1 suites are unchanged.

How tested

  • perp-feeds: pnpm run check clean. ANVIL_RPC_URL=http://127.0.0.1:8599 pnpm test: 159 tests, 159 pass, 0 fail. That includes the v1 and v2 forward/expiry anvil suites and the new v2 cash-pool unit tests, one of which uses an EURC series.
  • perp-keeper: pnpm run check clean. pnpm test: 47 tests, 47 pass. These cover the v2 config (no PERP, CNGN_SPOT_FEED, FUTURE list with a placeholder EURC future) and readHealth with no perp.
  • BASE_RPC_URL=… ./scripts/e2e-engine.sh (fork at head): 4/4 pass, v1 engine and v2 engine. On v2 the keeper started an auction on the account under maintenance and bid 29.88% from a v2 bid account.
  • BASE_RPC_URL=… ./scripts/e2e.sh (perp stack deploy on a fork): 5/5 pass.
  • check_perp_pager.py --self-test: ok. abi_selectors.py --self-test: ok (30 scripts, full forge build of risk-core and execution). The propose_margin_engine_v2_batch, propose_margin_engine_batch and propose_perp_enable_batch self-tests: ok.
  • One read-only pager run against Base, with no provider and a scratch state file. It read v1 (one side $11,348), v2 cNGN-30NOV26 (closed, $0), pool CI: cover infra/ and alert when main goes red #37 $0.00 cash, SM $3,250 against $1,860. State futureManagers = [v1, v2].
  • ExpiryKeeper v2 on a Base fork with future-feeds-v2-cngn-30nov26.env: coverPoolDeficit → net 0, nothing settled; tick → before-window.

What's left / decisions

  • The v2 engine keeper's funding account (KEEPER_ACCOUNT), its EOA and SSM key, and its cash. The v2 batch's keeper gate wants about a full side's IM at the cap (~$2,790 at 0.000743).
  • MAX_CNGN_INVENTORY (8,000,000) and MAX_EURC_INVENTORY (40,000) in engine-keeper-v2.env are copied from v1's. Revisit them, EURC's especially at the eurc-cap step.
  • Box deploy (not done): the exchange-future-v2 checkout, both v2 units in DRY_RUN first, and ENGINE_V2_KEEPER_HEALTH_URL in /etc/numo/perp-pager.env. The pager reads MARGIN_ENGINE_V2.json by default as soon as its checkout includes this, which is what the v2 pager gate needs. Record each in OPS_BOX_CHANGES.md.
  • EURC-30NOV26: once deployed, it needs its own artifact (with baseSymbol: "EURC") added to FUTURE_V2_ARTIFACTS, its future added to the v2 engine keeper's FUTURE, and a future-feeds-v2-eurc-30nov26.env instance. No code changes.
  • sm-coverage sums the series' needs: conservative, since cNGN and EUR moves are not one step.

@robertleifke
robertleifke merged commit 840526a into main Oct 27, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant