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