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    spec: SpecId,
214}
215
216impl DummyHost {
217    /// Create a new dummy host with the given spec.
218    pub fn new(spec: SpecId) -> Self {
219        Self {
220            gas_params: GasParams::new_spec(spec),
221            spec,
222        }
223    }
224}
225
226impl Host for DummyHost {
227    fn basefee(&self) -> U256 {
228        U256::ZERO
229    }
230
231    fn blob_gasprice(&self) -> U256 {
232        U256::ZERO
233    }
234
235    fn gas_limit(&self) -> U256 {
236        U256::ZERO
237    }
238
239    fn gas_params(&self) -> &GasParams {
240        &self.gas_params
241    }
242
243    fn is_amsterdam_eip8037_enabled(&self) -> bool {
244        self.spec.is_enabled_in(SpecId::AMSTERDAM)
245    }
246
247    fn difficulty(&self) -> U256 {
248        U256::ZERO
249    }
250
251    fn prevrandao(&self) -> Option<U256> {
252        None
253    }
254
255    fn block_number(&self) -> U256 {
256        U256::ZERO
257    }
258
259    fn timestamp(&self) -> U256 {
260        U256::ZERO
261    }
262
263    fn beneficiary(&self) -> Address {
264        Address::ZERO
265    }
266
267    fn slot_num(&self) -> U256 {
268        U256::ZERO
269    }
270
271    fn chain_id(&self) -> U256 {
272        U256::ZERO
273    }
274
275    fn effective_gas_price(&self) -> U256 {
276        U256::ZERO
277    }
278
279    fn caller(&self) -> Address {
280        Address::ZERO
281    }
282
283    fn blob_hash(&self, _number: usize) -> Option<U256> {
284        None
285    }
286
287    fn max_initcode_size(&self) -> usize {
288        0
289    }
290
291    fn block_hash(&mut self, _number: u64) -> Option<B256> {
292        None
293    }
294
295    fn selfdestruct(
296        &mut self,
297        _address: Address,
298        _target: Address,
299        _skip_cold_load: bool,
300    ) -> Result<StateLoad<SelfDestructResult>, LoadError> {
301        Ok(Default::default())
302    }
303
304    fn log(&mut self, _log: Log) {}
305
306    fn tstore(&mut self, _address: Address, _key: StorageKey, _value: StorageValue) {}
307
308    fn tload(&mut self, _address: Address, _key: StorageKey) -> StorageValue {
309        StorageValue::ZERO
310    }
311
312    fn load_account_info_skip_cold_load(
313        &mut self,
314        _address: Address,
315        _load_code: bool,
316        _skip_cold_load: bool,
317    ) -> Result<AccountInfoLoad<'_>, LoadError> {
318        Ok(Default::default())
319    }
320
321    fn sstore_skip_cold_load(
322        &mut self,
323        _address: Address,
324        _key: StorageKey,
325        _value: StorageValue,
326        _skip_cold_load: bool,
327    ) -> Result<StateLoad<SStoreResult>, LoadError> {
328        Ok(Default::default())
329    }
330
331    fn sload_skip_cold_load(
332        &mut self,
333        _address: Address,
334        _key: StorageKey,
335        _skip_cold_load: bool,
336    ) -> Result<StateLoad<StorageValue>, LoadError> {
337        Ok(Default::default())
338    }
339}
340
341#[cfg(test)]
342mod tests {
343    use super::*;
344    use primitives::{hardfork::SpecId, Address, U256};
345    use state::{AccountInfo, Bytecode};
346    use std::borrow::Cow;
347
348    /// Host used to regression-test [`Host::load_account_delegated`].
349    ///
350    /// `delegated` is a non-empty EIP-7702 account pointing at an empty `delegate`.
351    struct Eip7702Host {
352        dummy: DummyHost,
353        delegated: Address,
354        delegate: Address,
355        delegated_info: AccountInfo,
356    }
357
358    impl Eip7702Host {
359        fn new() -> Self {
360            let delegated = Address::repeat_byte(0x11);
361            let delegate = Address::repeat_byte(0x22);
362            let delegated_info = AccountInfo::new(
363                U256::from(1),
364                1,
365                B256::ZERO,
366                Bytecode::new_eip7702(delegate),
367            );
368            Self {
369                dummy: DummyHost::new(SpecId::PRAGUE),
370                delegated,
371                delegate,
372                delegated_info,
373            }
374        }
375    }
376
377    impl Host for Eip7702Host {
378        fn basefee(&self) -> U256 {
379            self.dummy.basefee()
380        }
381        fn blob_gasprice(&self) -> U256 {
382            self.dummy.blob_gasprice()
383        }
384        fn gas_limit(&self) -> U256 {
385            self.dummy.gas_limit()
386        }
387        fn gas_params(&self) -> &GasParams {
388            self.dummy.gas_params()
389        }
390        fn is_amsterdam_eip8037_enabled(&self) -> bool {
391            self.dummy.is_amsterdam_eip8037_enabled()
392        }
393        fn difficulty(&self) -> U256 {
394            self.dummy.difficulty()
395        }
396        fn prevrandao(&self) -> Option<U256> {
397            self.dummy.prevrandao()
398        }
399        fn block_number(&self) -> U256 {
400            self.dummy.block_number()
401        }
402        fn timestamp(&self) -> U256 {
403            self.dummy.timestamp()
404        }
405        fn beneficiary(&self) -> Address {
406            self.dummy.beneficiary()
407        }
408        fn slot_num(&self) -> U256 {
409            self.dummy.slot_num()
410        }
411        fn chain_id(&self) -> U256 {
412            self.dummy.chain_id()
413        }
414        fn effective_gas_price(&self) -> U256 {
415            self.dummy.effective_gas_price()
416        }
417        fn caller(&self) -> Address {
418            self.dummy.caller()
419        }
420        fn blob_hash(&self, number: usize) -> Option<U256> {
421            self.dummy.blob_hash(number)
422        }
423        fn max_initcode_size(&self) -> usize {
424            self.dummy.max_initcode_size()
425        }
426        fn block_hash(&mut self, number: u64) -> Option<B256> {
427            self.dummy.block_hash(number)
428        }
429        fn selfdestruct(
430            &mut self,
431            address: Address,
432            target: Address,
433            skip_cold_load: bool,
434        ) -> Result<StateLoad<SelfDestructResult>, LoadError> {
435            self.dummy.selfdestruct(address, target, skip_cold_load)
436        }
437        fn log(&mut self, log: Log) {
438            self.dummy.log(log)
439        }
440        fn tstore(&mut self, address: Address, key: StorageKey, value: StorageValue) {
441            self.dummy.tstore(address, key, value)
442        }
443        fn tload(&mut self, address: Address, key: StorageKey) -> StorageValue {
444            self.dummy.tload(address, key)
445        }
446        fn sstore_skip_cold_load(
447            &mut self,
448            address: Address,
449            key: StorageKey,
450            value: StorageValue,
451            skip_cold_load: bool,
452        ) -> Result<StateLoad<SStoreResult>, LoadError> {
453            self.dummy
454                .sstore_skip_cold_load(address, key, value, skip_cold_load)
455        }
456        fn sload_skip_cold_load(
457            &mut self,
458            address: Address,
459            key: StorageKey,
460            skip_cold_load: bool,
461        ) -> Result<StateLoad<StorageValue>, LoadError> {
462            self.dummy
463                .sload_skip_cold_load(address, key, skip_cold_load)
464        }
465
466        fn load_account_info_skip_cold_load(
467            &mut self,
468            address: Address,
469            _load_code: bool,
470            _skip_cold_load: bool,
471        ) -> Result<AccountInfoLoad<'_>, LoadError> {
472            if address == self.delegated {
473                Ok(AccountInfoLoad {
474                    account: Cow::Owned(self.delegated_info.clone()),
475                    is_cold: false,
476                    is_empty: false,
477                })
478            } else if address == self.delegate {
479                // Empty delegated target: must not overwrite the caller's `is_empty`.
480                Ok(AccountInfoLoad {
481                    account: Cow::Owned(AccountInfo::default()),
482                    is_cold: true,
483                    is_empty: true,
484                })
485            } else {
486                Ok(Default::default())
487            }
488        }
489    }
490
491    #[test]
492    fn load_account_delegated_keeps_caller_is_empty_not_delegate() {
493        // Regression: previously `is_empty` was overwritten with the empty
494        // delegate account's flag. Gas accounting / account-creation costs
495        // must use the EIP-7702 account itself (non-empty here).
496        let mut host = Eip7702Host::new();
497        let load = host
498            .load_account_delegated(host.delegated)
499            .expect("delegated account loads");
500
501        assert!(
502            load.data.is_delegate_account_cold.is_some(),
503            "delegate account must be loaded"
504        );
505        assert!(
506            !load.data.is_empty,
507            "is_empty must stay false for the non-empty EIP-7702 account"
508        );
509    }
510}