Skip to main content

tempo_revm/
block.rs

1use alloy_evm::env::BlockEnvironment;
2use alloy_primitives::{Address, B256, U256, uint};
3use revm::{
4    context::{Block, BlockEnv},
5    context_interface::block::BlobExcessGasAndPrice,
6};
7
8/// Tempo block environment.
9#[derive(Debug, Clone, Default, derive_more::Deref, derive_more::DerefMut)]
10pub struct TempoBlockEnv {
11    /// Inner [`BlockEnv`].
12    #[deref]
13    #[deref_mut]
14    pub inner: BlockEnv,
15
16    /// Milliseconds portion of the timestamp.
17    pub timestamp_millis_part: u64,
18}
19
20impl TempoBlockEnv {
21    /// Returns the current timestamp in milliseconds.
22    pub fn timestamp_millis(&self) -> U256 {
23        self.inner
24            .timestamp
25            .saturating_mul(uint!(1000_U256))
26            .saturating_add(U256::from(self.timestamp_millis_part))
27    }
28}
29
30impl Block for TempoBlockEnv {
31    #[inline]
32    fn number(&self) -> U256 {
33        self.inner.number()
34    }
35
36    #[inline]
37    fn beneficiary(&self) -> Address {
38        self.inner.beneficiary()
39    }
40
41    #[inline]
42    fn timestamp(&self) -> U256 {
43        self.inner.timestamp()
44    }
45
46    #[inline]
47    fn gas_limit(&self) -> u64 {
48        self.inner.gas_limit()
49    }
50
51    #[inline]
52    fn basefee(&self) -> u64 {
53        self.inner.basefee()
54    }
55
56    #[inline]
57    fn difficulty(&self) -> U256 {
58        self.inner.difficulty()
59    }
60
61    #[inline]
62    fn prevrandao(&self) -> Option<B256> {
63        self.inner.prevrandao()
64    }
65
66    #[inline]
67    fn blob_excess_gas_and_price(&self) -> Option<BlobExcessGasAndPrice> {
68        self.inner.blob_excess_gas_and_price()
69    }
70}
71
72impl BlockEnvironment for TempoBlockEnv {
73    fn inner_mut(&mut self) -> &mut BlockEnv {
74        &mut self.inner
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81    use proptest::prelude::*;
82
83    /// Helper to create a TempoBlockEnv with the given timestamp and millis_part.
84    fn make_block_env(timestamp: U256, millis_part: u64) -> TempoBlockEnv {
85        TempoBlockEnv {
86            inner: BlockEnv {
87                timestamp,
88                ..Default::default()
89            },
90            timestamp_millis_part: millis_part,
91        }
92    }
93
94    /// Strategy for random U256 values.
95    fn arb_u256() -> impl Strategy<Value = U256> {
96        any::<[u64; 4]>().prop_map(U256::from_limbs)
97    }
98
99    proptest! {
100        #![proptest_config(ProptestConfig::with_cases(500))]
101
102        /// Property: timestamp_millis never panics (uses saturating arithmetic)
103        #[test]
104        fn proptest_timestamp_millis_no_panic(
105            timestamp in arb_u256(),
106            millis_part in any::<u64>(),
107        ) {
108            let block = make_block_env(timestamp, millis_part);
109            let _ = block.timestamp_millis();
110        }
111
112        /// Property: timestamp_millis >= timestamp * 1000 (saturation means >= not >)
113        #[test]
114        fn proptest_timestamp_millis_ge_scaled_timestamp(
115            timestamp in arb_u256(),
116            millis_part in any::<u64>(),
117        ) {
118            let block = make_block_env(timestamp, millis_part);
119            let result = block.timestamp_millis();
120            let scaled = timestamp.saturating_mul(uint!(1000_U256));
121
122            prop_assert!(result >= scaled,
123                "timestamp_millis ({}) should be >= timestamp * 1000 ({})",
124                result, scaled);
125        }
126
127        /// Property: for small timestamps, timestamp_millis == timestamp * 1000 + millis_part
128        #[test]
129        fn proptest_timestamp_millis_exact_for_small_values(
130            timestamp in 0u64..u64::MAX / 1000,
131            millis_part in 0u64..1000,
132        ) {
133            let block = make_block_env(U256::from(timestamp), millis_part);
134            let expected = U256::from(timestamp) * uint!(1000_U256) + U256::from(millis_part);
135            prop_assert_eq!(block.timestamp_millis(), expected);
136        }
137
138        /// Property: timestamp_millis is monotonic in both inputs
139        #[test]
140        fn proptest_timestamp_millis_monotonicity(
141            ts1 in 0u64..u64::MAX / 1000,
142            ts2 in 0u64..u64::MAX / 1000,
143            mp1 in 0u64..1000,
144            mp2 in 0u64..1000,
145        ) {
146            let block1 = make_block_env(U256::from(ts1), mp1);
147            let block2 = make_block_env(U256::from(ts2), mp2);
148
149            let result1 = block1.timestamp_millis();
150            let result2 = block2.timestamp_millis();
151
152            if ts1 < ts2 || (ts1 == ts2 && mp1 <= mp2) {
153                prop_assert!(result1 <= result2,
154                    "Monotonicity violated: ts1={}, mp1={}, result1={}, ts2={}, mp2={}, result2={}",
155                    ts1, mp1, result1, ts2, mp2, result2);
156            }
157        }
158
159        /// Property: millis_part < 1000 means it doesn't overflow into the next second
160        #[test]
161        fn proptest_timestamp_millis_sub_second(
162            timestamp in 0u64..u64::MAX / 1000,
163            millis_part in 0u64..1000,
164        ) {
165            let block = make_block_env(U256::from(timestamp), millis_part);
166            let result = block.timestamp_millis();
167            let next_second = U256::from(timestamp + 1) * uint!(1000_U256);
168
169            prop_assert!(result < next_second,
170                "result ({}) should be < next_second ({})",
171                result, next_second);
172        }
173
174        /// Property: millis_part >= 1000 overflows into subsequent seconds but uses saturating math
175        ///
176        /// When millis_part >= 1000, the result "overflows" into subsequent seconds conceptually.
177        /// E.g., timestamp=5, millis_part=2500 -> result = 5*1000 + 2500 = 7500 (equivalent to 7.5 seconds)
178        /// This is technically invalid input but the function handles it safely via saturating arithmetic.
179        #[test]
180        fn proptest_timestamp_millis_large_millis_part(
181            timestamp in 0u64..u64::MAX / 1000,
182            millis_part in 1000u64..u64::MAX,
183        ) {
184            let block = make_block_env(U256::from(timestamp), millis_part);
185            let result = block.timestamp_millis();
186
187            // Result should equal timestamp * 1000 + millis_part (saturating)
188            let scaled = U256::from(timestamp).saturating_mul(uint!(1000_U256));
189            let expected = scaled.saturating_add(U256::from(millis_part));
190
191            prop_assert_eq!(result, expected,
192                "timestamp={}, millis_part={}, result={}, expected={}",
193                timestamp, millis_part, result, expected);
194        }
195
196        /// Property: when millis_part >= 1000, monotonicity can be violated
197        ///
198        /// This demonstrates that millis_part should be constrained to 0..1000 for correct
199        /// time ordering semantics. A large millis_part can cause a "smaller" timestamp to
200        /// have a larger result than a "larger" timestamp with small millis_part.
201        #[test]
202        fn proptest_timestamp_millis_large_millis_breaks_monotonicity(
203            ts in 0u64..u64::MAX / 2000,
204            large_mp in 1000u64..u64::MAX,
205        ) {
206            // Block with timestamp=ts and large millis_part
207            let block1 = make_block_env(U256::from(ts), large_mp);
208            // Block with timestamp=ts+1 and millis_part=0
209            let block2 = make_block_env(U256::from(ts + 1), 0);
210
211            let result1 = block1.timestamp_millis();
212            let result2 = block2.timestamp_millis();
213
214            // When large_mp >= 1000, result1 may exceed result2 even though ts < ts+1
215            // This is expected behavior - millis_part is expected to be < 1000
216            // Just verify no panics and results are computed correctly
217            let expected1 = U256::from(ts).saturating_mul(uint!(1000_U256))
218                .saturating_add(U256::from(large_mp));
219            let expected2 = U256::from(ts + 1).saturating_mul(uint!(1000_U256));
220
221            prop_assert_eq!(result1, expected1);
222            prop_assert_eq!(result2, expected2);
223        }
224    }
225}