stratus/eth/primitives/
unix_time.rs

1use std::ops::Deref;
2
3use alloy_primitives::U256;
4use chrono::DateTime;
5use chrono::Utc;
6use display_json::DebugAsJson;
7use fake::Dummy;
8use fake::Faker;
9
10use crate::ext::InfallibleExt;
11
12#[derive(DebugAsJson, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
13pub struct UnixTime(u64);
14
15impl UnixTime {
16    pub const ZERO: UnixTime = UnixTime(0u64);
17
18    #[cfg(not(feature = "dev"))]
19    pub fn now() -> Self {
20        Self(Utc::now().timestamp() as u64)
21    }
22
23    #[cfg(feature = "dev")]
24    pub fn now() -> Self {
25        offset::now()
26    }
27
28    #[cfg(feature = "dev")]
29    pub fn set_offset(current_block_timestamp: UnixTime, new_block_timestamp: UnixTime) -> anyhow::Result<()> {
30        offset::set(current_block_timestamp, new_block_timestamp)
31    }
32
33    #[cfg(feature = "dev")]
34    pub fn evm_set_next_block_timestamp_was_called() -> bool {
35        offset::evm_set_next_block_timestamp_was_called()
36    }
37}
38
39impl Deref for UnixTime {
40    type Target = u64;
41
42    fn deref(&self) -> &Self::Target {
43        &self.0
44    }
45}
46
47impl Dummy<Faker> for UnixTime {
48    fn dummy_with_rng<R: rand::Rng + ?Sized>(_: &Faker, rng: &mut R) -> Self {
49        rng.next_u64().into()
50    }
51}
52
53// -----------------------------------------------------------------------------
54// Conversions: Other -> Self
55// -----------------------------------------------------------------------------
56
57impl From<u64> for UnixTime {
58    fn from(value: u64) -> Self {
59        UnixTime(value)
60    }
61}
62
63// -----------------------------------------------------------------------------
64// Conversions: Self -> Other
65// -----------------------------------------------------------------------------
66
67impl From<UnixTime> for U256 {
68    fn from(value: UnixTime) -> Self {
69        Self::from_limbs([value.0, 0, 0, 0])
70    }
71}
72
73impl From<UnixTime> for DateTime<Utc> {
74    fn from(value: UnixTime) -> Self {
75        DateTime::from_timestamp(value.0 as i64, 0).expect_infallible()
76    }
77}
78
79#[cfg(feature = "dev")]
80mod offset {
81    use std::sync::atomic::AtomicBool;
82    use std::sync::atomic::AtomicI64;
83    use std::sync::atomic::AtomicU64;
84    use std::sync::atomic::Ordering::Acquire;
85    use std::sync::atomic::Ordering::SeqCst;
86
87    use super::UnixTime;
88    use super::Utc;
89
90    /// Stores the time difference (in seconds) to apply to subsequent blocks
91    /// This maintains relative time offsets after an explicit timestamp is used
92    pub static NEW_TIMESTAMP_DIFF: AtomicI64 = AtomicI64::new(0);
93
94    /// Stores the exact timestamp to use for the next block
95    /// Only used once, then reset to 0
96    pub static NEW_TIMESTAMP: AtomicU64 = AtomicU64::new(0);
97
98    /// Indicates whether evm_setNextBlockTimestamp was called and hasn't been consumed yet
99    pub static EVM_SET_NEXT_BLOCK_TIMESTAMP_WAS_CALLED: AtomicBool = AtomicBool::new(false);
100
101    /// Stores the timestamp of the most recently processed block
102    /// Used to ensure that block timestamps always increase monotonically
103    pub static LAST_BLOCK_TIMESTAMP: AtomicI64 = AtomicI64::new(0);
104
105    /// Sets the timestamp for the next block and calculates the offset for subsequent blocks
106    ///
107    /// # Scenarios:
108    /// 1. Setting a future timestamp:
109    ///    - current_block = 100, new_timestamp = 110
110    ///    - diff = (110 - current_time) = +10
111    ///    - Next block will be exactly 110
112    ///    - Subsequent blocks will maintain the +10 offset from current time
113    ///
114    /// 2. Setting timestamp to 0 (reset):
115    ///    - Stores the current block timestamp for reference
116    ///    - Resets the time offset to 0
117    ///    - Next block will be current_block + 1
118    ///    - Subsequent blocks use current time
119    ///
120    /// 3. Setting a past timestamp (error):
121    ///    - current_block = 100, new_timestamp = 90
122    ///    - Returns error: "timestamp can't be before the latest block"
123    pub fn set(current_block_timestamp: UnixTime, new_block_timestamp: UnixTime) -> anyhow::Result<()> {
124        use crate::log_and_err;
125
126        if *new_block_timestamp == 0 {
127            LAST_BLOCK_TIMESTAMP.store(*current_block_timestamp as i64, SeqCst);
128        }
129
130        if *new_block_timestamp != 0 && *new_block_timestamp < *current_block_timestamp {
131            return log_and_err!("timestamp can't be before the latest block");
132        }
133
134        let current_time = Utc::now().timestamp();
135        let diff: i64 = if *new_block_timestamp == 0 {
136            0
137        } else {
138            (*new_block_timestamp as i64).saturating_sub(current_time)
139        };
140
141        NEW_TIMESTAMP.store(*new_block_timestamp, SeqCst);
142        NEW_TIMESTAMP_DIFF.store(diff, SeqCst);
143        EVM_SET_NEXT_BLOCK_TIMESTAMP_WAS_CALLED.store(true, SeqCst);
144        Ok(())
145    }
146
147    /// Returns the timestamp for the current block based on various conditions.
148    /// Ensures proper time progression by guaranteeing that each block's timestamp
149    /// is at least 1 second greater than the previous block.
150    ///
151    /// # Behavior
152    /// There are two main paths for timestamp generation:
153    ///
154    /// 1. When a specific timestamp was set (was_evm_timestamp_set = true):
155    ///    - If new_timestamp is 0: Returns last_block_timestamp + 1
156    ///    - If new_timestamp > 0: Returns exactly new_timestamp
157    ///    - After this call, resets the timestamp flag and stored value
158    ///
159    /// 2. For subsequent blocks (was_evm_timestamp_set = false):
160    ///    - If new_timestamp is set: Uses it as reference point
161    ///    - Otherwise: Uses max(current_time + offset, last_block_timestamp)
162    ///    - In both cases, adds 1 second to ensure progression
163    ///
164    /// The function always updates LAST_BLOCK_TIMESTAMP with the returned value
165    /// to maintain the chain of increasing timestamps.
166    pub fn now() -> UnixTime {
167        let new_timestamp = NEW_TIMESTAMP.load(Acquire);
168        let new_timestamp_diff = NEW_TIMESTAMP_DIFF.load(Acquire);
169        let was_evm_timestamp_set = EVM_SET_NEXT_BLOCK_TIMESTAMP_WAS_CALLED.load(Acquire);
170        let last_block_timestamp = LAST_BLOCK_TIMESTAMP.load(Acquire);
171        let current_time = Utc::now().timestamp();
172
173        let result = if !was_evm_timestamp_set {
174            let last_timestamp = if new_timestamp != 0 {
175                new_timestamp as i64
176            } else {
177                std::cmp::max(current_time + new_timestamp_diff, last_block_timestamp)
178            };
179
180            UnixTime((last_timestamp + 1) as u64)
181        } else {
182            EVM_SET_NEXT_BLOCK_TIMESTAMP_WAS_CALLED.store(false, SeqCst);
183            NEW_TIMESTAMP.store(0, SeqCst);
184
185            if new_timestamp == 0 {
186                UnixTime((last_block_timestamp + 1) as u64)
187            } else {
188                UnixTime(new_timestamp)
189            }
190        };
191
192        LAST_BLOCK_TIMESTAMP.store(*result as i64, SeqCst);
193        result
194    }
195
196    /// Returns whether evm_setNextBlockTimestamp was called and hasn't been consumed yet
197    pub fn evm_set_next_block_timestamp_was_called() -> bool {
198        EVM_SET_NEXT_BLOCK_TIMESTAMP_WAS_CALLED.load(Acquire)
199    }
200}