diff --git a/contracts/risk-core/DEPLOYED_ADDRESSES.md b/contracts/risk-core/DEPLOYED_ADDRESSES.md index 39bc30ea..ec971f9b 100644 --- a/contracts/risk-core/DEPLOYED_ADDRESSES.md +++ b/contracts/risk-core/DEPLOYED_ADDRESSES.md @@ -176,7 +176,7 @@ Artifacts: `deployments/8453/MARGIN_ENGINE_V2.json`, `MARGIN_ENGINE_V2_VAULT_ACT the liquidation keeper, the pager and SM coverage, and markets-service listing a second series (#228) under a `cNGN-` symbol, not `USDC-cNGN-30NOV26`, which v1's series holds. -### EURC-30NOV26 on MarginEngine v2 — DEPLOYED 2026-10-30 (closed), setup pending +### EURC-30NOV26 on MarginEngine v2 — DEPLOYED and SET UP 2026-10-30 (closed) A second v2 series, on v2's market 2 (EUR): USDC per EURC, 1,000 EURC a contract, VM in USDC through pool #37, expiry 1796054400 (2026-11-30 16:00 UTC; last trade 15:00), IM 6% / MM 4%, carry -1.5% (an assumption), cap 0 until enable @@ -194,8 +194,9 @@ two owner-only engine setters in the vault's setup batch. are nominated to the vault (`pendingOwner`); the cap is 0. Artifacts: `EURC_FUTURE_V2.json`, `EURC_FUTURE_V2_VAULT_ACTIONS.json`, `EURC_FUTURE_V2_ENABLE_VAULT_ACTIONS.json`. The deployer sends nothing else until setup lands. -2. `scripts/ops/propose_eurc_future_v2_batch.py eurc-future-setup`: accept both; `engineV2.whitelistAsset(future, 2, - Future)`; `engineV2.setFutureMarginRequirements(2, 0.04e18, 0.06e18)`. +2. **Setup landed 2026-10-30 05:35 UTC** (`eurc-future-setup`, proposed from the box's exchange-ops at 23e5037 and + approved by hand in MPCVault): the vault owns both; `engineV2.whitelistAsset(future, 2, Future)`; + `engineV2.setFutureMarginRequirements(2, 0.04e18, 0.06e18)`. The deployer is free again. 3. Off chain: forward publisher for this feed, pager and v2 keeper reading the future, markets-service listing `EURC-30NOV26`. 4. `eurc-future-enable`: `future.setTotalPositionCap(v2, 18e18)`, `tradeModule.setDatedFutureAsset(future, true)`. diff --git a/contracts/risk-core/scripts/ops/check_perp_pager.py b/contracts/risk-core/scripts/ops/check_perp_pager.py index dd38aec1..43dc0f77 100644 --- a/contracts/risk-core/scripts/ops/check_perp_pager.py +++ b/contracts/risk-core/scripts/ops/check_perp_pager.py @@ -579,7 +579,10 @@ def future_v2_artifacts() -> list[tuple[Path, dict]]: def series_label(future: dict) -> str: - """cNGN-30NOV26-style, for messages: the artifact's base (default cNGN, MARGIN_ENGINE_V2.json names none) and expiry.""" + """cNGN-30NOV26-style, for messages: the artifact's symbol (EURC_FUTURE_V2.json names one), else its base (default + cNGN, MARGIN_ENGINE_V2.json names none) and expiry.""" + if future.get("symbol"): + return future["symbol"] return f"{future.get('baseSymbol', 'cNGN')}-{time.strftime('%d%b%y', time.gmtime(int(future['expiry']))).upper()}" @@ -1119,6 +1122,8 @@ def self_test() -> int: assert abs(parts["EURC-30NOV26"] - 10 * 1_000 * 1.1625 * 0.15) < 1e-6 assert series_label({"expiry": 1796047200}) == "cNGN-30NOV26" assert series_label({"expiry": 1796047200, "baseSymbol": "EURC"}) == "EURC-30NOV26" + # EURC_FUTURE_V2.json names its series in `symbol` and has no baseSymbol: read as cNGN it would share cNGN's label + assert series_label({"expiry": 1796054400, "symbol": "EURC-30NOV26"}) == "EURC-30NOV26" # What the run read goes to the state file: each manager once, each future asset (v1's, then every v2 series). watched: dict = {} record_watched(watched, {"manager": "0xv1", "future": "0xf1"}) diff --git a/contracts/risk-core/scripts/ops/engine-keeper-v2.env b/contracts/risk-core/scripts/ops/engine-keeper-v2.env index f47724bf..03808d76 100644 --- a/contracts/risk-core/scripts/ops/engine-keeper-v2.env +++ b/contracts/risk-core/scripts/ops/engine-keeper-v2.env @@ -3,7 +3,7 @@ # from SSM in the unit's ExecStart. Addresses from risk-core deployments/8453/MARGIN_ENGINE_V2.json. v1's engine keeper # (engine-keeper.env) moved here that day and is disabled: v1's USDC-cNGN-30NOV26 has no liquidator until it settles. # -# Per engine, not per series: a v2 series (EURC-30NOV26 once listed) is one more address in FUTURE. +# Per engine, not per series: a v2 series is one more address in FUTURE. CHAIN_ID=8453 # The keeper's funding account under v2, created by its EOA 0x753f…f83b (SubAccounts.createAccount(eoa, 0x0705…0950)) # and funded with v1 #33's 5,000 USDC cash, 2026-10-28. @@ -12,8 +12,9 @@ SUB_ACCOUNTS=0x7019244E25FA416e6Ca2ed2F3cA25277aef72843 MANAGER=0x0705219a1594d5Fc5dC2573da4082cAf3e1B0950 AUCTION=0xd07176B006cE8246e930d37b8bc44B1154f8c4cE CASH=0xA74E49b4Ed7cb176bc02ef4D8a1A3240C9aD4272 -# No PERP: v2 lists none. Comma-separated, one per series. -FUTURE=0xcACEe66ad41fD1472a47D7D5Fe372FFbFac9AA75 +# No PERP: v2 lists none. Comma-separated, one per series: cNGN-30NOV26, then EURC-30NOV26 (EURC_FUTURE_V2.json, +# added 2026-10-30 after its setup landed). +FUTURE=0xcACEe66ad41fD1472a47D7D5Fe372FFbFac9AA75,0x6Ff7084D6F1760a57b9475a41F54b12279cD7744 POOL_ACCOUNT=37 SECURITY_MODULE_ACCOUNT=21 # v2's deploy (2026-10-27, blocks 53164124–53164160): no account was under it before. diff --git a/contracts/risk-core/scripts/ops/future-feeds-v2-eurc-30nov26.env b/contracts/risk-core/scripts/ops/future-feeds-v2-eurc-30nov26.env new file mode 100644 index 00000000..7c390243 --- /dev/null +++ b/contracts/risk-core/scripts/ops/future-feeds-v2-eurc-30nov26.env @@ -0,0 +1,35 @@ +# /etc/numo/future-feeds-v2-eurc-30nov26.env (numo-future-feeds-v2@eurc-30nov26.service), for EURC-30NOV26 on +# MarginEngine v2: from risk-core/deployments/8453/EURC_FUTURE_V2.json (deployed 2026-10-30, setup landed the same +# day). The box's copy adds DRY_RUN. A change here is copied to the box and recorded in OPS_BOX_CHANGES.md the same +# day. Holds no secret. +CHAIN_ID=8453 +FUTURE_VERSION=v2 +BASE_SYMBOL=EURC +DATA_SUBMITTER=0xe0C06DD245f1e8C8bC516c66C66e64648987F912 +# The EURC spot feed (USD per EURC, Chainlink EURC/USD), v2 market 2's spot feed: read as is. +INDEX_FEED=0xaaE62Ae5df3747B8f43F895561043FB77D97C435 +FORWARD_FEED=0xB67fCd9A66efc3C12Be3Ea2bBB97385004394d14 +FUTURE_ASSET=0x6Ff7084D6F1760a57b9475a41F54b12279cD7744 +# The expiry, 2026-11-30 16:00 UTC: two hours after cNGN-30NOV26, so the two series do not share a sub id. +FUTURE_SUB_ID=1796054400 +# MarginEngine v2. Its VM pool is account #37, cash only, shared with cNGN-30NOV26. +FUTURE_MANAGER=0x0705219a1594d5Fc5dC2573da4082cAf3e1B0950 +# Annual EUR-over-USD carry, 18dp, signed: -1.5% (an assumption, ECB ~2% against fed funds ~3.5%), a forward +# premium. The figure the deploy's initial mark was checked against (EURC_FUTURE_V2.json carry). +CARRY=-15000000000000000 +# The forward KMS key (0xd65F…3650), the feed's only signer: each feed's EIP-712 domain binds signatures to it. +FORWARD_SIGNER_KMS_KEY_ID=arn:aws:kms:us-east-1:957261948743:key/4312f074-ddcb-4db3-89d5-02d042beaacd +# A relayer of this instance's own (run-with-ssm-forward.sh): sharing cNGN's would race it for nonces. +FORWARD_RELAYER_SSM_NAME=forward_relayer_key_v2_eurc +MARKETS_SERVICE_URL=https://api.numofx.com +FORWARD_TICK_MS=60000 +# Each side of the futures book must fill this much (USDC) before it sets the mark; else carry. A contract is 1,000 +# EURC, ~$1,120: about 2 contracts a side. +FORWARD_MIN_BOOK_DEPTH_USDC=2000 +# The contract's own band (3% narrowing to 0.5% over 14 days) is read from the chain and clamps every mark. +FORWARD_MAX_BASIS_BPS=300 +FORWARD_SAFETY_BPS=50 +FORWARD_MARK_LATE_MS=7200000 +FIXING_SAMPLE_INTERVAL_MS=300000 +CLOSE_OUT_BATCH=20 +SETTLE_PER_TICK=10 diff --git a/contracts/risk-core/scripts/ops/perp-pager.env b/contracts/risk-core/scripts/ops/perp-pager.env index 6e9ea3fa..c565a281 100644 --- a/contracts/risk-core/scripts/ops/perp-pager.env +++ b/contracts/risk-core/scripts/ops/perp-pager.env @@ -10,3 +10,5 @@ KEEPER_ACCOUNT=23 # keeper-unhealthy-v2 / insolvent-account-v2 for it. v2's futures are read from # deployments/8453/MARGIN_ENGINE_V2.json by default (FUTURE_V2_ARTIFACTS, comma-separated, adds a series). ENGINE_V2_KEEPER_HEALTH_URL=http://127.0.0.1:9466/health +# Every v2 series, one artifact each, from the pager's own checkout: cNGN-30NOV26 and EURC-30NOV26 (2026-10-30). +FUTURE_V2_ARTIFACTS=/home/ec2-user/exchange-pager/contracts/risk-core/deployments/8453/MARGIN_ENGINE_V2.json,/home/ec2-user/exchange-pager/contracts/risk-core/deployments/8453/EURC_FUTURE_V2.json diff --git a/contracts/risk-core/test/fork/EurcFutureV2Fork.t.sol b/contracts/risk-core/test/fork/EurcFutureV2Fork.t.sol index 691d87e3..d951cffc 100644 --- a/contracts/risk-core/test/fork/EurcFutureV2Fork.t.sol +++ b/contracts/risk-core/test/fork/EurcFutureV2Fork.t.sol @@ -118,8 +118,12 @@ contract EurcFutureV2Fork is Test { uint nonce; IEMatching matching; + // The block before EURC-30NOV26's deploy (2026-10-30, 53273869): its setup has since landed, and the deploy refuses an + // engine whose EUR market already lists a future, so the suite rehearses from just before it. + uint constant FORK_BLOCK = 53_273_868; + function setUp() public { - vm.createSelectFork(vm.envString("BASE_RPC_URL")); + vm.createSelectFork(vm.envString("BASE_RPC_URL"), FORK_BLOCK); script = new DeployEurcFutureV2(); params = script.loadChainParams(); params.forwardSigner = vm.addr(forwardPk); diff --git a/infra/aws/variables.tf b/infra/aws/variables.tf index 05682409..66bb9544 100644 --- a/infra/aws/variables.tf +++ b/infra/aws/variables.tf @@ -322,6 +322,16 @@ variable "future_series" { base_asset = "cNGN" contract_size = "1000000" amount_scale = "1000000000000000" # 1,000 cNGN: 1e18 per 1,000,000 cNGN contract + }, { + # EURC-30NOV26 (risk-core EURC_FUTURE_V2.json), listed closed 2026-10-30: USDC per EURC, 1,000 EURC a contract, + # 16:00 UTC so its sub id is not cNGN's. + asset_address = "0x6Ff7084D6F1760a57b9475a41F54b12279cD7744" + expiry = 1796054400 + manager_address = "0x0705219a1594d5Fc5dC2573da4082cAf3e1B0950" + kind = "linear" + base_asset = "EURC" + contract_size = "1000" + amount_scale = "1000000000000000" # 1 EURC: 1e18 per 1,000 EURC contract }] validation {