evm_rs_emulator/core_module/state.rs
1use std::{collections::HashMap, fmt};
2
3use ethers::prelude::*;
4use ethers::{types::U256, utils::keccak256};
5
6use crate::core_module::utils;
7
8use super::utils::errors::ExecutionError;
9
10// Colored output
11use colored::*;
12
13/* -------------------------------------------------------------------------- */
14/* AccountState struct */
15/* -------------------------------------------------------------------------- */
16
17/// Represents the state of an account on the Ethereum Virtual Machine.
18pub struct AccountState {
19 /// The account's nonce, which is incremented each time a transaction is sent from the account.
20 pub nonce: u64,
21 /// The account's balance, represented as a 32-byte array.
22 pub balance: [u8; 32],
23 /// The account's storage, represented as a hashmap where the keys and values are both 32-byte arrays.
24 pub storage: HashMap<[u8; 32], [u8; 32]>,
25 /// The hash of the account's code, represented as a 32-byte array.
26 pub code_hash: [u8; 32],
27}
28
29/// Implements the Debug trait for the AccountState struct, which allows for the struct to be printed in a formatted way.
30/// The function prints the nonce, balance, code hash, and storage of the account state.
31/// If the code hash is empty, it prints "Empty code" instead of the hash.
32/// For each storage slot and value, it prints them in a formatted way.
33/// If the storage is empty, it prints "Empty storage".
34impl fmt::Debug for AccountState {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 let mut code_hash: String = utils::debug::to_hex_string(self.code_hash);
37 if self.code_hash == [0u8; 32] {
38 code_hash = format!("{}", "Empty code".red()).to_string()
39 }
40
41 writeln!(f, " {}: {}", "Nonce".magenta(), self.nonce)?;
42 writeln!(
43 f,
44 " {}: {}",
45 "Balance".magenta(),
46 U256::from(self.balance).to_string()
47 )?;
48 writeln!(f, " {}: {}", "Code Hash".magenta(), code_hash)?;
49 write!(f, " {}: ", "Storage".magenta())?;
50 for (slot, value) in &self.storage {
51 println!("\n┌────────────────────────────────────────────────────────────────────────────────────────────────────────┐");
52 // Print the slot
53 let hex: String = utils::debug::to_hex_string(slot.to_owned());
54 println!("│ {}: {} │", "Slot".bright_blue(), hex);
55
56 // Print the value
57 let hex: String = utils::debug::to_hex_string(value.to_owned());
58 println!("│ {}: {} │", "Value".blue(), hex);
59
60 println!("└────────────────────────────────────────────────────────────────────────────────────────────────────────┘");
61 }
62 if self.storage.is_empty() {
63 write!(f, " {}", "Empty storage".red())?;
64 }
65 Ok(())
66 }
67}
68
69/* -------------------------------------------------------------------------- */
70/* Log struct */
71/* -------------------------------------------------------------------------- */
72
73/// Represents a log entry in the Ethereum Virtual Machine (EVM) state.
74pub struct Log {
75 /// The address of the contract that generated the log.
76 pub address: [u8; 20],
77 /// The topics associated with the log.
78 pub topics: Vec<[u8; 32]>,
79 /// The data associated with the log.
80 pub data: Vec<u8>,
81}
82
83/// Implements the Debug trait for the Log struct, which allows for the struct to be printed in a formatted way.
84/// The function writes the address, topics, and data of the Log struct to the provided formatter.
85/// If the topics vector is not empty, it prints each topic in a formatted way.
86/// Otherwise, it prints "No topics".
87impl fmt::Debug for Log {
88 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89 writeln!(
90 f,
91 "{}: {}",
92 "Address".magenta(),
93 utils::debug::to_hex_address(self.address)
94 )?;
95
96 write!(f, "{}: ", "Topics".magenta())?;
97 if !self.topics.is_empty() {
98 for (idx, topic) in self.topics.iter().enumerate() {
99 println!("\n┌────────────────────────────────────────────────────────────────────────────────────────────────────────┐");
100 let hex: String = utils::debug::to_hex_string(topic.to_owned());
101 println!("│ {}: {} {} │", "Topic".bright_blue(), idx, hex);
102 println!("└────────────────────────────────────────────────────────────────────────────────────────────────────────┘");
103 }
104 } else {
105 writeln!(f, "{}", "No topics".red())?;
106 }
107
108 writeln!(
109 f,
110 "{}: {}",
111 "Data".magenta(),
112 utils::debug::vec_to_hex_string(self.data.clone())
113 )?;
114
115 Ok(())
116 }
117}
118
119/* -------------------------------------------------------------------------- */
120/* EVM state struct */
121/* -------------------------------------------------------------------------- */
122
123/// Represents the state of the Ethereum Virtual Machine (EVM).
124#[derive(Debug)]
125pub struct EvmState {
126 /// A mapping of account addresses to their respective account states.
127 pub accounts: HashMap<[u8; 20], AccountState>,
128 /// A mapping of code hashes to their respective code.
129 pub codes: HashMap<[u8; 32], Vec<u8>>,
130 /// A vector of logs generated during the execution of the EVM.
131 pub logs: Vec<Log>,
132 /// A flag indicating whether the EVM is in static mode or not.
133 pub static_mode: bool,
134 /// An optional provider for interacting with the Ethereum network.
135 pub provider: Option<Provider<Http>>,
136}
137
138/// Implementation of the EVM state.
139impl EvmState {
140 /// Creates a new instance of the `State` struct.
141 ///
142 /// # Arguments
143 ///
144 /// * `fork_url` - An optional `String` representing the URL of the fork to use.
145 ///
146 /// # Returns
147 ///
148 /// A new instance of the `State` struct.
149 pub fn new(fork_url: Option<String>) -> Self {
150 Self {
151 accounts: HashMap::new(),
152 codes: HashMap::new(),
153 logs: Vec::new(),
154 static_mode: false,
155 provider: if fork_url.is_some() {
156 Some(Provider::<Http>::try_from(fork_url.unwrap()).unwrap())
157 } else {
158 None
159 },
160 }
161 }
162
163 // Transfer value from one account to another
164 /// Transfers a given value from one account to another.
165 ///
166 /// # Arguments
167 ///
168 /// * `from` - An array of 20 bytes representing the address of the account to transfer from.
169 /// * `to` - An array of 20 bytes representing the address of the account to transfer to.
170 /// * `value` - An array of 32 bytes representing the value to transfer.
171 ///
172 /// # Errors
173 ///
174 /// Returns an `ExecutionError` if:
175 ///
176 /// * The static mode is enabled (static call).
177 /// * The account to transfer from does not exist.
178 /// * The account to transfer to does not exist.
179 /// * The balance of the account to transfer from is insufficient.
180 ///
181 /// # Returns
182 ///
183 /// Returns `Ok(())` if the transfer was successful.
184 pub fn transfer(
185 &mut self,
186 from: [u8; 20],
187 to: [u8; 20],
188 value: [u8; 32],
189 ) -> Result<(), ExecutionError> {
190 // Check if static mode is enabled
191 if self.static_mode {
192 return Err(ExecutionError::StaticCallStateChanged);
193 }
194
195 let value_u256 = U256::from_big_endian(&value);
196
197 let from_balance = U256::from_big_endian(
198 &self
199 .accounts
200 .get(&from)
201 .ok_or(ExecutionError::AccountNotFound)?
202 .balance,
203 );
204
205 let to_balance = U256::from_big_endian(
206 &self
207 .accounts
208 .get(&to)
209 .ok_or(ExecutionError::AccountNotFound)?
210 .balance,
211 );
212
213 // Check if the balance is sufficient
214 if from_balance < value_u256 {
215 return Err(ExecutionError::InsufficientBalance);
216 }
217
218 // Transfer the value
219 let new_from_balance = from_balance - value_u256;
220 let new_to_balance = to_balance + value_u256;
221
222 if let Some(from_account) = self.accounts.get_mut(&from) {
223 let mut result_bytes = [0u8; 32];
224 new_from_balance.to_big_endian(&mut result_bytes);
225 from_account.balance = result_bytes;
226 }
227
228 if let Some(to_account) = self.accounts.get_mut(&to) {
229 let mut result_bytes = [0u8; 32];
230 new_to_balance.to_big_endian(&mut result_bytes);
231 to_account.balance = result_bytes;
232 }
233
234 Ok(())
235 }
236
237 /// Loads a 256-bit value from the storage of the given account at the given slot.
238 /// If the account is not found in the emulator's local state, the storage value is fetched from the provider.
239 /// If the provider is not set, or if the storage fetch fails, the function returns a zero-filled 256-bit value.
240 ///
241 /// # Arguments
242 ///
243 /// * `account` - An array of 20 bytes representing the address of the account to load from.
244 /// * `slot` - An array of 32 bytes representing the slot to load from.
245 ///
246 /// # Errors
247 ///
248 /// Never return an error, but returns a 32-byte array of zero instead.
249 ///
250 /// # Returns
251 ///
252 /// Returns a 32-byte array representing the value at the given slot.
253 pub fn sload(&mut self, account: [u8; 20], slot: [u8; 32]) -> Result<[u8; 32], ExecutionError> {
254 match self.accounts.get(&account) {
255 Some(account_state) => match account_state.storage.get(&slot) {
256 Some(value) => Ok(*value),
257 None => Ok([0u8; 32]),
258 },
259 None => {
260 let provider = self.provider.as_ref();
261
262 if provider.is_none() {
263 return Ok([0u8; 32]);
264 }
265
266 let contract_address = Address::from(account);
267 let future =
268 provider
269 .unwrap()
270 .get_storage_at(contract_address, H256::from(&slot), None);
271
272 // Block on the future and get the result
273 let storage_result = tokio::runtime::Runtime::new()
274 .expect("Could not create a Runtime")
275 .block_on(future);
276
277 match storage_result {
278 Ok(storage) => {
279 let storage_bytes = storage.to_fixed_bytes();
280
281 // Save the fetched storage data locally
282 if let Some(account_state) = self.accounts.get_mut(&account) {
283 account_state.storage.insert(slot, storage_bytes);
284 }
285
286 Ok(storage_bytes)
287 }
288 Err(_) => Ok([0u8; 32]),
289 }
290 }
291 }
292 }
293
294 // Store a 32 bytes word in storage of a specific account
295 /// Stores a value in the storage of an account.
296 ///
297 /// # Arguments
298 ///
299 /// * `account` - The address of the account to store the value in.
300 /// * `slot` - The slot in the storage to store the value in.
301 /// * `value` - The value to store in the specified slot.
302 ///
303 /// # Errors
304 ///
305 /// Returns an `ExecutionError` if the static mode is enabled or if the account is not found.
306 pub fn sstore(
307 &mut self,
308 account: [u8; 20],
309 slot: [u8; 32],
310 value: [u8; 32],
311 ) -> Result<(), ExecutionError> {
312 // Check if static mode is enabled
313 if self.static_mode {
314 return Err(ExecutionError::StaticCallStateChanged);
315 }
316
317 match self.accounts.get_mut(&account) {
318 Some(account_state) => {
319 account_state.storage.insert(slot, value);
320 Ok(())
321 }
322 None => Err(ExecutionError::AccountNotFound),
323 }
324 }
325
326 /// Returns the code at the given address. If the code is not already in the state, it will be fetched from the blockchain using the provider.
327 ///
328 /// # Arguments
329 ///
330 /// * `address` - The address of the contract to get the code for.
331 ///
332 /// # Errors
333 ///
334 /// Returns an `ExecutionError` if the code is not found.
335 ///
336 /// # Returns
337 ///
338 /// Returns a reference to the Vec<u8> of the code.
339 pub fn get_code_at(&mut self, address: [u8; 20]) -> Result<&Vec<u8>, ExecutionError> {
340 match self.accounts.get(&address) {
341 Some(account_state) => {
342 let code_hash = account_state.code_hash;
343 self.get_code(code_hash)
344 }
345 None => {
346 let provider = self.provider.as_ref();
347
348 if provider.is_none() {
349 return Err(ExecutionError::CodeNotFound);
350 }
351
352 // Asynchronously fetch the code from the blockchain
353 let contract_address = Address::from(address);
354
355 let future = provider.unwrap().get_code(contract_address, None);
356
357 // Block on the future and get the result
358 let code_result = tokio::runtime::Runtime::new()
359 .expect("Could not create a Runtime")
360 .block_on(future);
361
362 match code_result {
363 Ok(code) => {
364 let code_hash = keccak256(&code.0);
365 if let Some(account) = self.accounts.get_mut(&address) {
366 account.code_hash = code_hash;
367 }
368 self.codes.insert(code_hash, code.to_vec());
369 Ok(&self.codes[&code_hash])
370 }
371 Err(_) => Err(ExecutionError::CodeNotFound),
372 }
373 }
374 }
375 }
376
377 /// Stores the code of an account at the given address.
378 ///
379 /// # Arguments
380 ///
381 /// * `address` - A 20-byte array representing the address of the account.
382 /// * `code` - A vector of bytes representing the code to be stored.
383 ///
384 /// # Errors
385 ///
386 /// Returns an `ExecutionError` if the account is not found.
387 ///
388 /// # Returns
389 ///
390 /// Returns `Ok(())` if the code is successfully stored.
391 pub fn put_code_at(&mut self, address: [u8; 20], code: Vec<u8>) -> Result<(), ExecutionError> {
392 let code_hash = self.put_code(code)?;
393
394 match self.accounts.get_mut(&address) {
395 Some(account_state) => {
396 account_state.code_hash = code_hash.to_owned();
397 Ok(())
398 }
399 None => Err(ExecutionError::AccountNotFound),
400 }
401 }
402
403 /// Returns a reference to the code associated with the given code hash.
404 ///
405 /// # Arguments
406 ///
407 /// * `code_hash` - A 32-byte array representing the hash of the code to retrieve.
408 ///
409 /// # Errors
410 ///
411 /// Returns an `ExecutionError::CodeNotFound` error if the code hash is not found in the state.
412 ///
413 /// # Returns
414 ///
415 /// Returns a reference to the code associated with the given code hash.
416 fn get_code(&self, code_hash: [u8; 32]) -> Result<&Vec<u8>, ExecutionError> {
417 self.codes
418 .get(&code_hash)
419 .ok_or(ExecutionError::CodeNotFound)
420 }
421
422 /// Store contract code and return its hash
423 fn put_code(&mut self, code: Vec<u8>) -> Result<[u8; 32], ExecutionError> {
424 // Check if static mode is enabled
425 if self.static_mode {
426 return Err(ExecutionError::StaticCallStateChanged);
427 }
428
429 let code_hash = keccak256(&code);
430 self.codes.insert(code_hash, code);
431 Ok(code_hash)
432 }
433
434 /// Print the state of the EVM
435 /// This function is used for debugging purposes.
436 pub fn debug_state(&mut self) {
437 let border_line =
438 "\n╔═══════════════════════════════════════════════════════════════════════════════════════════════════════╗";
439 let footer_line =
440 "╚═══════════════════════════════════════════════════════════════════════════════════════════════════════╝";
441
442 // Print out the storage
443 println!("\n{}", border_line.clone().red());
444 println!(
445 "{} {:<101} {}",
446 "║".red(),
447 "Final storage".yellow(),
448 "║".red()
449 );
450 println!("{}", footer_line.clone().red());
451
452 // ... other code ...
453
454 // Create a vector of all addresses
455 let addresses: Vec<_> = self.accounts.keys().cloned().collect();
456
457 // Iterate over the vector of addresses
458 for address in addresses {
459 let account_state = &self.accounts[&address];
460 let hex: String = utils::debug::to_hex_address(address.to_owned());
461 println!("{}", hex.blue());
462 println!("{:?}", account_state);
463 let code_hash = account_state.code_hash;
464 if code_hash != [0u8; 32] {
465 let code = self.get_code_at(address.to_owned()).unwrap();
466 let code_hex: String = utils::debug::vec_to_hex_string(code.to_owned());
467 println!(" {}: {}", "Code".magenta(), code_hex);
468 }
469 println!("\n");
470 }
471
472 if self.accounts.is_empty() {
473 println!("Empty EVM state");
474 }
475 }
476}