linera_execution/evm/
database.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Code specific to the usage of the [Revm](https://bluealloy.github.io/revm/) runtime.
5//! Here we implement the Database traits of Revm.
6
7use std::{
8    collections::HashMap,
9    sync::{Arc, Mutex},
10};
11
12use linera_base::vm::VmRuntime;
13use linera_views::common::from_bytes_option;
14use revm::{primitives::keccak256, Database, DatabaseCommit, DatabaseRef};
15use revm_context::BlockEnv;
16use revm_context_interface::block::BlobExcessGasAndPrice;
17use revm_database::{AccountState, DBErrorMarker};
18use revm_primitives::{address, Address, B256, U256};
19use revm_state::{AccountInfo, Bytecode, EvmState};
20
21use crate::{ApplicationId, BaseRuntime, Batch, ContractRuntime, ExecutionError, ServiceRuntime};
22
23// The runtime costs are not available in service operations.
24// We need to set a limit to gas usage in order to avoid blocking
25// the validator.
26// We set up the limit similarly to Infura to 20 million.
27pub const EVM_SERVICE_GAS_LIMIT: u64 = 20_000_000;
28
29/// The cost of loading from storage.
30const SLOAD_COST: u64 = 2100;
31
32/// The cost of storing a non-zero value in the storage for the first time.
33const SSTORE_COST_SET: u64 = 20000;
34
35/// The cost of not changing the state of the variable in the storage.
36const SSTORE_COST_NO_OPERATION: u64 = 100;
37
38/// The cost of overwriting the storage to a different value.
39const SSTORE_COST_RESET: u64 = 2900;
40
41/// The refund from releasing data.
42const SSTORE_REFUND_RELEASE: u64 = 4800;
43
44/// The number of key writes, reads, release, and no change in EVM has to be accounted for.
45/// Then we remove those costs from the final bill.
46#[derive(Clone, Default)]
47pub(crate) struct StorageStats {
48    key_no_operation: u64,
49    key_reset: u64,
50    key_set: u64,
51    key_release: u64,
52    key_read: u64,
53}
54
55impl StorageStats {
56    pub fn storage_costs(&self) -> u64 {
57        let mut storage_costs = 0;
58        storage_costs += self.key_no_operation * SSTORE_COST_NO_OPERATION;
59        storage_costs += self.key_reset * SSTORE_COST_RESET;
60        storage_costs += self.key_set * SSTORE_COST_SET;
61        storage_costs += self.key_read * SLOAD_COST;
62        storage_costs
63    }
64
65    pub fn storage_refund(&self) -> u64 {
66        self.key_release * SSTORE_REFUND_RELEASE
67    }
68}
69
70/// This is the encapsulation of the `Runtime` corresponding to the contract.
71pub(crate) struct DatabaseRuntime<Runtime> {
72    /// This is the storage statistics of the read/write in order to adjust gas costs.
73    storage_stats: Arc<Mutex<StorageStats>>,
74    /// This is the EVM address of the contract.
75    /// At the creation, is is set to `Address::ZERO` and then later set to the correct value.
76    pub contract_address: Address,
77    /// The runtime of the contract.
78    pub runtime: Arc<Mutex<Runtime>>,
79    /// The uncommited changes to the contract.
80    pub changes: EvmState,
81}
82
83impl<Runtime> Clone for DatabaseRuntime<Runtime> {
84    fn clone(&self) -> Self {
85        Self {
86            storage_stats: self.storage_stats.clone(),
87            contract_address: self.contract_address,
88            runtime: self.runtime.clone(),
89            changes: self.changes.clone(),
90        }
91    }
92}
93
94#[repr(u8)]
95enum KeyTag {
96    /// Key prefix for the storage of the zero contract.
97    NullAddress,
98    /// Key prefix for the storage of the contract address.
99    ContractAddress,
100}
101
102#[repr(u8)]
103pub enum KeyCategory {
104    AccountInfo,
105    AccountState,
106    Storage,
107}
108
109fn application_id_to_address(application_id: ApplicationId) -> Address {
110    let application_id: [u64; 4] = <[u64; 4]>::from(application_id.application_description_hash);
111    let application_id: [u8; 32] = linera_base::crypto::u64_array_to_be_bytes(application_id);
112    Address::from_slice(&application_id[0..20])
113}
114
115impl<Runtime: BaseRuntime> DatabaseRuntime<Runtime> {
116    /// Encode the `index` of the EVM storage associated to the smart contract
117    /// in a linera key.
118    fn get_linera_key(val: u8, index: U256) -> Result<Vec<u8>, ExecutionError> {
119        let mut key = vec![val, KeyCategory::Storage as u8];
120        bcs::serialize_into(&mut key, &index)?;
121        Ok(key)
122    }
123
124    /// Returns the tag associated to the contract.
125    fn get_contract_address_key(&self, address: &Address) -> Option<u8> {
126        if address == &Address::ZERO {
127            return Some(KeyTag::NullAddress as u8);
128        }
129        if address == &self.contract_address {
130            return Some(KeyTag::ContractAddress as u8);
131        }
132        None
133    }
134
135    /// Creates a new `DatabaseRuntime`.
136    pub fn new(runtime: Runtime) -> Self {
137        let storage_stats = StorageStats::default();
138        // We cannot acquire a lock on runtime here.
139        // So, we set the contract_address to a default value
140        // and update it later.
141        Self {
142            storage_stats: Arc::new(Mutex::new(storage_stats)),
143            contract_address: Address::ZERO,
144            runtime: Arc::new(Mutex::new(runtime)),
145            changes: HashMap::new(),
146        }
147    }
148
149    /// Returns the current storage states and clears it to default.
150    pub fn take_storage_stats(&self) -> StorageStats {
151        let mut storage_stats_read = self
152            .storage_stats
153            .lock()
154            .expect("The lock should be possible");
155        let storage_stats = storage_stats_read.clone();
156        *storage_stats_read = StorageStats::default();
157        storage_stats
158    }
159}
160
161impl DBErrorMarker for ExecutionError {}
162
163impl<Runtime> Database for DatabaseRuntime<Runtime>
164where
165    Runtime: BaseRuntime,
166{
167    type Error = ExecutionError;
168
169    fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, ExecutionError> {
170        self.basic_ref(address)
171    }
172
173    fn code_by_hash(&mut self, _code_hash: B256) -> Result<Bytecode, ExecutionError> {
174        panic!("Functionality code_by_hash not implemented");
175    }
176
177    fn storage(&mut self, address: Address, index: U256) -> Result<U256, ExecutionError> {
178        self.storage_ref(address, index)
179    }
180
181    fn block_hash(&mut self, number: u64) -> Result<B256, ExecutionError> {
182        <Self as DatabaseRef>::block_hash_ref(self, number)
183    }
184}
185
186impl<Runtime> DatabaseCommit for DatabaseRuntime<Runtime>
187where
188    Runtime: BaseRuntime,
189{
190    fn commit(&mut self, changes: EvmState) {
191        self.changes = changes;
192    }
193}
194
195impl<Runtime> DatabaseRef for DatabaseRuntime<Runtime>
196where
197    Runtime: BaseRuntime,
198{
199    type Error = ExecutionError;
200
201    fn basic_ref(&self, address: Address) -> Result<Option<AccountInfo>, ExecutionError> {
202        if !self.changes.is_empty() {
203            let account = self.changes.get(&address).unwrap();
204            return Ok(Some(account.info.clone()));
205        }
206        let mut runtime = self.runtime.lock().expect("The lock should be possible");
207        let val = self.get_contract_address_key(&address);
208        let Some(val) = val else {
209            return Ok(Some(AccountInfo::default()));
210        };
211        let key_info = vec![val, KeyCategory::AccountInfo as u8];
212        let promise = runtime.read_value_bytes_new(key_info)?;
213        let result = runtime.read_value_bytes_wait(&promise)?;
214        let account_info = from_bytes_option::<AccountInfo>(&result)?;
215        Ok(account_info)
216    }
217
218    fn code_by_hash_ref(&self, _code_hash: B256) -> Result<Bytecode, ExecutionError> {
219        panic!("Functionality code_by_hash_ref not implemented");
220    }
221
222    fn storage_ref(&self, address: Address, index: U256) -> Result<U256, ExecutionError> {
223        if !self.changes.is_empty() {
224            let account = self.changes.get(&address).unwrap();
225            return Ok(match account.storage.get(&index) {
226                None => U256::ZERO,
227                Some(slot) => slot.present_value(),
228            });
229        }
230        let val = self.get_contract_address_key(&address);
231        let Some(val) = val else {
232            panic!("There is no storage associated to externally owned account");
233        };
234        let key = Self::get_linera_key(val, index)?;
235        {
236            let mut storage_stats = self
237                .storage_stats
238                .lock()
239                .expect("The lock should be possible");
240            storage_stats.key_read += 1;
241        }
242        let result = {
243            let mut runtime = self.runtime.lock().expect("The lock should be possible");
244            let promise = runtime.read_value_bytes_new(key)?;
245            runtime.read_value_bytes_wait(&promise)
246        }?;
247        Ok(from_bytes_option::<U256>(&result)?.unwrap_or_default())
248    }
249
250    fn block_hash_ref(&self, number: u64) -> Result<B256, ExecutionError> {
251        Ok(keccak256(number.to_string().as_bytes()))
252    }
253}
254
255impl<Runtime> DatabaseRuntime<Runtime>
256where
257    Runtime: ContractRuntime,
258{
259    /// Effectively commits changes to storage.
260    pub fn commit_changes(&mut self) -> Result<(), ExecutionError> {
261        let mut storage_stats = self
262            .storage_stats
263            .lock()
264            .expect("The lock should be possible");
265        let mut runtime = self.runtime.lock().expect("The lock should be possible");
266        let mut batch = Batch::new();
267        let mut list_new_balances = Vec::new();
268        for (address, account) in &self.changes {
269            if !account.is_touched() {
270                continue;
271            }
272            let val = self.get_contract_address_key(address);
273            if let Some(val) = val {
274                let key_prefix = vec![val, KeyCategory::Storage as u8];
275                let key_info = vec![val, KeyCategory::AccountInfo as u8];
276                let key_state = vec![val, KeyCategory::AccountState as u8];
277                if account.is_selfdestructed() {
278                    batch.delete_key_prefix(key_prefix);
279                    batch.put_key_value(key_info, &AccountInfo::default())?;
280                    batch.put_key_value(key_state, &AccountState::NotExisting)?;
281                } else {
282                    let is_newly_created = account.is_created();
283                    batch.put_key_value(key_info, &account.info)?;
284
285                    let account_state = if is_newly_created {
286                        batch.delete_key_prefix(key_prefix);
287                        AccountState::StorageCleared
288                    } else {
289                        let promise = runtime.read_value_bytes_new(key_state.clone())?;
290                        let result = runtime.read_value_bytes_wait(&promise)?;
291                        let account_state =
292                            from_bytes_option::<AccountState>(&result)?.unwrap_or_default();
293                        if account_state.is_storage_cleared() {
294                            AccountState::StorageCleared
295                        } else {
296                            AccountState::Touched
297                        }
298                    };
299                    batch.put_key_value(key_state, &account_state)?;
300                    for (index, value) in &account.storage {
301                        if value.present_value() == value.original_value() {
302                            storage_stats.key_no_operation += 1;
303                        } else {
304                            let key = Self::get_linera_key(val, *index)?;
305                            if value.original_value() == U256::ZERO {
306                                batch.put_key_value(key, &value.present_value())?;
307                                storage_stats.key_set += 1;
308                            } else if value.present_value() == U256::ZERO {
309                                batch.delete_key(key);
310                                storage_stats.key_release += 1;
311                            } else {
312                                batch.put_key_value(key, &value.present_value())?;
313                                storage_stats.key_reset += 1;
314                            }
315                        }
316                    }
317                }
318            } else {
319                if !account.storage.is_empty() {
320                    panic!("For user account, storage must be empty");
321                }
322                // TODO(#3756): Implement EVM transfers within Linera.
323                // The only allowed operations are the ones for the
324                // account balances.
325                if account.info.balance != U256::ZERO {
326                    let new_balance = (address, account.info.balance);
327                    list_new_balances.push(new_balance);
328                }
329            }
330        }
331        runtime.write_batch(batch)?;
332        if !list_new_balances.is_empty() {
333            panic!("The conversion Ethereum address / Linera address is not yet implemented");
334        }
335        self.changes.clear();
336        Ok(())
337    }
338}
339
340impl<Runtime> DatabaseRuntime<Runtime>
341where
342    Runtime: BaseRuntime,
343{
344    /// Reads the nonce of the user
345    pub fn get_nonce(&self, address: &Address) -> Result<u64, ExecutionError> {
346        let account_info = self.basic_ref(*address)?;
347        Ok(match account_info {
348            None => 0,
349            Some(account_info) => account_info.nonce,
350        })
351    }
352
353    /// Sets the EVM contract address from the value Address::ZERO.
354    /// The value is set from the `ApplicationId`.
355    pub fn set_contract_address(&mut self) -> Result<(), ExecutionError> {
356        let mut runtime = self.runtime.lock().expect("The lock should be possible");
357        let application_id = runtime.application_id()?;
358        self.contract_address = application_id_to_address(application_id);
359        Ok(())
360    }
361
362    /// Checks if the contract is already initialized. It is possible
363    /// that the constructor has not yet been called.
364    pub fn is_initialized(&self) -> Result<bool, ExecutionError> {
365        let mut keys = Vec::new();
366        for key_tag in [KeyTag::NullAddress, KeyTag::ContractAddress] {
367            let key = vec![key_tag as u8, KeyCategory::AccountInfo as u8];
368            keys.push(key);
369        }
370        let mut runtime = self.runtime.lock().expect("The lock should be possible");
371        let promise = runtime.contains_keys_new(keys)?;
372        let result = runtime.contains_keys_wait(&promise)?;
373        Ok(result[0] && result[1])
374    }
375
376    pub fn get_block_env(&self) -> Result<BlockEnv, ExecutionError> {
377        let mut runtime = self.runtime.lock().expect("The lock should be possible");
378        // The block height being used
379        let block_height_linera = runtime.block_height()?;
380        let block_height_evm = block_height_linera.0;
381        // This is the receiver address of all the gas spent in the block.
382        let beneficiary = address!("00000000000000000000000000000000000000bb");
383        // The difficulty which is no longer relevant after The Merge.
384        let difficulty = U256::ZERO;
385        // We do not have access to the Resources so we keep it to the maximum
386        // and the control is done elsewhere.
387        let gas_limit = u64::MAX;
388        // The timestamp. Both the EVM and Linera use the same UNIX epoch.
389        // But the Linera epoch is in microseconds since the start and the
390        // Ethereum epoch is in seconds
391        let timestamp_linera = runtime.read_system_timestamp()?;
392        let timestamp_evm = timestamp_linera.micros() / 1_000_000;
393        // The basefee is the minimum feee for executing. We have no such
394        // concept in Linera
395        let basefee = 0;
396        let chain_id = runtime.chain_id()?;
397        let entry = format!("{}{}", chain_id, block_height_linera);
398        // The randomness beacon being used.
399        let prevrandao = keccak256(entry.as_bytes());
400        // The blob excess gas and price is not relevant to the execution
401        // on Linera. We set up a default value as in REVM.
402        let entry = BlobExcessGasAndPrice {
403            excess_blob_gas: 0,
404            blob_gasprice: 1,
405        };
406        let blob_excess_gas_and_price = Some(entry);
407        Ok(BlockEnv {
408            number: block_height_evm,
409            beneficiary,
410            difficulty,
411            gas_limit,
412            timestamp: timestamp_evm,
413            basefee,
414            prevrandao: Some(prevrandao),
415            blob_excess_gas_and_price,
416        })
417    }
418
419    pub fn constructor_argument(&self) -> Result<Vec<u8>, ExecutionError> {
420        let mut runtime = self.runtime.lock().expect("The lock should be possible");
421        let constructor_argument = runtime.application_parameters()?;
422        Ok(serde_json::from_slice::<Vec<u8>>(&constructor_argument)?)
423    }
424}
425
426impl<Runtime> DatabaseRuntime<Runtime>
427where
428    Runtime: ContractRuntime,
429{
430    pub fn get_contract_block_env(&self) -> Result<BlockEnv, ExecutionError> {
431        let mut block_env = self.get_block_env()?;
432        let mut runtime = self.runtime.lock().expect("The lock should be possible");
433        // We use the gas_limit from the runtime
434        let gas_limit = runtime.maximum_fuel_per_block(VmRuntime::Evm)?;
435        block_env.gas_limit = gas_limit;
436        Ok(block_env)
437    }
438}
439
440impl<Runtime> DatabaseRuntime<Runtime>
441where
442    Runtime: ServiceRuntime,
443{
444    pub fn get_service_block_env(&self) -> Result<BlockEnv, ExecutionError> {
445        let mut block_env = self.get_block_env()?;
446        block_env.gas_limit = EVM_SERVICE_GAS_LIMIT;
447        Ok(block_env)
448    }
449}