shadowforge_lib/adapters/
timelock.rs1use chrono::{DateTime, Utc};
4
5use crate::domain::errors::TimeLockError;
6use crate::domain::ports::TimeLockService;
7use crate::domain::timelock;
8use crate::domain::types::{Payload, TimeLockPuzzle};
9
10pub struct TimeLockServiceImpl {
12 squarings_per_sec: u64,
14}
15
16impl TimeLockServiceImpl {
17 #[must_use]
19 pub const fn new(squarings_per_sec: u64) -> Self {
20 Self { squarings_per_sec }
21 }
22}
23
24impl Default for TimeLockServiceImpl {
25 fn default() -> Self {
26 Self::new(timelock::default_squarings_per_sec())
27 }
28}
29
30impl TimeLockService for TimeLockServiceImpl {
31 fn lock(
32 &self,
33 payload: &Payload,
34 unlock_at: DateTime<Utc>,
35 ) -> Result<TimeLockPuzzle, TimeLockError> {
36 timelock::create_puzzle(payload, unlock_at, self.squarings_per_sec)
37 }
38
39 fn unlock(&self, puzzle: &TimeLockPuzzle) -> Result<Payload, TimeLockError> {
40 timelock::solve_puzzle(puzzle)
41 }
42
43 fn try_unlock(&self, puzzle: &TimeLockPuzzle) -> Result<Option<Payload>, TimeLockError> {
44 timelock::try_solve_puzzle(puzzle, self.squarings_per_sec)
45 }
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51
52 type TestResult = Result<(), Box<dyn std::error::Error>>;
53
54 #[test]
55 fn adapter_roundtrip() -> TestResult {
56 let service = TimeLockServiceImpl::default();
57 let payload = Payload::from_bytes(b"adapter test".to_vec());
58 let unlock_at = Utc::now();
59
60 let puzzle = service.lock(&payload, unlock_at)?;
61 let recovered = service.unlock(&puzzle)?;
62
63 assert_eq!(recovered.as_bytes(), payload.as_bytes());
64 Ok(())
65 }
66
67 #[test]
68 fn adapter_try_unlock_returns_none_for_future() -> TestResult {
69 let service = TimeLockServiceImpl::default();
70 let payload = Payload::from_bytes(b"future test".to_vec());
71 let unlock_at = Utc::now() + chrono::Duration::hours(24);
72
73 let puzzle = service.lock(&payload, unlock_at)?;
74 let result = service.try_unlock(&puzzle)?;
75
76 assert!(result.is_none());
77 Ok(())
78 }
79}