cratefield_module_guardrails/
fakes.rs1#![allow(clippy::disallowed_types)]
10#![allow(clippy::missing_panics_doc)]
13
14use async_trait::async_trait;
15use std::collections::BTreeMap;
16use std::sync::{Arc, Mutex};
17use time::OffsetDateTime;
18
19use crate::action::{Chain, Request, TokenKey};
20use crate::audit::{AuditEntry, AuditError, AuditSink};
21use crate::caps::{LedgerError, Prices, PricesError, SpendEntry, SpendLedger};
22use crate::hygiene::{Allowance, AllowanceError, AllowanceSource};
23use crate::kill::{KillSwitch, KillSwitchError, Scope};
24use crate::simulate::{SimError, SimulationReport, Simulator};
25use crate::{SignError, WalletSigner};
26
27#[derive(Clone, Default)]
32pub struct MemoryKillSwitch {
33 engaged: Arc<Mutex<BTreeMap<String, String>>>,
34}
35
36impl MemoryKillSwitch {
37 #[must_use]
39 pub fn new() -> Self {
40 Self::default()
41 }
42
43 #[must_use]
45 pub fn engagements(&self) -> BTreeMap<String, String> {
46 self.engaged.lock().expect("unpoisoned").clone()
47 }
48}
49
50#[async_trait]
51impl KillSwitch for MemoryKillSwitch {
52 async fn engaged(&self, scope: &Scope) -> Result<bool, KillSwitchError> {
53 Ok(self
54 .engaged
55 .lock()
56 .expect("unpoisoned")
57 .contains_key(&scope.key()))
58 }
59
60 async fn engage(&self, scope: Scope, reason: &str) -> Result<(), KillSwitchError> {
61 self.engaged
62 .lock()
63 .expect("unpoisoned")
64 .insert(scope.key(), reason.to_owned());
65 Ok(())
66 }
67
68 async fn release(&self, scope: &Scope) -> Result<(), KillSwitchError> {
69 self.engaged
70 .lock()
71 .expect("unpoisoned")
72 .remove(&scope.key());
73 Ok(())
74 }
75}
76
77#[derive(Debug, Clone)]
82pub enum Script {
83 Report(SimulationReport),
85 Fail(SimError),
87}
88
89#[derive(Clone, Default)]
92pub struct ScriptedSimulator {
93 inner: Arc<Mutex<ScriptedSim>>,
94}
95
96#[derive(Default)]
97struct ScriptedSim {
98 script: Option<Script>,
99 calls: Vec<Request>,
100}
101
102impl ScriptedSimulator {
103 #[must_use]
106 pub fn new() -> Self {
107 Self::default()
108 }
109
110 #[must_use]
112 pub fn respond(self, report: SimulationReport) -> Self {
113 self.set(Script::Report(report));
114 self
115 }
116
117 #[must_use]
119 pub fn fail(self, error: SimError) -> Self {
120 self.set(Script::Fail(error));
121 self
122 }
123
124 #[must_use]
126 pub fn clean(self) -> Self {
127 self.respond(SimulationReport::clean())
128 }
129
130 fn set(&self, script: Script) {
131 self.inner.lock().expect("unpoisoned").script = Some(script);
132 }
133
134 #[must_use]
136 pub fn calls(&self) -> Vec<Request> {
137 self.inner.lock().expect("unpoisoned").calls.clone()
138 }
139}
140
141#[async_trait]
142impl Simulator for ScriptedSimulator {
143 async fn simulate(&self, request: &Request) -> Result<SimulationReport, SimError> {
144 let mut inner = self.inner.lock().expect("unpoisoned");
145 inner.calls.push(request.clone());
146 match &inner.script {
147 Some(Script::Report(report)) => Ok(report.clone()),
148 Some(Script::Fail(error)) => Err(error.clone()),
149 None => Err(SimError::Unavailable("no script configured".to_owned())),
150 }
151 }
152}
153
154#[derive(Clone, Default)]
160pub struct FixedPrices {
161 table: Arc<Mutex<BTreeMap<TokenKey, u128>>>,
162}
163
164impl FixedPrices {
165 #[must_use]
167 pub fn new() -> Self {
168 Self::default()
169 }
170
171 #[must_use]
173 pub fn price(self, key: TokenKey, micros_per_unit: u128) -> Self {
174 self.table
175 .lock()
176 .expect("unpoisoned")
177 .insert(key, micros_per_unit);
178 self
179 }
180
181 #[must_use]
183 pub fn native(self, chain: Chain, micros_per_unit: u128) -> Self {
184 self.price(TokenKey::native(chain), micros_per_unit)
185 }
186}
187
188#[async_trait]
189impl Prices for FixedPrices {
190 async fn micro_usd(
191 &self,
192 chain: &Chain,
193 token: &str,
194 raw_amount: u128,
195 ) -> Result<Option<u128>, PricesError> {
196 let key = TokenKey {
197 chain: chain.clone(),
198 token: token.to_owned(),
199 };
200 Ok(self
201 .table
202 .lock()
203 .expect("unpoisoned")
204 .get(&key)
205 .map(|per| per.saturating_mul(raw_amount)))
206 }
207}
208
209#[derive(Clone, Default)]
214pub struct MemoryLedger {
215 entries: Arc<Mutex<Vec<RecordedSpend>>>,
216}
217
218#[derive(Debug, Clone, PartialEq, Eq)]
220pub struct RecordedSpend {
221 pub subject: String,
223 pub entry: SpendEntry,
225 pub at: OffsetDateTime,
227}
228
229impl MemoryLedger {
230 #[must_use]
232 pub fn new() -> Self {
233 Self::default()
234 }
235
236 #[must_use]
238 pub fn recorded(&self) -> Vec<RecordedSpend> {
239 self.entries.lock().expect("unpoisoned").clone()
240 }
241}
242
243#[async_trait]
244impl SpendLedger for MemoryLedger {
245 async fn spent(
246 &self,
247 subject: &str,
248 token: &TokenKey,
249 since: OffsetDateTime,
250 ) -> Result<u128, LedgerError> {
251 let entries = self.entries.lock().expect("unpoisoned");
252 Ok(entries
253 .iter()
254 .filter(|r| r.subject == subject && r.entry.key == *token && r.at >= since)
255 .fold(0_u128, |acc, r| acc.saturating_add(r.entry.micro_usd)))
256 }
257
258 async fn record(
259 &self,
260 subject: &str,
261 spend: &[SpendEntry],
262 at: OffsetDateTime,
263 ) -> Result<(), LedgerError> {
264 let mut entries = self.entries.lock().expect("unpoisoned");
265 for entry in spend {
266 entries.push(RecordedSpend {
267 subject: subject.to_owned(),
268 entry: entry.clone(),
269 at,
270 });
271 }
272 Ok(())
273 }
274}
275
276#[derive(Clone, Default)]
281pub struct MemoryAudit {
282 entries: Arc<Mutex<Vec<AuditEntry>>>,
283}
284
285impl MemoryAudit {
286 #[must_use]
288 pub fn new() -> Self {
289 Self::default()
290 }
291
292 #[must_use]
294 pub fn entries(&self) -> Vec<AuditEntry> {
295 self.entries.lock().expect("unpoisoned").clone()
296 }
297
298 #[must_use]
300 pub fn len(&self) -> usize {
301 self.entries.lock().expect("unpoisoned").len()
302 }
303
304 #[must_use]
306 pub fn is_empty(&self) -> bool {
307 self.len() == 0
308 }
309}
310
311#[async_trait]
312impl AuditSink for MemoryAudit {
313 async fn record(&self, entry: &AuditEntry) -> Result<(), AuditError> {
314 self.entries.lock().expect("unpoisoned").push(entry.clone());
315 Ok(())
316 }
317}
318
319#[derive(Clone, Default)]
324pub struct MemoryAllowances {
325 allowances: Arc<Mutex<Vec<Allowance>>>,
326}
327
328impl MemoryAllowances {
329 #[must_use]
331 pub fn new() -> Self {
332 Self::default()
333 }
334
335 #[must_use]
337 pub fn with(self, allowances: Vec<Allowance>) -> Self {
338 *self.allowances.lock().expect("unpoisoned") = allowances;
339 self
340 }
341}
342
343#[async_trait]
344impl AllowanceSource for MemoryAllowances {
345 async fn open_allowances(
346 &self,
347 _chain: &Chain,
348 _owner: &str,
349 ) -> Result<Vec<Allowance>, AllowanceError> {
350 Ok(self.allowances.lock().expect("unpoisoned").clone())
351 }
352}
353
354#[derive(Debug, Clone)]
359pub enum SignScript {
360 Sign(Vec<u8>),
362 Fail(String),
364}
365
366#[derive(Clone, Default)]
369pub struct RecordingSigner {
370 inner: Arc<Mutex<RecordedSigning>>,
371}
372
373#[derive(Default)]
374struct RecordedSigning {
375 script: Option<SignScript>,
376 signed: Vec<Request>,
377}
378
379impl RecordingSigner {
380 #[must_use]
382 pub fn signing(self, signature: Vec<u8>) -> Self {
383 self.set(SignScript::Sign(signature));
384 self
385 }
386
387 #[must_use]
389 pub fn failing(self, message: &str) -> Self {
390 self.set(SignScript::Fail(message.to_owned()));
391 self
392 }
393
394 fn set(&self, script: SignScript) {
395 let mut inner = self.inner.lock().expect("unpoisoned");
396 inner.script = Some(script);
397 }
398
399 #[must_use]
401 pub fn signed(&self) -> Vec<Request> {
402 self.inner.lock().expect("unpoisoned").signed.clone()
403 }
404}
405
406#[async_trait]
407impl WalletSigner for RecordingSigner {
408 async fn sign(&self, request: &Request) -> Result<Vec<u8>, SignError> {
409 let mut inner = self.inner.lock().expect("unpoisoned");
410 inner.signed.push(request.clone());
411 match &inner.script {
412 Some(SignScript::Sign(bytes)) => Ok(bytes.clone()),
413 Some(SignScript::Fail(message)) => Err(SignError(message.clone())),
414 None => Err(SignError("no script configured".to_owned())),
415 }
416 }
417}
418
419#[derive(Clone, Copy, Debug, Default)]
424pub struct FailingAudit;
425
426#[async_trait]
427impl AuditSink for FailingAudit {
428 async fn record(&self, _entry: &AuditEntry) -> Result<(), AuditError> {
429 Err(AuditError("sink down".to_owned()))
430 }
431}