foundry_cheatcodes/inspector/
env_overrides.rs1use alloy_primitives::{B256, U256, map::HashMap};
4use foundry_evm_core::backend::LocalForkId;
5
6type EnvOverridesByFork = HashMap<Option<LocalForkId>, EnvOverrides>;
8
9#[derive(Clone, Debug, Default)]
38pub struct EnvOverrides {
39 pub basefee: Option<u64>,
41 pub implicit_basefee: Option<u64>,
43 pub gas_price: Option<u128>,
45 pub blob_hashes: Option<Vec<B256>>,
47 pub pre_override_gas_price: Option<u128>,
51 pub pre_override_tx_type: Option<u8>,
55 pub pre_override_blob_hashes: Option<Vec<B256>>,
58 pub(super) pending_opcode: Option<u8>,
63 pub(super) pending_blobhash_index: Option<u64>,
67}
68
69impl EnvOverrides {
70 #[inline]
72 pub const fn is_any_set(&self) -> bool {
73 self.basefee.is_some()
74 || self.implicit_basefee.is_some()
75 || self.gas_price.is_some()
76 || self.blob_hashes.is_some()
77 }
78
79 #[inline]
81 pub fn basefee_override(&self) -> Option<u64> {
82 self.basefee.or(self.implicit_basefee)
83 }
84
85 #[inline]
87 pub const fn gas_price_override(&self) -> Option<u128> {
88 self.gas_price
89 }
90
91 #[inline]
95 pub fn blob_hash_override(&self, index: u64) -> Option<B256> {
96 let blob_hashes = self.blob_hashes.as_ref()?;
97 Some(blob_hashes.get(index as usize).copied().unwrap_or_default())
98 }
99}
100
101#[derive(Clone, Debug, Default)]
105pub struct EnvOverrideState {
106 by_fork: EnvOverridesByFork,
107 snapshots: HashMap<U256, EnvOverridesByFork>,
108}
109
110impl EnvOverrideState {
111 #[inline]
113 pub fn is_empty(&self) -> bool {
114 self.by_fork.is_empty()
115 }
116
117 #[inline]
119 pub fn is_any_set(&self) -> bool {
120 self.by_fork.values().any(EnvOverrides::is_any_set)
121 }
122
123 #[inline]
125 pub fn get(&self, fork_id: Option<LocalForkId>) -> Option<&EnvOverrides> {
126 self.by_fork.get(&fork_id)
127 }
128
129 #[inline]
131 pub(super) fn get_mut(&mut self, fork_id: Option<LocalForkId>) -> Option<&mut EnvOverrides> {
132 self.by_fork.get_mut(&fork_id)
133 }
134
135 pub(crate) fn update(
137 &mut self,
138 fork_id: Option<LocalForkId>,
139 f: impl FnOnce(&mut EnvOverrides),
140 ) {
141 f(self.by_fork.entry(fork_id).or_default());
142 }
143
144 pub(crate) fn clear_implicit_basefee(&mut self, fork_id: Option<LocalForkId>) {
146 if let Some(overrides) = self.by_fork.get_mut(&fork_id) {
147 overrides.implicit_basefee = None;
148 }
149 }
150
151 pub(crate) fn remove_if_unset(&mut self, fork_id: Option<LocalForkId>) {
153 if self.by_fork.get(&fork_id).is_some_and(|overrides| !overrides.is_any_set()) {
154 self.by_fork.remove(&fork_id);
155 }
156 }
157
158 pub(crate) fn save_snapshot(
160 &mut self,
161 snapshot_id: U256,
162 active_fork_id: Option<LocalForkId>,
163 tx_gas_price: u128,
164 tx_type: u8,
165 tx_blob_hashes: &[B256],
166 ) {
167 let mut snapshot = self.by_fork.clone();
168 let active = snapshot.entry(active_fork_id).or_default();
169 if active.gas_price.is_none() {
170 active.pre_override_gas_price = Some(tx_gas_price);
171 }
172 if active.blob_hashes.is_none() {
173 active.pre_override_tx_type = Some(tx_type);
174 active.pre_override_blob_hashes = Some(tx_blob_hashes.to_vec());
175 }
176 self.snapshots.insert(snapshot_id, snapshot);
177 }
178
179 pub(crate) fn restore_snapshot(&mut self, snapshot_id: U256, delete: bool) {
183 let snapshot = if delete {
184 self.snapshots.remove(&snapshot_id)
185 } else {
186 self.snapshots.get(&snapshot_id).cloned()
187 };
188 if let Some(snapshot) = snapshot {
189 self.by_fork = snapshot;
190 }
191 }
192
193 pub(crate) fn delete_snapshot(&mut self, snapshot_id: U256) {
195 self.snapshots.remove(&snapshot_id);
196 }
197
198 pub(crate) fn clear_snapshots(&mut self) {
200 self.snapshots.clear();
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207
208 const FORK: Option<LocalForkId> = Some(U256::from_limbs([1, 0, 0, 0]));
209 const SNAPSHOT: U256 = U256::from_limbs([7, 0, 0, 0]);
210
211 #[test]
212 fn override_values() {
213 let mut overrides = EnvOverrides::default();
214 assert_eq!(overrides.basefee_override(), None);
215 assert_eq!(overrides.gas_price_override(), None);
216 assert_eq!(overrides.blob_hash_override(0), None);
217
218 overrides.implicit_basefee = Some(1);
219 assert_eq!(overrides.basefee_override(), Some(1));
220 overrides.basefee = Some(2);
221 assert_eq!(overrides.basefee_override(), Some(2));
222
223 overrides.gas_price = Some(3);
224 assert_eq!(overrides.gas_price_override(), Some(3));
225
226 let hash = B256::repeat_byte(1);
227 overrides.blob_hashes = Some(vec![hash]);
228 assert_eq!(overrides.blob_hash_override(0), Some(hash));
229 assert_eq!(overrides.blob_hash_override(1), Some(B256::ZERO));
230 assert_eq!(overrides.blob_hash_override(u64::MAX), Some(B256::ZERO));
231 }
232
233 #[test]
234 fn tracks_overrides_on_every_fork() {
235 let mut state = EnvOverrideState::default();
236 assert!(state.is_empty());
237 assert!(!state.is_any_set());
238
239 state.update(None, |_| {});
240 assert!(!state.is_empty());
241 assert!(!state.is_any_set());
242
243 state.update(FORK, |o| o.gas_price = Some(1));
244 assert!(state.is_any_set());
245
246 state.remove_if_unset(None);
247 state.remove_if_unset(FORK);
248 assert!(state.get(None).is_none());
249 assert_eq!(state.get(FORK).and_then(EnvOverrides::gas_price_override), Some(1));
250
251 state.clear_implicit_basefee(None);
252 assert!(state.get(None).is_none(), "clearing must not insert an entry");
253 state.update(FORK, |o| o.implicit_basefee = Some(2));
254 state.clear_implicit_basefee(FORK);
255 assert_eq!(state.get(FORK).and_then(EnvOverrides::basefee_override), None);
256 }
257
258 #[test]
259 fn save_snapshot_records_tx_values_that_are_not_overridden() {
260 let mut state = EnvOverrideState::default();
261 let tx_hashes = [B256::repeat_byte(1)];
262
263 state.save_snapshot(SNAPSHOT, None, 10, 3, &tx_hashes);
264 let snapshot = &state.snapshots[&SNAPSHOT];
265 let active = &snapshot[&None];
266 assert!(!active.is_any_set());
267 assert_eq!(active.pre_override_gas_price, Some(10));
268 assert_eq!(active.pre_override_tx_type, Some(3));
269 assert_eq!(active.pre_override_blob_hashes.as_deref(), Some(&tx_hashes[..]));
270 assert!(state.is_empty(), "saving a snapshot must not change the live overrides");
271
272 state.update(None, |o| {
273 o.gas_price = Some(1);
274 o.blob_hashes = Some(vec![]);
275 });
276 state.update(FORK, |o| o.basefee = Some(2));
277 state.save_snapshot(SNAPSHOT, None, 10, 3, &tx_hashes);
278 let snapshot = &state.snapshots[&SNAPSHOT];
279 let active = &snapshot[&None];
280 assert_eq!(active.pre_override_gas_price, None);
281 assert_eq!(active.pre_override_tx_type, None);
282 assert_eq!(active.pre_override_blob_hashes, None);
283 assert_eq!(snapshot[&FORK].pre_override_gas_price, None, "only the active fork");
284 }
285
286 #[test]
287 fn restores_snapshots() {
288 let mut state = EnvOverrideState::default();
289 state.update(None, |o| o.basefee = Some(1));
290 state.save_snapshot(SNAPSHOT, None, 0, 0, &[]);
291
292 state.update(None, |o| o.basefee = Some(2));
293 state.restore_snapshot(SNAPSHOT, false);
294 assert_eq!(state.get(None).and_then(EnvOverrides::basefee_override), Some(1));
295
296 state.update(None, |o| o.basefee = Some(3));
298 state.restore_snapshot(SNAPSHOT, true);
299 assert_eq!(state.get(None).and_then(EnvOverrides::basefee_override), Some(1));
300
301 state.update(None, |o| o.basefee = Some(4));
303 state.restore_snapshot(SNAPSHOT, false);
304 assert_eq!(state.get(None).and_then(EnvOverrides::basefee_override), Some(4));
305 }
306
307 #[test]
308 fn deletes_snapshots() {
309 let mut state = EnvOverrideState::default();
310 state.save_snapshot(SNAPSHOT, None, 0, 0, &[]);
311 state.save_snapshot(SNAPSHOT + U256::from(1), None, 0, 0, &[]);
312 state.update(None, |o| o.basefee = Some(1));
313
314 state.delete_snapshot(SNAPSHOT);
315 state.restore_snapshot(SNAPSHOT, false);
316 assert!(state.is_any_set());
317
318 state.clear_snapshots();
319 state.restore_snapshot(SNAPSHOT + U256::from(1), false);
320 assert!(state.is_any_set());
321 }
322}