melin_server/
app_factory.rs1use melin_app::app_factory::AppFactory;
10use melin_trading::trading_event::TradingEvent;
11use melin_types::types::{AccountId, CurrencyId, InstrumentSpec, Symbol};
12
13use crate::exchange_app::ServerApp;
14
15#[derive(Debug, Clone, Copy)]
20pub struct FactoryConfig {
21 pub accounts: u32,
23 pub instruments: u32,
25 pub max_orders_per_account: u32,
27 pub max_orders_per_second: u32,
30 pub max_orders_burst: u32,
33}
34
35#[derive(Debug, Clone, Copy)]
37pub struct Factory {
38 config: FactoryConfig,
39}
40
41impl Factory {
42 pub fn new(config: FactoryConfig) -> Self {
43 Self { config }
44 }
45}
46
47impl AppFactory for Factory {
48 type App = ServerApp;
49
50 fn empty(&self) -> ServerApp {
51 ServerApp(melin_exchange_core::exchange::Exchange::with_capacity())
52 }
53
54 fn prefault(&self, app: &mut ServerApp) {
55 app.0.prefault_seed(
56 self.config.accounts as usize,
57 self.config.instruments as usize,
58 );
59 }
60
61 fn apply_operator_policy(&self, app: &mut ServerApp) {
62 let restored_buckets = app.order_bucket_count();
73 let limiter_disabled =
74 self.config.max_orders_per_second == 0 || self.config.max_orders_burst == 0;
75 if limiter_disabled && restored_buckets > 0 {
76 tracing::warn!(
77 restored_buckets,
78 max_orders_per_second = self.config.max_orders_per_second,
79 max_orders_burst = self.config.max_orders_burst,
80 "config mismatch: snapshot carries rate-limit buckets but local limiter \
81 is disabled — primary and replica must run with matching values"
82 );
83 }
84
85 app.set_max_open_orders_per_account(self.config.max_orders_per_account);
86 app.set_max_orders_per_second(
87 self.config.max_orders_per_second,
88 self.config.max_orders_burst,
89 );
90
91 tracing::info!(
97 max_orders_per_account = self.config.max_orders_per_account,
98 max_orders_per_second = self.config.max_orders_per_second,
99 max_orders_burst = self.config.max_orders_burst,
100 "applied per-account order limits (SEC-03 cap, SEC-04 rate)"
101 );
102 }
103
104 fn seed_events(&self) -> Vec<TradingEvent> {
105 let mut events =
106 Vec::with_capacity(self.config.instruments as usize + self.config.accounts as usize);
107 for i in 0..self.config.instruments {
108 events.push(TradingEvent::AddInstrument {
109 spec: InstrumentSpec {
110 symbol: Symbol(i),
111 base: CurrencyId(i * 2),
112 quote: CurrencyId(i * 2 + 1),
113 },
114 });
115 }
116 for acct in 1..=self.config.accounts {
117 events.push(TradingEvent::ProvisionAccount {
118 account: AccountId(acct),
119 amount: u64::MAX / 4,
120 });
121 }
122 events
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 fn cfg(accounts: u32, instruments: u32) -> FactoryConfig {
131 FactoryConfig {
132 accounts,
133 instruments,
134 max_orders_per_account: 100,
135 max_orders_per_second: 1_000,
136 max_orders_burst: 100,
137 }
138 }
139
140 #[test]
141 fn seed_events_count_matches_config() {
142 let factory = Factory::new(cfg(5, 3));
143 let events = factory.seed_events();
144 assert_eq!(events.len(), 8);
146 }
147
148 #[test]
149 fn seed_events_order_is_instruments_then_accounts() {
150 let factory = Factory::new(cfg(2, 2));
151 let events = factory.seed_events();
152 assert!(matches!(events[0], TradingEvent::AddInstrument { .. }));
153 assert!(matches!(events[1], TradingEvent::AddInstrument { .. }));
154 assert!(matches!(events[2], TradingEvent::ProvisionAccount { .. }));
155 assert!(matches!(events[3], TradingEvent::ProvisionAccount { .. }));
156 }
157
158 #[test]
159 fn seed_events_empty_when_no_accounts_or_instruments() {
160 let factory = Factory::new(cfg(0, 0));
161 assert!(factory.seed_events().is_empty());
162 }
163
164 #[test]
165 fn empty_does_not_apply_policy() {
166 let factory = Factory::new(cfg(2, 2));
167 let app = factory.empty();
168 assert_ne!(app.max_open_orders_per_account(), 100);
169 }
170
171 #[test]
172 fn apply_operator_policy_overrides_default() {
173 let factory = Factory::new(cfg(2, 2));
174 let mut app = factory.empty();
175 factory.apply_operator_policy(&mut app);
176 assert_eq!(app.max_open_orders_per_account(), 100);
177 }
178}