Skip to main content

revm_context_interface/
host.rs

1//! Host interface for external blockchain state access.
2
3use crate::{
4    cfg::GasParams,
5    context::{SStoreResult, SelfDestructResult, StateLoad},
6    journaled_state::{AccountInfoLoad, AccountLoad},
7};
8use auto_impl::auto_impl;
9use primitives::{hardfork::SpecId, Address, Bytes, Log, StorageKey, StorageValue, B256, U256};
10use state::Bytecode;
11
12/// Error that can happen when loading account info.
13#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15pub enum LoadError {
16    /// Cold load skipped.
17    ColdLoadSkipped,
18    /// Database error.
19    DBError,
20}
21
22/// Host trait with all methods that are needed by the Interpreter.
23///
24/// This trait is implemented for all types that have `ContextTr` trait.
25///
26/// There are few groups of functions which are Block, Transaction, Config, Database and Journal functions.
27#[auto_impl(&mut, Box)]
28pub trait Host {
29    /* Block */
30
31    /// Block basefee, calls ContextTr::block().basefee()
32    fn basefee(&self) -> U256;
33    /// Block blob gasprice, calls `ContextTr::block().blob_gasprice()`
34    fn blob_gasprice(&self) -> U256;
35    /// Block gas limit, calls ContextTr::block().gas_limit()
36    fn gas_limit(&self) -> U256;
37    /// Block difficulty, calls ContextTr::block().difficulty()
38    fn difficulty(&self) -> U256;
39    /// Block prevrandao, calls ContextTr::block().prevrandao()
40    fn prevrandao(&self) -> Option<U256>;
41    /// Block number, calls ContextTr::block().number()
42    fn block_number(&self) -> U256;
43    /// Block timestamp, calls ContextTr::block().timestamp()
44    fn timestamp(&self) -> U256;
45    /// Block beneficiary, calls ContextTr::block().beneficiary()
46    fn beneficiary(&self) -> Address;
47    /// Block slot number, calls ContextTr::block().slot_num()
48    fn slot_num(&self) -> U256;
49    /// Chain id, calls ContextTr::cfg().chain_id()
50    fn chain_id(&self) -> U256;
51
52    /* Transaction */
53
54    /// Transaction effective gas price, calls `ContextTr::tx().effective_gas_price(basefee as u128)`
55    fn effective_gas_price(&self) -> U256;
56    /// Transaction caller, calls `ContextTr::tx().caller()`
57    fn caller(&self) -> Address;
58    /// Transaction blob hash, calls `ContextTr::tx().blob_hash(number)`
59    fn blob_hash(&self, number: usize) -> Option<U256>;
60
61    /* Config */
62
63    /// Max initcode size, calls `ContextTr::cfg().max_code_size().saturating_mul(2)`
64    fn max_initcode_size(&self) -> usize;
65
66    /// Gas params contains the dynamic gas constants for the EVM.
67    fn gas_params(&self) -> &GasParams;
68
69    /// Returns whether state gas (EIP-8037) is enabled.
70    fn is_amsterdam_eip8037_enabled(&self) -> bool;
71
72    /// Returns whether EIP-8024 stack instructions are enabled.
73    fn is_amsterdam_eip8024_enabled(&self) -> bool;
74
75    /* Database */
76
77    /// Block hash, calls `ContextTr::journal_mut().db().block_hash(number)`
78    fn block_hash(&mut self, number: u64) -> Option<B256>;
79
80    /* Journal */
81
82    /// Selfdestruct account, calls `ContextTr::journal_mut().selfdestruct(address, target)`
83    fn selfdestruct(
84        &mut self,
85        address: Address,
86        target: Address,
87        skip_cold_load: bool,
88    ) -> Result<StateLoad<SelfDestructResult>, LoadError>;
89
90    /// Log, calls `ContextTr::journal_mut().log(log)`
91    fn log(&mut self, log: Log);
92
93    /// Sstore with optional fetch from database. Return none if the value is cold or if there is db error.
94    fn sstore_skip_cold_load(
95        &mut self,
96        address: Address,
97        key: StorageKey,
98        value: StorageValue,
99        skip_cold_load: bool,
100    ) -> Result<StateLoad<SStoreResult>, LoadError>;
101
102    /// Sstore, calls `ContextTr::journal_mut().sstore(address, key, value)`
103    fn sstore(
104        &mut self,
105        address: Address,
106        key: StorageKey,
107        value: StorageValue,
108    ) -> Option<StateLoad<SStoreResult>> {
109        self.sstore_skip_cold_load(address, key, value, false).ok()
110    }
111
112    /// Sload with optional fetch from database. Return none if the value is cold or if there is db error.
113    fn sload_skip_cold_load(
114        &mut self,
115        address: Address,
116        key: StorageKey,
117        skip_cold_load: bool,
118    ) -> Result<StateLoad<StorageValue>, LoadError>;
119
120    /// Sload, calls `ContextTr::journal_mut().sload(address, key)`
121    fn sload(&mut self, address: Address, key: StorageKey) -> Option<StateLoad<StorageValue>> {
122        self.sload_skip_cold_load(address, key, false).ok()
123    }
124
125    /// Tstore, calls `ContextTr::journal_mut().tstore(address, key, value)`
126    fn tstore(&mut self, address: Address, key: StorageKey, value: StorageValue);
127
128    /// Tload, calls `ContextTr::journal_mut().tload(address, key)`
129    fn tload(&mut self, address: Address, key: StorageKey) -> StorageValue;
130
131    /// Main function to load account info.
132    ///
133    /// If load_code is true, it will load the code fetching it from the database if not done before.
134    ///
135    /// If skip_cold_load is true, it will not load the account if it is cold. This is needed to short circuit
136    /// the load if there is not enough gas.
137    ///
138    /// Returns AccountInfo, is_cold and is_empty.
139    fn load_account_info_skip_cold_load(
140        &mut self,
141        address: Address,
142        load_code: bool,
143        skip_cold_load: bool,
144    ) -> Result<AccountInfoLoad<'_>, LoadError>;
145
146    /// Balance, calls `ContextTr::journal_mut().load_account(address)`
147    #[inline]
148    fn balance(&mut self, address: Address) -> Option<StateLoad<U256>> {
149        self.load_account_info_skip_cold_load(address, false, false)
150            .ok()
151            .map(|load| load.into_state_load(|i| i.balance))
152    }
153
154    /// Load account delegated, calls `ContextTr::journal_mut().load_account_delegated(address)`
155    #[inline]
156    fn load_account_delegated(&mut self, address: Address) -> Option<StateLoad<AccountLoad>> {
157        let account = self
158            .load_account_info_skip_cold_load(address, true, false)
159            .ok()?;
160
161        let mut account_load = StateLoad::new(
162            AccountLoad {
163                is_delegate_account_cold: None,
164                is_empty: account.is_empty,
165            },
166            account.is_cold,
167        );
168
169        // load delegate code if account is EIP-7702
170        if let Some(address) = account.code.as_ref().and_then(Bytecode::eip7702_address) {
171            let delegate_account = self
172                .load_account_info_skip_cold_load(address, true, false)
173                .ok()?;
174            account_load.data.is_delegate_account_cold = Some(delegate_account.is_cold);
175        }
176
177        Some(account_load)
178    }
179
180    /// Load account code, calls [`Host::load_account_info_skip_cold_load`] with `load_code` set to false.
181    #[inline]
182    fn load_account_code(&mut self, address: Address) -> Option<StateLoad<Bytes>> {
183        self.load_account_info_skip_cold_load(address, true, false)
184            .ok()
185            .map(|load| {
186                load.into_state_load(|i| {
187                    i.code
188                        .as_ref()
189                        .map(|b| b.original_bytes())
190                        .unwrap_or_default()
191                })
192            })
193    }
194
195    /// Load account code hash, calls [`Host::load_account_info_skip_cold_load`] with `load_code` set to false.
196    #[inline]
197    fn load_account_code_hash(&mut self, address: Address) -> Option<StateLoad<B256>> {
198        self.load_account_info_skip_cold_load(address, false, false)
199            .ok()
200            .map(|load| {
201                load.into_state_load(|i| {
202                    if i.is_empty() {
203                        B256::ZERO
204                    } else {
205                        i.code_hash
206                    }
207                })
208            })
209    }
210}
211
212/// Dummy host that implements [`Host`] trait and  returns all default values.
213#[derive(Default, Debug)]
214pub struct DummyHost {
215    gas_params: GasParams,
216    spec: SpecId,
217}
218
219impl DummyHost {
220    /// Create a new dummy host with the given spec.
221    pub fn new(spec: SpecId) -> Self {
222        Self {
223            gas_params: GasParams::new_spec(spec),
224            spec,
225        }
226    }
227}
228
229impl Host for DummyHost {
230    fn basefee(&self) -> U256 {
231        U256::ZERO
232    }
233
234    fn blob_gasprice(&self) -> U256 {
235        U256::ZERO
236    }
237
238    fn gas_limit(&self) -> U256 {
239        U256::ZERO
240    }
241
242    fn gas_params(&self) -> &GasParams {
243        &self.gas_params
244    }
245
246    fn is_amsterdam_eip8037_enabled(&self) -> bool {
247        self.spec.is_enabled_in(SpecId::AMSTERDAM)
248    }
249
250    fn is_amsterdam_eip8024_enabled(&self) -> bool {
251        self.spec.is_enabled_in(SpecId::AMSTERDAM)
252    }
253
254    fn difficulty(&self) -> U256 {
255        U256::ZERO
256    }
257
258    fn prevrandao(&self) -> Option<U256> {
259        None
260    }
261
262    fn block_number(&self) -> U256 {
263        U256::ZERO
264    }
265
266    fn timestamp(&self) -> U256 {
267        U256::ZERO
268    }
269
270    fn beneficiary(&self) -> Address {
271        Address::ZERO
272    }
273
274    fn slot_num(&self) -> U256 {
275        U256::ZERO
276    }
277
278    fn chain_id(&self) -> U256 {
279        U256::ZERO
280    }
281
282    fn effective_gas_price(&self) -> U256 {
283        U256::ZERO
284    }
285
286    fn caller(&self) -> Address {
287        Address::ZERO
288    }
289
290    fn blob_hash(&self, _number: usize) -> Option<U256> {
291        None
292    }
293
294    fn max_initcode_size(&self) -> usize {
295        0
296    }
297
298    fn block_hash(&mut self, _number: u64) -> Option<B256> {
299        None
300    }
301
302    fn selfdestruct(
303        &mut self,
304        _address: Address,
305        _target: Address,
306        _skip_cold_load: bool,
307    ) -> Result<StateLoad<SelfDestructResult>, LoadError> {
308        Ok(Default::default())
309    }
310
311    fn log(&mut self, _log: Log) {}
312
313    fn tstore(&mut self, _address: Address, _key: StorageKey, _value: StorageValue) {}
314
315    fn tload(&mut self, _address: Address, _key: StorageKey) -> StorageValue {
316        StorageValue::ZERO
317    }
318
319    fn load_account_info_skip_cold_load(
320        &mut self,
321        _address: Address,
322        _load_code: bool,
323        _skip_cold_load: bool,
324    ) -> Result<AccountInfoLoad<'_>, LoadError> {
325        Ok(Default::default())
326    }
327
328    fn sstore_skip_cold_load(
329        &mut self,
330        _address: Address,
331        _key: StorageKey,
332        _value: StorageValue,
333        _skip_cold_load: bool,
334    ) -> Result<StateLoad<SStoreResult>, LoadError> {
335        Ok(Default::default())
336    }
337
338    fn sload_skip_cold_load(
339        &mut self,
340        _address: Address,
341        _key: StorageKey,
342        _skip_cold_load: bool,
343    ) -> Result<StateLoad<StorageValue>, LoadError> {
344        Ok(Default::default())
345    }
346}
347
348#[cfg(test)]
349mod tests {
350    use super::*;
351    use primitives::{hardfork::SpecId, Address, U256};
352    use state::{AccountInfo, Bytecode};
353    use std::borrow::Cow;
354
355    /// Host used to regression-test [`Host::load_account_delegated`].
356    ///
357    /// `delegated` is a non-empty EIP-7702 account pointing at an empty `delegate`.
358    struct Eip7702Host {
359        dummy: DummyHost,
360        delegated: Address,
361        delegate: Address,
362        delegated_info: AccountInfo,
363    }
364
365    impl Eip7702Host {
366        fn new() -> Self {
367            let delegated = Address::repeat_byte(0x11);
368            let delegate = Address::repeat_byte(0x22);
369            let delegated_info = AccountInfo::new(
370                U256::from(1),
371                1,
372                B256::ZERO,
373                Bytecode::new_eip7702(delegate),
374            );
375            Self {
376                dummy: DummyHost::new(SpecId::PRAGUE),
377                delegated,
378                delegate,
379                delegated_info,
380            }
381        }
382    }
383
384    impl Host for Eip7702Host {
385        fn basefee(&self) -> U256 {
386            self.dummy.basefee()
387        }
388        fn blob_gasprice(&self) -> U256 {
389            self.dummy.blob_gasprice()
390        }
391        fn gas_limit(&self) -> U256 {
392            self.dummy.gas_limit()
393        }
394        fn gas_params(&self) -> &GasParams {
395            self.dummy.gas_params()
396        }
397        fn is_amsterdam_eip8037_enabled(&self) -> bool {
398            self.dummy.is_amsterdam_eip8037_enabled()
399        }
400        fn is_amsterdam_eip8024_enabled(&self) -> bool {
401            self.dummy.is_amsterdam_eip8024_enabled()
402        }
403        fn difficulty(&self) -> U256 {
404            self.dummy.difficulty()
405        }
406        fn prevrandao(&self) -> Option<U256> {
407            self.dummy.prevrandao()
408        }
409        fn block_number(&self) -> U256 {
410            self.dummy.block_number()
411        }
412        fn timestamp(&self) -> U256 {
413            self.dummy.timestamp()
414        }
415        fn beneficiary(&self) -> Address {
416            self.dummy.beneficiary()
417        }
418        fn slot_num(&self) -> U256 {
419            self.dummy.slot_num()
420        }
421        fn chain_id(&self) -> U256 {
422            self.dummy.chain_id()
423        }
424        fn effective_gas_price(&self) -> U256 {
425            self.dummy.effective_gas_price()
426        }
427        fn caller(&self) -> Address {
428            self.dummy.caller()
429        }
430        fn blob_hash(&self, number: usize) -> Option<U256> {
431            self.dummy.blob_hash(number)
432        }
433        fn max_initcode_size(&self) -> usize {
434            self.dummy.max_initcode_size()
435        }
436        fn block_hash(&mut self, number: u64) -> Option<B256> {
437            self.dummy.block_hash(number)
438        }
439        fn selfdestruct(
440            &mut self,
441            address: Address,
442            target: Address,
443            skip_cold_load: bool,
444        ) -> Result<StateLoad<SelfDestructResult>, LoadError> {
445            self.dummy.selfdestruct(address, target, skip_cold_load)
446        }
447        fn log(&mut self, log: Log) {
448            self.dummy.log(log)
449        }
450        fn tstore(&mut self, address: Address, key: StorageKey, value: StorageValue) {
451            self.dummy.tstore(address, key, value)
452        }
453        fn tload(&mut self, address: Address, key: StorageKey) -> StorageValue {
454            self.dummy.tload(address, key)
455        }
456        fn sstore_skip_cold_load(
457            &mut self,
458            address: Address,
459            key: StorageKey,
460            value: StorageValue,
461            skip_cold_load: bool,
462        ) -> Result<StateLoad<SStoreResult>, LoadError> {
463            self.dummy
464                .sstore_skip_cold_load(address, key, value, skip_cold_load)
465        }
466        fn sload_skip_cold_load(
467            &mut self,
468            address: Address,
469            key: StorageKey,
470            skip_cold_load: bool,
471        ) -> Result<StateLoad<StorageValue>, LoadError> {
472            self.dummy
473                .sload_skip_cold_load(address, key, skip_cold_load)
474        }
475
476        fn load_account_info_skip_cold_load(
477            &mut self,
478            address: Address,
479            _load_code: bool,
480            _skip_cold_load: bool,
481        ) -> Result<AccountInfoLoad<'_>, LoadError> {
482            if address == self.delegated {
483                Ok(AccountInfoLoad {
484                    account: Cow::Owned(self.delegated_info.clone()),
485                    is_cold: false,
486                    is_empty: false,
487                })
488            } else if address == self.delegate {
489                // Empty delegated target: must not overwrite the caller's `is_empty`.
490                Ok(AccountInfoLoad {
491                    account: Cow::Owned(AccountInfo::default()),
492                    is_cold: true,
493                    is_empty: true,
494                })
495            } else {
496                Ok(Default::default())
497            }
498        }
499    }
500
501    #[test]
502    fn load_account_delegated_keeps_caller_is_empty_not_delegate() {
503        // Regression: previously `is_empty` was overwritten with the empty
504        // delegate account's flag. Gas accounting / account-creation costs
505        // must use the EIP-7702 account itself (non-empty here).
506        let mut host = Eip7702Host::new();
507        let load = host
508            .load_account_delegated(host.delegated)
509            .expect("delegated account loads");
510
511        assert!(
512            load.data.is_delegate_account_cold.is_some(),
513            "delegate account must be loaded"
514        );
515        assert!(
516            !load.data.is_empty,
517            "is_empty must stay false for the non-empty EIP-7702 account"
518        );
519    }
520}