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