diff --git a/.github/workflows/ethereum-tests.yml b/.github/workflows/ethereum-tests.yml index 22f2b6766d..13384d56d7 100644 --- a/.github/workflows/ethereum-tests.yml +++ b/.github/workflows/ethereum-tests.yml @@ -21,6 +21,7 @@ jobs: with: repository: ethereum/tests path: ethtests + submodules: recursive, - name: Install toolchain uses: actions-rs/toolchain@v1 @@ -34,4 +35,4 @@ jobs: cache-on-failure: true - name: Run Ethereum tests - run: cargo run --profile ethtests -p revme -- statetest ethtests/GeneralStateTests + run: cargo run --profile ethtests -p revme -- statetest ethtests/GeneralStateTests ethtests/LegacyTests/Constantinople/GeneralStateTests/ diff --git a/Cargo.lock b/Cargo.lock index 3d1ab7055f..a96898063c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1829,6 +1829,7 @@ name = "revm_precompiles" version = "1.1.0" dependencies = [ "bytes", + "hashbrown", "hex", "k256", "num", diff --git a/README.md b/README.md index ddf829a4ca..4793f67785 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,6 @@ run tests with command: `cargo run --release -- statetest tests/GeneralStateTest There is public telegram group: https://t.me/+Ig4WDWOzikA3MzA0 -Or you can contact me directly on email: dragan0rakita@gmail.com +Or if you want to hire me or contact me directly, here is my email: dragan0rakita@gmail.com and telegram: https://t.me/draganrakita diff --git a/bins/revme/src/statetest/cmd.rs b/bins/revme/src/statetest/cmd.rs index 30939bd8c0..18802f6d3d 100644 --- a/bins/revme/src/statetest/cmd.rs +++ b/bins/revme/src/statetest/cmd.rs @@ -6,13 +6,16 @@ use structopt::StructOpt; #[derive(StructOpt, Debug)] pub struct Cmd { #[structopt(required = true)] - path: PathBuf, + path: Vec, } impl Cmd { pub fn run(&self) -> Result<(), TestError> { - let test_files = find_all_json_tests(&self.path); - println!("Start running tests on: {:?}", self.path); - run(test_files) + for path in &self.path { + println!("Start running tests on: {:?}", path); + let test_files = find_all_json_tests(path); + run(test_files)? + } + Ok(()) } } diff --git a/bins/revme/src/statetest/models/deserializer.rs b/bins/revme/src/statetest/models/deserializer.rs index 38a4068651..b83a02511e 100644 --- a/bins/revme/src/statetest/models/deserializer.rs +++ b/bins/revme/src/statetest/models/deserializer.rs @@ -75,3 +75,14 @@ where .map_err(D::Error::custom)? .into()) } + +pub fn deserialize_opt_str_as_bytes<'de, D>(deserializer: D) -> Result, D::Error> +where + D: de::Deserializer<'de>, +{ + #[derive(Debug, Deserialize)] + struct WrappedValue(#[serde(deserialize_with = "deserialize_str_as_bytes")] Bytes); + + Option::::deserialize(deserializer) + .map(|opt_wrapped: Option| opt_wrapped.map(|wrapped: WrappedValue| wrapped.0)) +} diff --git a/bins/revme/src/statetest/models/mod.rs b/bins/revme/src/statetest/models/mod.rs index f3a0c990c2..2b44caac1d 100644 --- a/bins/revme/src/statetest/models/mod.rs +++ b/bins/revme/src/statetest/models/mod.rs @@ -30,8 +30,9 @@ pub struct Test { pub indexes: TxPartIndices, // logs pub logs: H256, - #[serde(deserialize_with = "deserialize_str_as_bytes")] - pub txbytes: Bytes, + #[serde(default)] + #[serde(deserialize_with = "deserialize_opt_str_as_bytes")] + pub txbytes: Option, } #[derive(Debug, PartialEq, Eq, Deserialize)] diff --git a/bins/revme/src/statetest/models/spec.rs b/bins/revme/src/statetest/models/spec.rs index e5a8bd4663..3e7f93d1d8 100644 --- a/bins/revme/src/statetest/models/spec.rs +++ b/bins/revme/src/statetest/models/spec.rs @@ -3,34 +3,44 @@ use serde_derive::*; #[derive(Debug, PartialEq, Eq, PartialOrd, Hash, Ord, Deserialize)] pub enum SpecName { - EIP150, - EIP158, Frontier, - Homestead, - Byzantium, - Constantinople, - ConstantinopleFix, - Istanbul, - EIP158ToByzantiumAt5, FrontierToHomesteadAt5, + Homestead, HomesteadToDaoAt5, HomesteadToEIP150At5, - ByzantiumToConstantinopleAt5, + EIP150, + EIP158, // EIP-161: State trie clearing + EIP158ToByzantiumAt5, + Byzantium, // done + ByzantiumToConstantinopleAt5, // SKIPPED ByzantiumToConstantinopleFixAt5, - Berlin, - London, - BerlinToLondonAt5, - Merge, + Constantinople, // SKIPPED + ConstantinopleFix, + Istanbul, + Berlin, //done + BerlinToLondonAt5, // done + London, // done + Merge, //done } impl SpecName { pub fn to_spec_id(&self) -> SpecId { match self { - Self::Merge => SpecId::MERGE, - Self::London => SpecId::LONDON, - Self::Berlin => SpecId::BERLIN, + Self::Frontier => SpecId::FRONTIER, + Self::Homestead | Self::FrontierToHomesteadAt5 => SpecId::HOMESTEAD, + Self::EIP150 | Self::HomesteadToDaoAt5 | Self::HomesteadToEIP150At5 => { + SpecId::TANGERINE + } + Self::EIP158 => SpecId::SPURIOUS_DRAGON, + Self::Byzantium | Self::EIP158ToByzantiumAt5 => SpecId::BYZANTIUM, + Self::ConstantinopleFix | Self::ByzantiumToConstantinopleFixAt5 => SpecId::PETERSBURG, Self::Istanbul => SpecId::ISTANBUL, - _ => panic!("Conversion failed"), + Self::Berlin => SpecId::BERLIN, + Self::London | Self::BerlinToLondonAt5 => SpecId::LONDON, + Self::Merge => SpecId::MERGE, + Self::ByzantiumToConstantinopleAt5 | Self::Constantinople => { + panic!("Overriden with PETERSBURG") + } //_ => panic!("Conversion failed"), } } } diff --git a/bins/revme/src/statetest/runner.rs b/bins/revme/src/statetest/runner.rs index 2556b2523e..ee1ddf0001 100644 --- a/bins/revme/src/statetest/runner.rs +++ b/bins/revme/src/statetest/runner.rs @@ -35,6 +35,8 @@ pub enum TestError { SerdeDeserialize(#[from] serde_json::Error), #[error("Internal system error")] SystemError, + #[error("Unknown private key: {private_key:?}")] + UnknownPrivateKey { private_key: H256 }, } pub fn find_all_json_tests(path: &Path) -> Vec { @@ -112,6 +114,11 @@ pub fn execute_test_suit(path: &Path, elapsed: &Arc>) -> Result< .unwrap(), H160::from_str("0x3fb1cd2cd96c6d5c0b5eb3322d807b34482481d4").unwrap(), ), + ( + H256::from_str("0xfe13266ff57000135fb9aa854bbfe455d8da85b21f626307bf3263a0c2a8e7fe") + .unwrap(), + H160::from_str("0xdcc5ba93a1ed7e045690d722f2bf460a51c61415").unwrap(), + ), ] .into_iter() .collect(); @@ -129,7 +136,7 @@ pub fn execute_test_suit(path: &Path, elapsed: &Arc>) -> Result< database.insert_account_info(*address, acc_info); // insert storage: for (&slot, &value) in info.storage.iter() { - database.insert_account_storage(*address, slot, value) + let _ = database.insert_account_storage(*address, slot, value); } } let mut env = Env::default(); @@ -145,9 +152,13 @@ pub fn execute_test_suit(path: &Path, elapsed: &Arc>) -> Result< env.block.difficulty = unit.env.current_difficulty; //tx env - env.tx.caller = *map_caller_keys - .get(&unit.transaction.secret_key.unwrap()) - .unwrap(); + env.tx.caller = + if let Some(caller) = map_caller_keys.get(&unit.transaction.secret_key.unwrap()) { + *caller + } else { + let private_key = unit.transaction.secret_key.unwrap(); + return Err(TestError::UnknownPrivateKey { private_key }); + }; env.tx.gas_price = unit .transaction .gas_price @@ -156,9 +167,9 @@ pub fn execute_test_suit(path: &Path, elapsed: &Arc>) -> Result< // post and execution for (spec_name, tests) in unit.post { - if !matches!( + if matches!( spec_name, - SpecName::Merge | SpecName::London | SpecName::Berlin | SpecName::Istanbul + SpecName::ByzantiumToConstantinopleAt5 | SpecName::Constantinople ) { continue; } @@ -226,13 +237,16 @@ pub fn execute_test_suit(path: &Path, elapsed: &Arc>) -> Result< *elapsed.lock().unwrap() += timer; + let is_legacy = !SpecId::enabled(evm.env.cfg.spec_id, SpecId::SPURIOUS_DRAGON); let db = evm.db().unwrap(); let state_root = state_merkle_trie_root( db.accounts .iter() .filter(|(_address, acc)| { - !(acc.info.is_empty()) - || matches!(acc.account_state, AccountState::None) + (is_legacy && !matches!(acc.account_state, AccountState::NotExisting)) + || (!is_legacy + && (!(acc.info.is_empty()) + || matches!(acc.account_state, AccountState::None))) }) .map(|(k, v)| (*k, v.clone())), ); @@ -269,7 +283,7 @@ pub fn execute_test_suit(path: &Path, elapsed: &Arc>) -> Result< pub fn run(test_files: Vec) -> Result<(), TestError> { let endjob = Arc::new(AtomicBool::new(false)); let console_bar = Arc::new(ProgressBar::new(test_files.len() as u64)); - let mut joins = Vec::new(); + let mut joins: Vec>> = Vec::new(); let queue = Arc::new(Mutex::new((0, test_files))); let elapsed = Arc::new(Mutex::new(std::time::Duration::ZERO)); for _ in 0..10 { diff --git a/bins/revme/src/statetest/trace.rs b/bins/revme/src/statetest/trace.rs index 2a51caf721..ca77c48923 100644 --- a/bins/revme/src/statetest/trace.rs +++ b/bins/revme/src/statetest/trace.rs @@ -58,7 +58,7 @@ impl Inspector for CustomPrintTracer { let gas_remaining = interp.gas.remaining() + self.full_gas_block - self.reduced_gas_block; println!( - "depth:{}, PC:{}, gas:{:#x}({}), OPCODE: {:?}({:?}) refund:{:#x}({}) Stack:{:?}, Data:", + "depth:{}, PC:{}, gas:{:#x}({}), OPCODE: {:?}({:?}) refund:{:#x}({}) Stack:{:?}, Data size:{}", data.journaled_state.depth(), interp.program_counter(), gas_remaining, @@ -68,7 +68,7 @@ impl Inspector for CustomPrintTracer { interp.gas.refunded(), interp.gas.refunded(), interp.stack.data(), - //hex::encode(interp.memory.data()), + interp.memory.data().len(), ); let pc = interp.program_counter(); @@ -141,12 +141,12 @@ impl Inspector for CustomPrintTracer { is_static: bool, ) -> (Return, Gas, Bytes) { println!( - "SM CALL: {:?},context:{:?}, is_static:{:?}, transfer:{:?}, input:{:?}", + "SM CALL: {:?},context:{:?}, is_static:{:?}, transfer:{:?}, input_size:{:?}", inputs.contract, inputs.context, is_static, inputs.transfer, - hex::encode(&inputs.input), + inputs.input.len(), ); (Return::Continue, Gas::new(0), Bytes::new()) } diff --git a/crates/revm/src/db.rs b/crates/revm/src/db.rs index a1779f1d90..733729498a 100644 --- a/crates/revm/src/db.rs +++ b/crates/revm/src/db.rs @@ -16,15 +16,16 @@ use auto_impl::auto_impl; #[auto_impl(& mut, Box)] pub trait Database { + type Error; /// Get basic account information. - fn basic(&mut self, address: H160) -> AccountInfo; + fn basic(&mut self, address: H160) -> Result, Self::Error>; /// Get account code by its hash - fn code_by_hash(&mut self, code_hash: H256) -> Bytecode; + fn code_by_hash(&mut self, code_hash: H256) -> Result; /// Get storage value of address at index. - fn storage(&mut self, address: H160, index: U256) -> U256; + fn storage(&mut self, address: H160, index: U256) -> Result; // History related - fn block_hash(&mut self, number: U256) -> H256; + fn block_hash(&mut self, number: U256) -> Result; } #[auto_impl(& mut, Box)] @@ -34,49 +35,47 @@ pub trait DatabaseCommit { #[auto_impl(&, Box)] pub trait DatabaseRef { + type Error; /// Whether account at address exists. //fn exists(&self, address: H160) -> Option; /// Get basic account information. - fn basic(&self, address: H160) -> AccountInfo; + fn basic(&self, address: H160) -> Result, Self::Error>; /// Get account code by its hash - fn code_by_hash(&self, code_hash: H256) -> Bytecode; + fn code_by_hash(&self, code_hash: H256) -> Result; /// Get storage value of address at index. - fn storage(&self, address: H160, index: U256) -> U256; + fn storage(&self, address: H160, index: U256) -> Result; // History related - fn block_hash(&self, number: U256) -> H256; + fn block_hash(&self, number: U256) -> Result; } -pub struct RefDBWrapper<'a> { - pub db: &'a dyn DatabaseRef, +pub struct RefDBWrapper<'a, Error> { + pub db: &'a dyn DatabaseRef, } -impl<'a> RefDBWrapper<'a> { - pub fn new(db: &'a dyn DatabaseRef) -> Self { +impl<'a, Error> RefDBWrapper<'a, Error> { + pub fn new(db: &'a dyn DatabaseRef) -> Self { Self { db } } } -impl<'a> Database for RefDBWrapper<'a> { - /// Whether account at address exists. - // fn exists(&mut self, address: H160) -> Option { - // self.db.exists(address) - // } +impl<'a, Error> Database for RefDBWrapper<'a, Error> { + type Error = Error; /// Get basic account information. - fn basic(&mut self, address: H160) -> AccountInfo { + fn basic(&mut self, address: H160) -> Result, Self::Error> { self.db.basic(address) } /// Get account code by its hash - fn code_by_hash(&mut self, code_hash: H256) -> Bytecode { + fn code_by_hash(&mut self, code_hash: H256) -> Result { self.db.code_by_hash(code_hash) } /// Get storage value of address at index. - fn storage(&mut self, address: H160, index: U256) -> U256 { + fn storage(&mut self, address: H160, index: U256) -> Result { self.db.storage(address, index) } // History related - fn block_hash(&mut self, number: U256) -> H256 { + fn block_hash(&mut self, number: U256) -> Result { self.db.block_hash(number) } } diff --git a/crates/revm/src/db/in_memory_db.rs b/crates/revm/src/db/in_memory_db.rs index a322808de0..159121f8d9 100644 --- a/crates/revm/src/db/in_memory_db.rs +++ b/crates/revm/src/db/in_memory_db.rs @@ -1,10 +1,7 @@ use super::{DatabaseCommit, DatabaseRef}; use crate::{interpreter::bytecode::Bytecode, Database, KECCAK_EMPTY}; use crate::{Account, AccountInfo, Log}; -use alloc::{ - collections::btree_map::{self, BTreeMap}, - vec::Vec, -}; +use alloc::vec::Vec; use hashbrown::{hash_map::Entry, HashMap as Map}; use primitive_types::{H160, H256, U256}; use sha3::{Digest, Keccak256}; @@ -20,9 +17,9 @@ impl InMemoryDB { /// Memory backend, storing all state values in a `Map` in memory. #[derive(Debug, Clone)] pub struct CacheDB { - /// Dummy account info where `code` is always `None`. - /// Code bytes can be found in `contracts`. - pub accounts: BTreeMap, + /// Account info where None means it is not existing. Not existing state is needed for Pre TANGERINE forks. + /// `code` is always `None`, and bytecode can be found in `contracts`. + pub accounts: Map, pub contracts: Map, pub logs: Vec, pub block_hashes: Map, @@ -35,15 +32,59 @@ pub struct DbAccount { /// If account is selfdestructed or newly created, storage will be cleared. pub account_state: AccountState, /// storage slots - pub storage: BTreeMap, + pub storage: Map, +} + +impl DbAccount { + pub fn new_not_existing() -> Self { + Self { + account_state: AccountState::NotExisting, + ..Default::default() + } + } + pub fn info(&self) -> Option { + if matches!(self.account_state, AccountState::NotExisting) { + None + } else { + Some(self.info.clone()) + } + } +} + +impl From> for DbAccount { + fn from(from: Option) -> Self { + if let Some(info) = from { + Self { + info, + account_state: AccountState::None, + ..Default::default() + } + } else { + Self::new_not_existing() + } + } +} + +impl From for DbAccount { + fn from(info: AccountInfo) -> Self { + Self { + info, + account_state: AccountState::None, + ..Default::default() + } + } } #[derive(Debug, Clone, Default)] pub enum AccountState { - /// EVM touched this account - EVMTouched, - /// EVM cleared storage of this account, mostly by selfdestruct - EVMStorageCleared, + /// Before Spurious Dragon hardfork there were a difference between empty and not existing. + /// And we are flaging it here. + NotExisting, + /// EVM touched this account. For newer hardfork this means it can be clearead/removed from state. + Touched, + /// EVM cleared storage of this account, mostly by selfdestruct, we dont ask database for storage slots + /// and asume they are U256::zero() + StorageCleared, /// EVM didnt interacted with this account #[default] None, @@ -55,7 +96,7 @@ impl CacheDB { contracts.insert(KECCAK_EMPTY, Bytecode::new()); contracts.insert(H256::zero(), Bytecode::new()); Self { - accounts: BTreeMap::new(), + accounts: Map::new(), contracts, logs: Vec::default(), block_hashes: Map::new(), @@ -83,28 +124,43 @@ impl CacheDB { self.accounts.entry(address).or_default().info = info; } - /// insert account storage without overriding account info - pub fn insert_account_storage(&mut self, address: H160, slot: U256, value: U256) { + fn load_account(&mut self, address: H160) -> Result<&mut DbAccount, ExtDB::Error> { let db = &self.db; - self.accounts - .entry(address) - .or_insert_with(|| DbAccount { - info: db.basic(address), - ..Default::default() - }) - .storage - .insert(slot, value); + match self.accounts.entry(address) { + Entry::Occupied(entry) => Ok(entry.into_mut()), + Entry::Vacant(entry) => Ok(entry.insert( + db.basic(address)? + .map(|info| DbAccount { + info, + ..Default::default() + }) + .unwrap_or_else(DbAccount::new_not_existing), + )), + } + } + + /// insert account storage without overriding account info + pub fn insert_account_storage( + &mut self, + address: H160, + slot: U256, + value: U256, + ) -> Result<(), ExtDB::Error> { + let account = self.load_account(address)?; + account.storage.insert(slot, value); + Ok(()) } /// replace account storage without overriding account info - pub fn replace_account_storage(&mut self, address: H160, storage: Map) { - let db = &self.db; - let mut account = self.accounts.entry(address).or_insert_with(|| DbAccount { - info: db.basic(address), - ..Default::default() - }); - account.account_state = AccountState::EVMStorageCleared; + pub fn replace_account_storage( + &mut self, + address: H160, + storage: Map, + ) -> Result<(), ExtDB::Error> { + let account = self.load_account(address)?; + account.account_state = AccountState::StorageCleared; account.storage = storage.into_iter().collect(); + Ok(()) } } @@ -114,7 +170,7 @@ impl DatabaseCommit for CacheDB { if account.is_destroyed { let db_account = self.accounts.entry(address).or_default(); db_account.storage.clear(); - db_account.account_state = AccountState::EVMStorageCleared; + db_account.account_state = AccountState::NotExisting; db_account.info = AccountInfo::default(); continue; } @@ -125,9 +181,9 @@ impl DatabaseCommit for CacheDB { db_account.account_state = if account.storage_cleared { db_account.storage.clear(); - AccountState::EVMStorageCleared + AccountState::StorageCleared } else { - AccountState::EVMTouched + AccountState::Touched }; db_account.storage.extend( account @@ -140,99 +196,106 @@ impl DatabaseCommit for CacheDB { } impl Database for CacheDB { - fn block_hash(&mut self, number: U256) -> H256 { + type Error = ExtDB::Error; + + fn block_hash(&mut self, number: U256) -> Result { match self.block_hashes.entry(number) { - Entry::Occupied(entry) => *entry.get(), + Entry::Occupied(entry) => Ok(*entry.get()), Entry::Vacant(entry) => { - let hash = self.db.block_hash(number); + let hash = self.db.block_hash(number)?; entry.insert(hash); - hash + Ok(hash) } } } - fn basic(&mut self, address: H160) -> AccountInfo { - match self.accounts.entry(address) { - btree_map::Entry::Occupied(entry) => entry.get().info.clone(), - btree_map::Entry::Vacant(entry) => { - let info = self.db.basic(address); - entry.insert(DbAccount { - info: info.clone(), - account_state: AccountState::EVMTouched, - storage: BTreeMap::new(), - }); - info - } - } + fn basic(&mut self, address: H160) -> Result, Self::Error> { + let basic = match self.accounts.entry(address) { + Entry::Occupied(entry) => entry.into_mut(), + Entry::Vacant(entry) => entry.insert( + self.db + .basic(address)? + .map(|info| DbAccount { + info, + ..Default::default() + }) + .unwrap_or_else(DbAccount::new_not_existing), + ), + }; + Ok(basic.info()) } /// Get the value in an account's storage slot. /// /// It is assumed that account is already loaded. - fn storage(&mut self, address: H160, index: U256) -> U256 { + fn storage(&mut self, address: H160, index: U256) -> Result { match self.accounts.entry(address) { - btree_map::Entry::Occupied(mut acc_entry) => { + Entry::Occupied(mut acc_entry) => { let acc_entry = acc_entry.get_mut(); match acc_entry.storage.entry(index) { - btree_map::Entry::Occupied(entry) => *entry.get(), - btree_map::Entry::Vacant(entry) => { - if matches!(acc_entry.account_state, AccountState::EVMStorageCleared) { - U256::zero() + Entry::Occupied(entry) => Ok(*entry.get()), + Entry::Vacant(entry) => { + if matches!( + acc_entry.account_state, + AccountState::StorageCleared | AccountState::NotExisting + ) { + Ok(U256::zero()) } else { - let slot = self.db.storage(address, index); + let slot = self.db.storage(address, index)?; entry.insert(slot); - slot + Ok(slot) } } } } - btree_map::Entry::Vacant(acc_entry) => { + Entry::Vacant(acc_entry) => { // acc needs to be loaded for us to access slots. - let info = self.db.basic(address); - let value = self.db.storage(address, index); - acc_entry.insert(DbAccount { - info, - account_state: AccountState::None, - storage: BTreeMap::from([(index, value)]), - }); - value + let info = self.db.basic(address)?; + let (account, value) = if info.is_some() { + let value = self.db.storage(address, index)?; + let mut account: DbAccount = info.into(); + account.storage.insert(index, value); + (account, value) + } else { + (info.into(), U256::zero()) + }; + acc_entry.insert(account); + Ok(value) } } } - fn code_by_hash(&mut self, code_hash: H256) -> Bytecode { + fn code_by_hash(&mut self, code_hash: H256) -> Result { match self.contracts.entry(code_hash) { - Entry::Occupied(entry) => entry.get().clone(), + Entry::Occupied(entry) => Ok(entry.get().clone()), Entry::Vacant(entry) => { // if you return code bytes when basic fn is called this function is not needed. - entry.insert(self.db.code_by_hash(code_hash)).clone() + Ok(entry.insert(self.db.code_by_hash(code_hash)?).clone()) } } } } impl DatabaseRef for CacheDB { - fn block_hash(&self, number: U256) -> H256 { - match self.block_hashes.get(&number) { - Some(entry) => *entry, - None => self.db.block_hash(number), - } - } + type Error = ExtDB::Error; - fn basic(&self, address: H160) -> AccountInfo { + fn basic(&self, address: H160) -> Result, Self::Error> { match self.accounts.get(&address) { - Some(acc) => acc.info.clone(), + Some(acc) => Ok(Some(acc.info.clone())), None => self.db.basic(address), } } - fn storage(&self, address: H160, index: U256) -> U256 { + fn storage(&self, address: H160, index: U256) -> Result { match self.accounts.get(&address) { Some(acc_entry) => match acc_entry.storage.get(&index) { - Some(entry) => *entry, + Some(entry) => Ok(*entry), None => { - if matches!(acc_entry.account_state, AccountState::EVMStorageCleared) { - U256::zero() + if matches!( + acc_entry.account_state, + AccountState::StorageCleared | AccountState::NotExisting + ) { + Ok(U256::zero()) } else { self.db.storage(address, index) } @@ -242,12 +305,19 @@ impl DatabaseRef for CacheDB { } } - fn code_by_hash(&self, code_hash: H256) -> Bytecode { + fn code_by_hash(&self, code_hash: H256) -> Result { match self.contracts.get(&code_hash) { - Some(entry) => entry.clone(), + Some(entry) => Ok(entry.clone()), None => self.db.code_by_hash(code_hash), } } + + fn block_hash(&self, number: U256) -> Result { + match self.block_hashes.get(&number) { + Some(entry) => Ok(*entry), + None => self.db.block_hash(number), + } + } } /// An empty database that always returns default values when queried. @@ -255,24 +325,25 @@ impl DatabaseRef for CacheDB { pub struct EmptyDB(); impl DatabaseRef for EmptyDB { + type Error = (); /// Get basic account information. - fn basic(&self, _address: H160) -> AccountInfo { - AccountInfo::default() + fn basic(&self, _address: H160) -> Result, Self::Error> { + Ok(None) } /// Get account code by its hash - fn code_by_hash(&self, _code_hash: H256) -> Bytecode { - Bytecode::new() + fn code_by_hash(&self, _code_hash: H256) -> Result { + Ok(Bytecode::new()) } /// Get storage value of address at index. - fn storage(&self, _address: H160, _index: U256) -> U256 { - U256::default() + fn storage(&self, _address: H160, _index: U256) -> Result { + Ok(U256::default()) } // History related - fn block_hash(&self, number: U256) -> H256 { + fn block_hash(&self, number: U256) -> Result { let mut buffer: [u8; 4 * 8] = [0; 4 * 8]; number.to_big_endian(&mut buffer); - H256::from_slice(&Keccak256::digest(&buffer)) + Ok(H256::from_slice(&Keccak256::digest(&buffer))) } } @@ -290,32 +361,33 @@ impl BenchmarkDB { } impl Database for BenchmarkDB { + type Error = (); /// Get basic account information. - fn basic(&mut self, address: H160) -> AccountInfo { + fn basic(&mut self, address: H160) -> Result, Self::Error> { if address == H160::zero() { - return AccountInfo { + return Ok(Some(AccountInfo { nonce: 1, balance: U256::from(10000000), code: Some(self.0.clone()), code_hash: self.1, - }; + })); } - AccountInfo::default() + Ok(None) } /// Get account code by its hash - fn code_by_hash(&mut self, _code_hash: H256) -> Bytecode { - Bytecode::default() + fn code_by_hash(&mut self, _code_hash: H256) -> Result { + Ok(Bytecode::default()) } /// Get storage value of address at index. - fn storage(&mut self, _address: H160, _index: U256) -> U256 { - U256::default() + fn storage(&mut self, _address: H160, _index: U256) -> Result { + Ok(U256::default()) } // History related - fn block_hash(&mut self, _number: U256) -> H256 { - H256::default() + fn block_hash(&mut self, _number: U256) -> Result { + Ok(H256::default()) } } @@ -342,10 +414,10 @@ mod tests { let (key, value) = (123u64.into(), 456u64.into()); let mut new_state = CacheDB::new(init_state); - new_state.insert_account_storage(account, key, value); + let _ = new_state.insert_account_storage(account, key, value); - assert_eq!(new_state.basic(account).nonce, nonce); - assert_eq!(new_state.storage(account, key), value); + assert_eq!(new_state.basic(account).unwrap().unwrap().nonce, nonce); + assert_eq!(new_state.storage(account, key), Ok(value)); } #[test] @@ -363,13 +435,13 @@ mod tests { let (key0, value0) = (123u64.into(), 456u64.into()); let (key1, value1) = (789u64.into(), 999u64.into()); - init_state.insert_account_storage(account, key0, value0); + let _ = init_state.insert_account_storage(account, key0, value0); let mut new_state = CacheDB::new(init_state); - new_state.replace_account_storage(account, [(key1, value1)].into()); + let _ = new_state.replace_account_storage(account, [(key1, value1)].into()); - assert_eq!(new_state.basic(account).nonce, nonce); - assert_eq!(new_state.storage(account, key0), 0.into()); - assert_eq!(new_state.storage(account, key1), value1); + assert_eq!(new_state.basic(account).unwrap().unwrap().nonce, nonce); + assert_eq!(new_state.storage(account, key0), Ok(0.into())); + assert_eq!(new_state.storage(account, key1), Ok(value1)); } } diff --git a/crates/revm/src/db/web3db.rs b/crates/revm/src/db/web3db.rs index e0a7d237bd..318bc0f703 100644 --- a/crates/revm/src/db/web3db.rs +++ b/crates/revm/src/db/web3db.rs @@ -48,7 +48,9 @@ impl Web3DB { } impl Database for Web3DB { - fn basic(&mut self, address: H160) -> AccountInfo { + type Error = (); + + fn basic(&mut self, address: H160) -> Result, Self::Error> { let add = wH160(address.0); let f = async { let nonce = self.web3.eth().transaction_count(add, self.block_number); @@ -58,7 +60,7 @@ impl Database for Web3DB { }; let (nonce, balance, code) = self.block_on(f); // panic on not getting data? - AccountInfo::new( + Ok(Some(AccountInfo::new( U256( balance .unwrap_or_else(|e| panic!("web3 get balance error:{:?}", e)) @@ -71,10 +73,10 @@ impl Database for Web3DB { code.unwrap_or_else(|e| panic!("web3 get node error:{:?}", e)) .0, )), - ) + ))) } - fn code_by_hash(&mut self, _code_hash: primitive_types::H256) -> Bytecode { + fn code_by_hash(&mut self, _code_hash: primitive_types::H256) -> Result { panic!("Should not be called. Code is already loaded"); // not needed because we already load code with basic info } @@ -83,7 +85,7 @@ impl Database for Web3DB { &mut self, address: primitive_types::H160, index: primitive_types::U256, - ) -> primitive_types::U256 { + ) -> Result { let add = wH160(address.0); let index = wU256(index.0); let f = async { @@ -95,17 +97,20 @@ impl Database for Web3DB { .unwrap(); U256::from_big_endian(storage.as_bytes()) }; - self.block_on(f) + Ok(self.block_on(f)) } - fn block_hash(&mut self, number: primitive_types::U256) -> primitive_types::H256 { + fn block_hash( + &mut self, + number: primitive_types::U256, + ) -> Result { if number > U256::from(u64::MAX) { - return KECCAK_EMPTY; + return Ok(KECCAK_EMPTY); } let number = number.as_u64(); if let Some(block_num) = self.block_number { match block_num { - BlockNumber::Number(t) if t.as_u64() > number => return KECCAK_EMPTY, + BlockNumber::Number(t) if t.as_u64() > number => return Ok(KECCAK_EMPTY), _ => (), } } @@ -118,6 +123,6 @@ impl Database for Web3DB { .ok() .flatten() }; - H256(self.block_on(f).unwrap().hash.unwrap().0) + Ok(H256(self.block_on(f).unwrap().hash.unwrap().0)) } } diff --git a/crates/revm/src/evm.rs b/crates/revm/src/evm.rs index 9c4da27019..2ec09c78f4 100644 --- a/crates/revm/src/evm.rs +++ b/crates/revm/src/evm.rs @@ -2,8 +2,7 @@ use crate::{ db::{Database, DatabaseCommit, DatabaseRef, RefDBWrapper}, evm_impl::{EVMImpl, Transact}, journaled_state::State, - BerlinSpec, ByzantiumSpec, Env, ExecutionResult, Inspector, IstanbulSpec, LatestSpec, - LondonSpec, MergeSpec, NoOpInspector, Spec, SpecId, + specification, Env, ExecutionResult, Inspector, NoOpInspector, }; use alloc::boxed::Box; use revm_precompiles::Precompiles; @@ -89,7 +88,8 @@ impl<'a, DB: DatabaseRef> EVM { let mut db = RefDBWrapper::new(db); let db = &mut db; let out = - evm_inner::(&mut self.env.clone(), db, &mut noop).transact(); + evm_inner::, false>(&mut self.env.clone(), db, &mut noop) + .transact(); out } else { panic!("Database needs to be set"); @@ -97,15 +97,19 @@ impl<'a, DB: DatabaseRef> EVM { } /// Execute transaction with given inspector, without wring to DB. Return change state. - pub fn inspect_ref>>( + pub fn inspect_ref>>( &'a self, mut inspector: INSP, ) -> (ExecutionResult, State) { if let Some(db) = self.db.as_ref() { let mut db = RefDBWrapper::new(db); let db = &mut db; - let out = evm_inner::(&mut self.env.clone(), db, &mut inspector) - .transact(); + let out = evm_inner::, true>( + &mut self.env.clone(), + db, + &mut inspector, + ) + .transact(); out } else { panic!("Database needs to be set"); @@ -150,13 +154,20 @@ pub fn evm_inner<'a, DB: Database, const INSPECT: bool>( db: &'a mut DB, insp: &'a mut dyn Inspector, ) -> Box { + use specification::*; match env.cfg.spec_id { - SpecId::LATEST => create_evm!(LatestSpec, db, env, insp), - SpecId::MERGE => create_evm!(MergeSpec, db, env, insp), - SpecId::LONDON => create_evm!(LondonSpec, db, env, insp), - SpecId::BERLIN => create_evm!(BerlinSpec, db, env, insp), - SpecId::ISTANBUL => create_evm!(IstanbulSpec, db, env, insp), + SpecId::FRONTIER | SpecId::FRONTIER_THAWING => create_evm!(FrontierSpec, db, env, insp), + SpecId::HOMESTEAD | SpecId::DAO_FORK => create_evm!(HomesteadSpec, db, env, insp), + SpecId::TANGERINE => create_evm!(TangerineSpec, db, env, insp), + SpecId::SPURIOUS_DRAGON => create_evm!(SpuriousDragonSpec, db, env, insp), SpecId::BYZANTIUM => create_evm!(ByzantiumSpec, db, env, insp), - _ => panic!("Spec Not supported"), + SpecId::PETERSBURG | SpecId::CONSTANTINOPLE => create_evm!(PetersburgSpec, db, env, insp), + SpecId::ISTANBUL | SpecId::MUIR_GLACIER => create_evm!(IstanbulSpec, db, env, insp), + SpecId::BERLIN => create_evm!(BerlinSpec, db, env, insp), + SpecId::LONDON | SpecId::ARROW_GLACIER | SpecId::GRAY_GLACIER => { + create_evm!(LondonSpec, db, env, insp) + } + SpecId::MERGE => create_evm!(MergeSpec, db, env, insp), + SpecId::LATEST => create_evm!(LatestSpec, db, env, insp), } } diff --git a/crates/revm/src/evm_impl.rs b/crates/revm/src/evm_impl.rs index bb4e5a2d11..583e4b00ab 100644 --- a/crates/revm/src/evm_impl.rs +++ b/crates/revm/src/evm_impl.rs @@ -5,9 +5,9 @@ use crate::{ interpreter::{Contract, Interpreter}, journaled_state::{Account, JournaledState, State}, models::SelfDestructResult, - return_ok, CallContext, CallInputs, CallScheme, CreateInputs, CreateScheme, Env, + return_ok, return_revert, CallContext, CallInputs, CallScheme, CreateInputs, CreateScheme, Env, ExecutionResult, Gas, Inspector, Log, Return, Spec, - SpecId::*, + SpecId::{self, *}, TransactOut, TransactTo, Transfer, KECCAK_EMPTY, }; use alloc::vec::Vec; @@ -18,10 +18,11 @@ use primitive_types::{H160, H256, U256}; use revm_precompiles::{Precompile, PrecompileOutput, Precompiles}; use sha3::{Digest, Keccak256}; -pub struct EVMData<'a, DB> { +pub struct EVMData<'a, DB: Database> { pub env: &'a mut Env, pub journaled_state: JournaledState, pub db: &'a mut DB, + pub error: Option, } pub struct EVMImpl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> { @@ -77,7 +78,14 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> Transact } // load acc - self.inner_load_account(caller); + if self + .data + .journaled_state + .load_account(caller, self.data.db) + .is_err() + { + return exit(Return::FatalExternalError); + } // EIP-3607: Reject transactions from senders with deployed code // This EIP is introduced after london but there was no colision in past @@ -160,7 +168,16 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> Transact }; if crate::USE_GAS { - gas.reimburse_unspend(&exit_reason, ret_gas); + match exit_reason { + return_ok!() => { + gas.erase_cost(ret_gas.remaining()); + gas.record_refund(ret_gas.refunded()); + } + return_revert!() => { + gas.erase_cost(ret_gas.remaining()); + } + _ => {} + } } let (state, logs, gas_used, gas_refunded) = self.finalize::(caller, &gas); @@ -184,26 +201,17 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> EVMImpl<'a, GSPEC, DB, inspector: &'a mut dyn Inspector, precompiles: Precompiles, ) -> Self { - let mut journaled_state = JournaledState::default(); - if env.cfg.perf_all_precompiles_have_balance { - // load precompiles without asking db. - let mut precompile_acc = Vec::new(); - for add in precompiles.addresses() { - precompile_acc.push(*add); - } - journaled_state.load_precompiles_default(&precompile_acc); + let journaled_state = if GSPEC::enabled(SpecId::SPURIOUS_DRAGON) { + JournaledState::new(precompiles.len()) } else { - let mut precompile_acc = Map::new(); - for add in precompiles.addresses() { - precompile_acc.insert(*add, db.basic(*add)); - } - journaled_state.load_precompiles(precompile_acc); - } + JournaledState::new_legacy(precompiles.len()) + }; Self { data: EVMData { env, journaled_state, db, + error: None, }, precompiles, inspector, @@ -236,7 +244,9 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> EVMImpl<'a, GSPEC, DB, effective_gas_price }; - self.data + // TODO + let _ = self + .data .journaled_state .load_account(coinbase, self.data.db); self.data.journaled_state.touch(&coinbase); @@ -253,7 +263,9 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> EVMImpl<'a, GSPEC, DB, (gas.spend() - gas_refunded, gas_refunded) } else { // touch coinbase - self.data + // TODO return + let _ = self + .data .journaled_state .load_account(coinbase, self.data.db); self.data.journaled_state.touch(&coinbase); @@ -267,7 +279,14 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> EVMImpl<'a, GSPEC, DB, for address in self.precompiles.addresses() { if let Some(precompile) = new_state.get_mut(address) { // we found it. - precompile.info.balance += self.data.db.basic(*address).balance; + precompile.info.balance += self + .data + .db + .basic(*address) + .ok() + .flatten() + .map(|acc| acc.balance) + .unwrap_or_default(); } } } @@ -275,10 +294,6 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> EVMImpl<'a, GSPEC, DB, (new_state, logs, gas_used, gas_refunded) } - fn inner_load_account(&mut self, caller: H160) -> bool { - self.data.journaled_state.load_account(caller, self.data.db) - } - fn initialization(&mut self) -> u64 { let is_create = matches!(self.data.env.tx.transact_to, TransactTo::Create(_)); let input = &self.data.env.tx.data; @@ -291,12 +306,16 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> EVMImpl<'a, GSPEC, DB, let mut accessed_slots = 0_u64; for (address, slots) in self.data.env.tx.access_list.iter() { - self.data + // TODO return + let _ = self + .data .journaled_state .load_account(*address, self.data.db); accessed_slots += slots.len() as u64; + // TODO return for slot in slots { - self.data + let _ = self + .data .journaled_state .sload(*address, *slot, self.data.db); } @@ -353,8 +372,10 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> EVMImpl<'a, GSPEC, DB, return (Return::CallTooDeep, None, gas, Bytes::new()); } // Check balance of caller and value. Do this before increasing nonce - if self.balance(inputs.caller).0 < inputs.value { - return (Return::OutOfFund, None, gas, Bytes::new()); + match self.balance(inputs.caller) { + Some(i) if i.0 < inputs.value => return (Return::OutOfFund, None, gas, Bytes::new()), + Some(_) => (), + _ => return (Return::FatalExternalError, None, gas, Bytes::new()), } // Increase nonce of caller and check if it overflows @@ -380,13 +401,20 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> EVMImpl<'a, GSPEC, DB, let checkpoint = self.data.journaled_state.checkpoint(); // Create contract account and check for collision - if !self.data.journaled_state.create_account( + match self.data.journaled_state.create_account( created_address, self.precompiles.contains(&created_address), self.data.db, ) { - self.data.journaled_state.checkpoint_revert(checkpoint); - return (Return::CreateCollision, ret, gas, Bytes::new()); + Ok(false) => { + self.data.journaled_state.checkpoint_revert(checkpoint); + return (Return::CreateCollision, ret, gas, Bytes::new()); + } + Err(err) => { + self.data.error = Some(err); + return (Return::FatalExternalError, ret, gas, Bytes::new()); + } + Ok(true) => (), } // Transfer value to contract address @@ -400,8 +428,8 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> EVMImpl<'a, GSPEC, DB, return (e, ret, gas, Bytes::new()); } - // Increase nonce of the contract - if SPEC::enabled(ISTANBUL) + // EIP-161: State trie clearing (invariant-preserving alternative) + if SPEC::enabled(SPURIOUS_DRAGON) && self .data .journaled_state @@ -443,7 +471,7 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> EVMImpl<'a, GSPEC, DB, return_ok!() => { let b = Bytes::new(); // if ok, check contract creation limit and calculate gas deduction on output len. - let bytes = interp.return_value(); + let mut bytes = interp.return_value(); // EIP-3541: Reject new contract code starting with the 0xEF byte if SPEC::enabled(LONDON) && !bytes.is_empty() && bytes.first() == Some(&0xEF) { @@ -461,10 +489,17 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> EVMImpl<'a, GSPEC, DB, } if crate::USE_GAS { let gas_for_code = bytes.len() as u64 * crate::gas::CODEDEPOSIT; - // record code deposit gas cost and check if we are out of gas. if !interp.gas.record_cost(gas_for_code) { - self.data.journaled_state.checkpoint_revert(checkpoint); - return (Return::OutOfGas, ret, interp.gas, b); + // record code deposit gas cost and check if we are out of gas. + // EIP-2 point 3: If contract creation does not have enough gas to pay for the + // final gas fee for adding the contract code to the state, the contract + // creation fails (i.e. goes out-of-gas) rather than leaving an empty contract. + if SPEC::enabled(HOMESTEAD) { + self.data.journaled_state.checkpoint_revert(checkpoint); + return (Return::OutOfGas, ret, interp.gas, b); + } else { + bytes = Bytes::new(); + } } } // if we have enought gas @@ -510,7 +545,11 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> EVMImpl<'a, GSPEC, DB, let mut gas = Gas::new(inputs.gas_limit); // Load account and get code. Account is now hot. - let (bytecode, _) = self.code(inputs.contract); + let bytecode = if let Some((bytecode, _)) = self.code(inputs.contract) { + bytecode + } else { + return (Return::FatalExternalError, gas, Bytes::new()); + }; // Check depth if self.data.journaled_state.depth() > interpreter::CALL_STACK_LIMIT { @@ -647,55 +686,88 @@ impl<'a, GSPEC: Spec, DB: Database + 'a, const INSPECT: bool> Host self.data.env } - fn block_hash(&mut self, number: U256) -> H256 { - self.data.db.block_hash(number) + fn block_hash(&mut self, number: U256) -> Option { + self.data + .db + .block_hash(number) + .map_err(|e| self.data.error = Some(e)) + .ok() } - fn load_account(&mut self, address: H160) -> (bool, bool) { - let (is_cold, exists) = self - .data + fn load_account(&mut self, address: H160) -> Option<(bool, bool)> { + self.data .journaled_state - .load_account_exist(address, self.data.db); - (is_cold, exists) + .load_account_exist(address, self.data.db) + .map_err(|e| self.data.error = Some(e)) + .ok() } - fn balance(&mut self, address: H160) -> (U256, bool) { - let is_cold = self.inner_load_account(address); - let balance = self.data.journaled_state.account(address).info.balance; - (balance, is_cold) + fn balance(&mut self, address: H160) -> Option<(U256, bool)> { + let db = &mut self.data.db; + let journal = &mut self.data.journaled_state; + let error = &mut self.data.error; + journal + .load_account(address, db) + .map_err(|e| *error = Some(e)) + .ok() + .map(|(acc, is_cold)| (acc.info.balance, is_cold)) } - fn code(&mut self, address: H160) -> (Bytecode, bool) { - let (acc, is_cold) = self.data.journaled_state.load_code(address, self.data.db); - (acc.info.code.clone().unwrap(), is_cold) + fn code(&mut self, address: H160) -> Option<(Bytecode, bool)> { + let journal = &mut self.data.journaled_state; + let db = &mut self.data.db; + let error = &mut self.data.error; + + let (acc, is_cold) = journal + .load_code(address, db) + .map_err(|e| *error = Some(e)) + .ok()?; + Some((acc.info.code.clone().unwrap(), is_cold)) } /// Get code hash of address. - fn code_hash(&mut self, address: H160) -> (H256, bool) { - let (acc, is_cold) = self.data.journaled_state.load_code(address, self.data.db); + fn code_hash(&mut self, address: H160) -> Option<(H256, bool)> { + let journal = &mut self.data.journaled_state; + let db = &mut self.data.db; + let error = &mut self.data.error; + + let (acc, is_cold) = journal + .load_code(address, db) + .map_err(|e| *error = Some(e)) + .ok()?; //asume that all precompiles have some balance let is_precompile = self.precompiles.contains(&address); if is_precompile && self.data.env.cfg.perf_all_precompiles_have_balance { - return (KECCAK_EMPTY, is_cold); + return Some((KECCAK_EMPTY, is_cold)); } if acc.is_empty() { - return (H256::zero(), is_cold); + // TODO check this for pre tangerine fork + return Some((H256::zero(), is_cold)); } - (acc.info.code_hash, is_cold) + Some((acc.info.code_hash, is_cold)) } - fn sload(&mut self, address: H160, index: U256) -> (U256, bool) { + fn sload(&mut self, address: H160, index: U256) -> Option<(U256, bool)> { // account is always hot. reference on that statement https://eips.ethereum.org/EIPS/eip-2929 see `Note 2:` self.data .journaled_state .sload(address, index, self.data.db) + .map_err(|e| self.data.error = Some(e)) + .ok() } - fn sstore(&mut self, address: H160, index: U256, value: U256) -> (U256, U256, U256, bool) { + fn sstore( + &mut self, + address: H160, + index: U256, + value: U256, + ) -> Option<(U256, U256, U256, bool)> { self.data .journaled_state .sstore(address, index, value, self.data.db) + .map_err(|e| self.data.error = Some(e)) + .ok() } fn log(&mut self, address: H160, topics: Vec, data: Bytes) { @@ -710,13 +782,15 @@ impl<'a, GSPEC: Spec, DB: Database + 'a, const INSPECT: bool> Host self.data.journaled_state.log(log); } - fn selfdestruct(&mut self, address: H160, target: H160) -> SelfDestructResult { + fn selfdestruct(&mut self, address: H160, target: H160) -> Option { if INSPECT { self.inspector.selfdestruct(); } self.data .journaled_state .selfdestruct(address, target, self.data.db) + .map_err(|e| self.data.error = Some(e)) + .ok() } fn create( @@ -765,23 +839,28 @@ pub trait Host { fn env(&mut self) -> &mut Env; /// load account. Returns (is_cold,is_new_account) - fn load_account(&mut self, address: H160) -> (bool, bool); + fn load_account(&mut self, address: H160) -> Option<(bool, bool)>; /// Get environmental block hash. - fn block_hash(&mut self, number: U256) -> H256; + fn block_hash(&mut self, number: U256) -> Option; /// Get balance of address. - fn balance(&mut self, address: H160) -> (U256, bool); + fn balance(&mut self, address: H160) -> Option<(U256, bool)>; /// Get code of address. - fn code(&mut self, address: H160) -> (Bytecode, bool); + fn code(&mut self, address: H160) -> Option<(Bytecode, bool)>; /// Get code hash of address. - fn code_hash(&mut self, address: H160) -> (H256, bool); + fn code_hash(&mut self, address: H160) -> Option<(H256, bool)>; /// Get storage value of address at index. - fn sload(&mut self, address: H160, index: U256) -> (U256, bool); + fn sload(&mut self, address: H160, index: U256) -> Option<(U256, bool)>; /// Set storage value of address at index. Return if slot is cold/hot access. - fn sstore(&mut self, address: H160, index: U256, value: U256) -> (U256, U256, U256, bool); + fn sstore( + &mut self, + address: H160, + index: U256, + value: U256, + ) -> Option<(U256, U256, U256, bool)>; /// Create a log owned by address with given topics and data. fn log(&mut self, address: H160, topics: Vec, data: Bytes); /// Mark an address to be deleted, with funds transferred to target. - fn selfdestruct(&mut self, address: H160, target: H160) -> SelfDestructResult; + fn selfdestruct(&mut self, address: H160, target: H160) -> Option; /// Invoke a create operation. fn create( &mut self, diff --git a/crates/revm/src/gas.rs b/crates/revm/src/gas.rs index 8f272929fa..7bc0d716f2 100644 --- a/crates/revm/src/gas.rs +++ b/crates/revm/src/gas.rs @@ -3,9 +3,6 @@ mod constants; pub use calc::*; pub use constants::*; - -use crate::{instructions::Return, return_ok, return_revert}; - #[derive(Clone, Copy, Debug)] pub struct Gas { limit: u64, @@ -25,19 +22,6 @@ impl Gas { } } - pub fn reimburse_unspend(&mut self, exit: &Return, other: Gas) { - match *exit { - return_ok!() => { - self.erase_cost(other.remaining()); - self.record_refund(other.refunded()); - } - return_revert!() => { - self.erase_cost(other.remaining()); - } - _ => {} - } - } - pub fn limit(&self) -> u64 { self.limit } diff --git a/crates/revm/src/gas/calc.rs b/crates/revm/src/gas/calc.rs index 305dccfe7c..9955756cd4 100644 --- a/crates/revm/src/gas/calc.rs +++ b/crates/revm/src/gas/calc.rs @@ -7,7 +7,7 @@ pub fn sstore_refund(original: U256, current: U256, new: U256) -> i6 if SPEC::enabled(ISTANBUL) { // EIP-3529: Reduction in refunds let sstore_clears_schedule = if SPEC::enabled(LONDON) { - (SSTORE_RESET - SLOAD_COLD + ACCESS_LIST_STORAGE_KEY) as i64 + (SSTORE_RESET - COLD_SLOAD_COST + ACCESS_LIST_STORAGE_KEY) as i64 } else { REFUND_SSTORE_CLEARS }; @@ -29,7 +29,7 @@ pub fn sstore_refund(original: U256, current: U256, new: U256) -> i6 if original == new { let (gas_sstore_reset, gas_sload) = if SPEC::enabled(BERLIN) { - (SSTORE_RESET - SLOAD_COLD, STORAGE_READ_WARM) + (SSTORE_RESET - COLD_SLOAD_COST, WARM_STORAGE_READ_COST) } else { (SSTORE_RESET, sload_cost::(false)) }; @@ -123,17 +123,14 @@ pub fn verylowcopy_cost(len: U256) -> Option { pub fn extcodecopy_cost(len: U256, is_cold: bool) -> Option { let wordd = len / U256::from(32); let wordr = len % U256::from(32); - let base_gas: u64 = if SPEC::enabled(BERLIN) { - if is_cold { - ACCOUNT_ACCESS_COLD - } else { - STORAGE_READ_WARM - } - } else if SPEC::enabled(ISTANBUL) { - 700 + + let base_gas: u64 = if SPEC::enabled(BERLIN) && is_cold { + // WARM_STORAGE_READ_COST is already calculated + COLD_ACCOUNT_ACCESS_COST - WARM_STORAGE_READ_COST } else { - 20 + 0 }; + // TODO make this u64 friendly, U256 does not make sense. let gas = U256::from(base_gas).checked_add(U256::from(COPY).checked_mul(if wordr.is_zero() { wordd @@ -151,9 +148,9 @@ pub fn extcodecopy_cost(len: U256, is_cold: bool) -> Option { pub fn account_access_gas(is_cold: bool) -> u64 { if SPEC::enabled(BERLIN) { if is_cold { - ACCOUNT_ACCESS_COLD + COLD_ACCOUNT_ACCESS_COST } else { - STORAGE_READ_WARM + WARM_STORAGE_READ_COST } } else if SPEC::enabled(ISTANBUL) { 700 @@ -195,9 +192,9 @@ pub fn sha3_cost(len: U256) -> Option { pub fn sload_cost(is_cold: bool) -> u64 { if SPEC::enabled(BERLIN) { if is_cold { - SLOAD_COLD + COLD_SLOAD_COST } else { - STORAGE_READ_WARM + WARM_STORAGE_READ_COST } } else if SPEC::enabled(ISTANBUL) { // EIP-1884: Repricing for trie-size-dependent opcodes @@ -220,7 +217,7 @@ pub fn sstore_cost( ) -> Option { // TODO untangle this mess and make it more elegant let (gas_sload, gas_sstore_reset) = if SPEC::enabled(BERLIN) { - (STORAGE_READ_WARM, SSTORE_RESET - SLOAD_COLD) + (WARM_STORAGE_READ_COST, SSTORE_RESET - COLD_SLOAD_COST) } else { (sload_cost::(is_cold), SSTORE_RESET) }; @@ -256,26 +253,23 @@ pub fn sstore_cost( }; // In EIP-2929 we charge extra if the slot has not been used yet in this transaction if SPEC::enabled(BERLIN) && is_cold { - Some(gas_cost + SLOAD_COLD) + Some(gas_cost + COLD_SLOAD_COST) } else { Some(gas_cost) } } pub fn selfdestruct_cost(res: SelfDestructResult) -> u64 { - let should_charge_topup = if SPEC::enabled(ISTANBUL) { - res.had_value && !res.exists + // EIP-161: State trie clearing (invariant-preserving alternative) + let should_charge_topup = if SPEC::enabled(SPURIOUS_DRAGON) { + res.had_value && !res.target_exists } else { - !res.exists + !res.target_exists }; - let selfdestruct_gas_topup = if should_charge_topup { - if SPEC::enabled(TANGERINE) { - //EIP-150: Gas cost changes for IO-heavy operations - 25000 - } else { - 0 - } + let selfdestruct_gas_topup = if SPEC::enabled(TANGERINE) && should_charge_topup { + //EIP-150: Gas cost changes for IO-heavy operations + 25000 } else { 0 }; @@ -284,7 +278,7 @@ pub fn selfdestruct_cost(res: SelfDestructResult) -> u64 { let mut gas = selfdestruct_gas + selfdestruct_gas_topup; if SPEC::enabled(BERLIN) && res.is_cold { - gas += ACCOUNT_ACCESS_COLD + gas += COLD_ACCOUNT_ACCESS_COST } gas } @@ -300,9 +294,9 @@ pub fn call_cost( let call_gas = if SPEC::enabled(BERLIN) { if is_cold { - ACCOUNT_ACCESS_COLD + COLD_ACCOUNT_ACCESS_COST } else { - STORAGE_READ_WARM + WARM_STORAGE_READ_COST } } else if SPEC::enabled(TANGERINE) { // EIP-150: Gas cost changes for IO-heavy operations @@ -319,9 +313,9 @@ pub fn call_cost( pub fn hot_cold_cost(is_cold: bool, regular_value: u64) -> u64 { if SPEC::enabled(BERLIN) { if is_cold { - ACCOUNT_ACCESS_COLD + COLD_ACCOUNT_ACCESS_COST } else { - STORAGE_READ_WARM + WARM_STORAGE_READ_COST } } else { regular_value @@ -338,7 +332,8 @@ fn xfer_cost(is_call_or_callcode: bool, transfers_value: bool) -> u64 { fn new_cost(is_call_or_staticcall: bool, is_new: bool, transfers_value: bool) -> u64 { if is_call_or_staticcall { - if SPEC::enabled(ISTANBUL) { + // EIP-161: State trie clearing (invariant-preserving alternative) + if SPEC::enabled(SPURIOUS_DRAGON) { if transfers_value && is_new { NEWACCOUNT } else { diff --git a/crates/revm/src/gas/constants.rs b/crates/revm/src/gas/constants.rs index 04fcb2ee1e..b49b42e496 100644 --- a/crates/revm/src/gas/constants.rs +++ b/crates/revm/src/gas/constants.rs @@ -31,8 +31,8 @@ pub const TRANSACTION_NON_ZERO_DATA_FRONTIER: u64 = 68; // berlin eip2929 constants pub const ACCESS_LIST_ADDRESS: u64 = 2400; pub const ACCESS_LIST_STORAGE_KEY: u64 = 1900; -pub const ACCOUNT_ACCESS_COLD: u64 = 2600; -pub const STORAGE_READ_WARM: u64 = 100; -pub const SLOAD_COLD: u64 = 2100; +pub const COLD_SLOAD_COST: u64 = 2100; +pub const COLD_ACCOUNT_ACCESS_COST: u64 = 2600; +pub const WARM_STORAGE_READ_COST: u64 = 100; pub const CALL_STIPEND: u64 = 2300; diff --git a/crates/revm/src/instructions.rs b/crates/revm/src/instructions.rs index b8a2bde423..e1b146f127 100644 --- a/crates/revm/src/instructions.rs +++ b/crates/revm/src/instructions.rs @@ -57,7 +57,7 @@ pub enum Return { StackUnderflow, StackOverflow, OutOfOffset, - FatalNotSupported, + FatalExternalError, GasMaxFeeGreaterThanPriorityFee, GasPriceLessThenBasefee, CallerGasLimitMoreThenBlock, diff --git a/crates/revm/src/instructions/host.rs b/crates/revm/src/instructions/host.rs index efa5c96999..4d7f9b2af0 100644 --- a/crates/revm/src/instructions/host.rs +++ b/crates/revm/src/instructions/host.rs @@ -1,6 +1,11 @@ use crate::{ - alloc::vec::Vec, gas, interpreter::Interpreter, return_ok, return_revert, CallContext, - CallInputs, CallScheme, CreateInputs, CreateScheme, Host, Return, Spec, SpecId::*, Transfer, + alloc::vec::Vec, + gas::{self, COLD_ACCOUNT_ACCESS_COST, WARM_STORAGE_READ_COST}, + interpreter::Interpreter, + return_ok, return_revert, CallContext, CallInputs, CallScheme, CreateInputs, CreateScheme, + Host, Return, Spec, + SpecId::*, + Transfer, }; use bytes::Bytes; use core::cmp::min; @@ -8,7 +13,11 @@ use primitive_types::{H160, H256, U256}; pub fn balance(interp: &mut Interpreter, host: &mut H) -> Return { pop_address!(interp, address); - let (balance, is_cold) = host.balance(address); + let ret = host.balance(address); + if ret.is_none() { + return Return::FatalExternalError; + } + let (balance, is_cold) = ret.unwrap(); gas!( interp, if SPEC::enabled(ISTANBUL) { @@ -29,8 +38,11 @@ pub fn selfbalance(interp: &mut Interpreter, host: &mut H) // gas!(interp, gas::LOW); // EIP-1884: Repricing for trie-size-dependent opcodes check!(SPEC::enabled(ISTANBUL)); - - let (balance, _) = host.balance(interp.contract.address); + let ret = host.balance(interp.contract.address); + if ret.is_none() { + return Return::FatalExternalError; + } + let (balance, _) = ret.unwrap(); push!(interp, balance); Return::Continue @@ -38,9 +50,15 @@ pub fn selfbalance(interp: &mut Interpreter, host: &mut H) pub fn extcodesize(interp: &mut Interpreter, host: &mut H) -> Return { pop_address!(interp, address); - - let (code, is_cold) = host.code(address); - gas!(interp, gas::account_access_gas::(is_cold)); + let ret = host.code(address); + if ret.is_none() { + return Return::FatalExternalError; + } + let (code, is_cold) = ret.unwrap(); + if SPEC::enabled(BERLIN) && is_cold { + // WARM_STORAGE_READ_COST is already calculated in gas block + gas!(interp, COLD_ACCOUNT_ACCESS_COST - WARM_STORAGE_READ_COST); + } push!(interp, U256::from(code.len())); @@ -50,16 +68,15 @@ pub fn extcodesize(interp: &mut Interpreter, host: &mut H) pub fn extcodehash(interp: &mut Interpreter, host: &mut H) -> Return { check!(SPEC::enabled(CONSTANTINOPLE)); // EIP-1052: EXTCODEHASH opcode pop_address!(interp, address); - let (code_hash, is_cold) = host.code_hash(address); - gas!( - interp, - if SPEC::enabled(ISTANBUL) { - // EIP-1884: Repricing for trie-size-dependent opcodes - gas::account_access_gas::(is_cold) - } else { - 400 - } - ); + let ret = host.code_hash(address); + if ret.is_none() { + return Return::FatalExternalError; + } + let (code_hash, is_cold) = ret.unwrap(); + if SPEC::enabled(BERLIN) && is_cold { + // WARM_STORAGE_READ_COST is already calculated in gas block + gas!(interp, COLD_ACCOUNT_ACCESS_COST - WARM_STORAGE_READ_COST); + } push_h256!(interp, code_hash); Return::Continue @@ -69,7 +86,12 @@ pub fn extcodecopy(interp: &mut Interpreter, host: &mut H) pop_address!(interp, address); pop!(interp, memory_offset, code_offset, len_u256); - let (code, is_cold) = host.code(address); + let ret = host.code(address); + if ret.is_none() { + return Return::FatalExternalError; + } + let (code, is_cold) = ret.unwrap(); + gas_or_fail!(interp, gas::extcodecopy_cost::(len_u256, is_cold)); let len = as_usize_or_fail!(len_u256, Return::OutOfGas); if len == 0 { @@ -94,7 +116,11 @@ pub fn blockhash(interp: &mut Interpreter, host: &mut H) -> Return { let diff = as_usize_saturated!(diff); // blockhash should push zero if number is same as current block number. if diff <= 256 && diff != 0 { - *number = U256::from_big_endian(host.block_hash(*number).as_ref()); + let ret = host.block_hash(*number); + if ret.is_none() { + return Return::FatalExternalError; + } + *number = U256::from_big_endian(ret.unwrap().as_ref()); return Return::Continue; } } @@ -104,7 +130,12 @@ pub fn blockhash(interp: &mut Interpreter, host: &mut H) -> Return { pub fn sload(interp: &mut Interpreter, host: &mut H) -> Return { pop!(interp, index); - let (value, is_cold) = host.sload(interp.contract.address, index); + + let ret = host.sload(interp.contract.address, index); + if ret.is_none() { + return Return::FatalExternalError; + } + let (value, is_cold) = ret.unwrap(); gas!(interp, gas::sload_cost::(is_cold)); push!(interp, value); Return::Continue @@ -114,7 +145,11 @@ pub fn sstore(interp: &mut Interpreter, host: &mut H) -> Re check!(!SPEC::IS_STATIC_CALL); pop!(interp, index, value); - let (original, old, new, is_cold) = host.sstore(interp.contract.address, index, value); + let ret = host.sstore(interp.contract.address, index, value); + if ret.is_none() { + return Return::FatalExternalError; + } + let (original, old, new, is_cold) = ret.unwrap(); gas_or_fail!(interp, { let remaining_gas = interp.gas.remaining(); gas::sstore_cost::(original, old, new, remaining_gas, is_cold) @@ -158,6 +193,10 @@ pub fn selfdestruct(interp: &mut Interpreter, host: &mut H) pop_address!(interp, target); let res = host.selfdestruct(interp.contract.address, target); + if res.is_none() { + return Return::FatalExternalError; + } + let res = res.unwrap(); // EIP-3529: Reduction in refunds if !SPEC::enabled(LONDON) && !res.previously_destroyed { @@ -168,16 +207,6 @@ pub fn selfdestruct(interp: &mut Interpreter, host: &mut H) Return::SelfDestruct } -fn gas_call_l64_after(interp: &mut Interpreter) -> Result { - if SPEC::enabled(TANGERINE) { - //EIP-150: Gas cost changes for IO-heavy operations - let gas = interp.gas().remaining(); - Ok(gas - gas / 64) - } else { - Ok(interp.gas().remaining()) - } -} - pub fn create( interp: &mut Interpreter, is_create2: bool, @@ -185,7 +214,8 @@ pub fn create( ) -> Return { check!(!SPEC::IS_STATIC_CALL); if is_create2 { - check!(SPEC::enabled(CONSTANTINOPLE)); // EIP-1014: Skinny CREATE2 + // EIP-1014: Skinny CREATE2 + check!(SPEC::enabled(PETERSBURG)); } interp.return_data_buffer = Bytes::new(); @@ -210,8 +240,13 @@ pub fn create( CreateScheme::Create }; - // take remaining gas and deduce l64 part of it. - let gas_limit = try_or_fail!(gas_call_l64_after::(interp)); + let mut gas_limit = interp.gas().remaining(); + + // EIP-150: Gas cost changes for IO-heavy operations + if SPEC::enabled(TANGERINE) { + // take remaining gas and deduce l64 part of it. + gas_limit -= gas_limit / 64 + } gas!(interp, gas_limit); let mut create_input = CreateInputs { @@ -222,20 +257,25 @@ pub fn create( gas_limit, }; - let (reason, address, gas, return_data) = host.create::(&mut create_input); + let (return_reason, address, gas, return_data) = host.create::(&mut create_input); interp.return_data_buffer = return_data; - let created_address: H256 = if matches!(reason, return_ok!()) { - address.map(|a| a.into()).unwrap_or_default() - } else { - H256::default() - }; - push_h256!(interp, created_address); - // reimburse gas that is not spend - interp.gas.reimburse_unspend(&reason, gas); - match reason { - Return::FatalNotSupported => Return::FatalNotSupported, - _ => interp.add_next_gas_block(interp.program_counter() - 1), + + match return_reason { + return_ok!() => { + push_h256!(interp, address.map(|a| a.into()).unwrap_or_default()); + interp.gas.erase_cost(gas.remaining()); + interp.gas.record_refund(gas.refunded()); + } + return_revert!() => { + push_h256!(interp, H256::default()); + interp.gas.erase_cost(gas.remaining()); + } + Return::FatalExternalError => return Return::FatalExternalError, + _ => { + push_h256!(interp, H256::default()); + } } + interp.add_next_gas_block(interp.program_counter() - 1) } pub fn call( @@ -339,7 +379,11 @@ pub fn call( }; // load account and calculate gas cost. - let (is_cold, exist) = host.load_account(to); + let res = host.load_account(to); + if res.is_none() { + return Return::FatalExternalError; + } + let (is_cold, exist) = res.unwrap(); let is_new = !exist; gas!( @@ -354,8 +398,13 @@ pub fn call( ); // take l64 part of gas_limit - let global_gas_limit = try_or_fail!(gas_call_l64_after::(interp)); - let mut gas_limit = min(global_gas_limit, local_gas_limit); + let mut gas_limit = if SPEC::enabled(TANGERINE) { + //EIP-150: Gas cost changes for IO-heavy operations + let gas = interp.gas().remaining(); + min(gas - gas / 64, local_gas_limit) + } else { + local_gas_limit + }; gas!(interp, gas_limit); @@ -381,21 +430,25 @@ pub fn call( interp.return_data_buffer = return_data; let target_len = min(out_len, interp.return_data_buffer.len()); - // return unspend gas. - interp.gas.reimburse_unspend(&reason, gas); + match reason { return_ok!() => { + // return unspend gas. + interp.gas.erase_cost(gas.remaining()); + interp.gas.record_refund(gas.refunded()); interp .memory .set(out_offset, &interp.return_data_buffer[..target_len]); push!(interp, U256::one()); } return_revert!() => { - push!(interp, U256::zero()); + interp.gas.erase_cost(gas.remaining()); interp .memory .set(out_offset, &interp.return_data_buffer[..target_len]); + push!(interp, U256::zero()); } + Return::FatalExternalError => return Return::FatalExternalError, _ => { push!(interp, U256::zero()); } diff --git a/crates/revm/src/instructions/macros.rs b/crates/revm/src/instructions/macros.rs index d1ad4f1d82..323327d2db 100644 --- a/crates/revm/src/instructions/macros.rs +++ b/crates/revm/src/instructions/macros.rs @@ -1,14 +1,5 @@ pub use crate::Return; -macro_rules! try_or_fail { - ( $e:expr ) => { - match $e { - Ok(v) => v, - Err(e) => return e, - } - }; -} - macro_rules! check { ($expresion:expr) => { if !$expresion { diff --git a/crates/revm/src/instructions/opcode.rs b/crates/revm/src/instructions/opcode.rs index 5d20371f41..e69678a3f1 100644 --- a/crates/revm/src/instructions/opcode.rs +++ b/crates/revm/src/instructions/opcode.rs @@ -306,8 +306,22 @@ macro_rules! gas_opcodee { /* 0x38 CODESIZE */ OpInfo::gas(gas::BASE), /* 0x39 CODECOPY */ OpInfo::dynamic_gas(), /* 0x3a GASPRICE */ OpInfo::gas(gas::BASE), - /* 0x3b EXTCODESIZE */ OpInfo::dynamic_gas(), - /* 0x3c EXTCODECOPY */ OpInfo::dynamic_gas(), + /* 0x3b EXTCODESIZE */ + OpInfo::gas(if SpecId::enabled($spec_id, SpecId::BERLIN) { + gas::WARM_STORAGE_READ_COST // add only part of gas + } else if SpecId::enabled($spec_id, SpecId::TANGERINE) { + 700 + } else { + 20 + }), + /* 0x3c EXTCODECOPY */ + OpInfo::gas(if SpecId::enabled($spec_id, SpecId::BERLIN) { + gas::WARM_STORAGE_READ_COST // add only part of gas + } else if SpecId::enabled($spec_id, SpecId::TANGERINE) { + 700 + } else { + 20 + }), /* 0x3d RETURNDATASIZE */ OpInfo::gas(if SpecId::enabled($spec_id, SpecId::BYZANTIUM) { gas::BASE @@ -315,7 +329,17 @@ macro_rules! gas_opcodee { 0 }), /* 0x3e RETURNDATACOPY */ OpInfo::dynamic_gas(), - /* 0x3f EXTCODEHASH */ OpInfo::dynamic_gas(), + /* 0x3f EXTCODEHASH */ + OpInfo::gas(if SpecId::enabled($spec_id, SpecId::BERLIN) { + gas::WARM_STORAGE_READ_COST // add only part of gas + } else if SpecId::enabled($spec_id, SpecId::ISTANBUL) { + 700 + } else if SpecId::enabled($spec_id, SpecId::PETERSBURG) { + // constantinople + 400 + } else { + 0 // not enabled + }), /* 0x40 BLOCKHASH */ OpInfo::gas(gas::BLOCKHASH), /* 0x41 COINBASE */ OpInfo::gas(gas::BASE), /* 0x42 TIMESTAMP */ OpInfo::gas(gas::BASE), @@ -535,10 +559,18 @@ pub const fn spec_opcode_gas(spec_id: SpecId) -> &'static [OpInfo; 256] { gas_opcodee!(FRONTIER, SpecId::FRONTIER); FRONTIER } + SpecId::FRONTIER_THAWING => { + gas_opcodee!(FRONTIER_THAWING, SpecId::FRONTIER_THAWING); + FRONTIER_THAWING + } SpecId::HOMESTEAD => { gas_opcodee!(HOMESTEAD, SpecId::HOMESTEAD); HOMESTEAD } + SpecId::DAO_FORK => { + gas_opcodee!(DAO_FORK, SpecId::DAO_FORK); + DAO_FORK + } SpecId::TANGERINE => { gas_opcodee!(TANGERINE, SpecId::TANGERINE); TANGERINE @@ -563,8 +595,8 @@ pub const fn spec_opcode_gas(spec_id: SpecId) -> &'static [OpInfo; 256] { gas_opcodee!(ISTANBUL, SpecId::ISTANBUL); ISTANBUL } - SpecId::MUIRGLACIER => { - gas_opcodee!(MUIRGLACIER, SpecId::MUIRGLACIER); + SpecId::MUIR_GLACIER => { + gas_opcodee!(MUIRGLACIER, SpecId::MUIR_GLACIER); MUIRGLACIER } SpecId::BERLIN => { @@ -575,6 +607,14 @@ pub const fn spec_opcode_gas(spec_id: SpecId) -> &'static [OpInfo; 256] { gas_opcodee!(LONDON, SpecId::LONDON); LONDON } + SpecId::ARROW_GLACIER => { + gas_opcodee!(ARROW_GLACIER, SpecId::ARROW_GLACIER); + ARROW_GLACIER + } + SpecId::GRAY_GLACIER => { + gas_opcodee!(GRAY_GLACIER, SpecId::GRAY_GLACIER); + GRAY_GLACIER + } SpecId::MERGE => { gas_opcodee!(MERGE, SpecId::MERGE); MERGE diff --git a/crates/revm/src/journaled_state.rs b/crates/revm/src/journaled_state.rs index ddd9434a80..4edcba1804 100644 --- a/crates/revm/src/journaled_state.rs +++ b/crates/revm/src/journaled_state.rs @@ -17,6 +17,13 @@ pub struct JournaledState { pub depth: usize, /// journal with changes that happened between calls. pub journal: Vec>, + /// Ethereum before EIP-161 differently defined empty and not-existing account + /// so we need to take care of that difference. Set this to false if you are handling + /// legacy transactions + pub is_before_spurious_dragon: bool, + /// It is assumed that precompiles start from 0x1 address and spand next N addresses. + /// we are using that assumption here + pub num_of_precompiles: usize, } pub type State = Map; @@ -35,14 +42,25 @@ pub struct Account { pub is_destroyed: bool, /// if account is touched pub is_touched: bool, - /// is precompile - pub is_existing_precompile: bool, + /// used only for pre spurious dragon hardforks where exisnting and empty was two saparate states. + /// it became same state after EIP-161: State trie clearing + pub is_not_existing: bool, } impl Account { pub fn is_empty(&self) -> bool { self.info.is_empty() } + pub fn new_not_existing() -> Self { + Self { + info: AccountInfo::default(), + storage: Map::new(), + storage_cleared: false, + is_destroyed: false, + is_touched: false, + is_not_existing: true, + } + } } impl From for Account { @@ -53,7 +71,7 @@ impl From for Account { storage_cleared: false, is_destroyed: false, is_touched: false, - is_existing_precompile: false, + is_not_existing: false, } } } @@ -130,22 +148,24 @@ pub struct JournalCheckpoint { journal_i: usize, } -impl Default for JournaledState { - fn default() -> Self { - Self::new() - } -} - impl JournaledState { - pub fn new() -> JournaledState { + pub fn new(num_of_precompiles: usize) -> JournaledState { Self { state: Map::new(), logs: Vec::new(), journal: vec![vec![]], depth: 0, + is_before_spurious_dragon: false, + num_of_precompiles, } } + pub fn new_legacy(num_of_precompiles: usize) -> JournaledState { + let mut journal = Self::new(num_of_precompiles); + journal.is_before_spurious_dragon = true; + journal + } + pub fn state(&mut self) -> &mut State { &mut self.state } @@ -163,29 +183,6 @@ impl JournaledState { } } - pub fn load_precompiles(&mut self, precompiles: Map) { - let state: Map = precompiles - .into_iter() - .map(|(k, value)| { - let acc = Account::from(value); - (k, acc) - }) - .collect(); - self.state.extend(state); - } - - pub fn load_precompiles_default(&mut self, precompiles: &[H160]) { - let state: State = precompiles - .iter() - .map(|&k| { - let mut acc = Account::from(AccountInfo::default()); - acc.is_existing_precompile = true; - (k, acc) - }) - .collect(); - self.state.extend(state); - } - /// do cleanup and return modified state pub fn finalize(&mut self) -> (State, Vec) { let state = mem::take(&mut self.state); @@ -253,8 +250,13 @@ impl JournaledState { db: &mut DB, ) -> Result<(bool, bool), Return> { // load accounts - let from_is_cold = self.load_account(*from, db); - let to_is_cold = self.load_account(*to, db); + let (_, from_is_cold) = self + .load_account(*from, db) + .map_err(|_| Return::FatalExternalError)?; + + let (_, to_is_cold) = self + .load_account(*to, db) + .map_err(|_| Return::FatalExternalError)?; // sub balance from let from_account = &mut self.state.get_mut(from).unwrap(); @@ -289,23 +291,23 @@ impl JournaledState { address: H160, is_precompile: bool, db: &mut DB, - ) -> bool { - let (acc, _) = self.load_code(address, db); + ) -> Result { + let (acc, _) = self.load_code(address, db)?; // Check collision. Bytecode needs to be empty. if let Some(ref code) = acc.info.code { if !code.is_empty() { - return false; + return Ok(false); } } // Check collision. Nonce is not zero if acc.info.nonce != 0 { - return false; + return Ok(false); } // Check collision. New account address is precompile. if is_precompile { - return false; + return Ok(false); } acc.storage_cleared = true; @@ -324,22 +326,29 @@ impl JournaledState { .last_mut() .unwrap() .push(JournalEntry::AccountTouched { address }); - true + Ok(true) } - fn journal_revert(state: &mut State, journal_entries: Vec) { + fn journal_revert( + state: &mut State, + journal_entries: Vec, + is_spurious_dragon_enabled: bool, + ) { + const PRECOMPILE3: H160 = + H160([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3]); for entry in journal_entries.into_iter().rev() { match entry { JournalEntry::AccountLoaded { address } => { + if is_spurious_dragon_enabled && address == PRECOMPILE3 { + continue; + } state.remove(&address); } JournalEntry::AccountTouched { address } => { - const PRECOMPILE3: H160 = - H160([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3]); - - if address != PRECOMPILE3 { - state.get_mut(&address).unwrap().is_touched = false; + if is_spurious_dragon_enabled && address == PRECOMPILE3 { + continue; } + state.get_mut(&address).unwrap().is_touched = false; } JournalEntry::AccountDestroyed { address, @@ -400,6 +409,7 @@ impl JournaledState { } pub fn checkpoint_revert(&mut self, checkpoint: JournalCheckpoint) { + let is_spurious_dragon_enabled = !self.is_before_spurious_dragon; let state = &mut self.state; self.depth -= 1; // iterate over last N journals sets and revert our global state @@ -408,7 +418,7 @@ impl JournaledState { .iter_mut() .rev() .take(leng - checkpoint.journal_i) - .for_each(|cs| Self::journal_revert(state, mem::take(cs))); + .for_each(|cs| Self::journal_revert(state, mem::take(cs), is_spurious_dragon_enabled)); self.logs.truncate(checkpoint.log_i); self.journal.truncate(checkpoint.journal_i); @@ -420,8 +430,8 @@ impl JournaledState { address: H160, target: H160, db: &mut DB, - ) -> SelfDestructResult { - let (is_cold, exists) = self.load_account_exist(target, db); + ) -> Result { + let (is_cold, target_exists) = self.load_account_exist(target, db)?; // transfer all the balance let acc = self.state.get_mut(&address).unwrap(); let balance = mem::take(&mut acc.info.balance); @@ -446,60 +456,85 @@ impl JournaledState { had_balance: balance, }); - SelfDestructResult { + Ok(SelfDestructResult { had_value: !balance.is_zero(), is_cold, - exists, + target_exists, previously_destroyed, - } + }) } /// load account into memory. return if it is cold or hot accessed - pub fn load_account(&mut self, address: H160, db: &mut DB) -> bool { - match self.state.entry(address) { - Entry::Occupied(ref mut _entry) => false, + pub fn load_account( + &mut self, + address: H160, + db: &mut DB, + ) -> Result<(&mut Account, bool), DB::Error> { + Ok(match self.state.entry(address) { + Entry::Occupied(entry) => (entry.into_mut(), false), Entry::Vacant(vac) => { - let acc: Account = db.basic(address).into(); + let account = if let Some(account) = db.basic(address)? { + account.into() + } else { + Account::new_not_existing() + }; + // journal loading of account. AccessList touch. self.journal .last_mut() .unwrap() .push(JournalEntry::AccountLoaded { address }); - vac.insert(acc); - true + // precompiles are hot loaded so we need to take that into account + let is_cold = !is_precompile(address, self.num_of_precompiles); + + (vac.insert(account), is_cold) } - } + }) } // first is is_cold second bool is exists. - pub fn load_account_exist(&mut self, address: H160, db: &mut DB) -> (bool, bool) { - let (acc, is_cold) = self.load_code(address, db); - if acc.is_existing_precompile { - (false, true) + pub fn load_account_exist( + &mut self, + address: H160, + db: &mut DB, + ) -> Result<(bool, bool), DB::Error> { + let is_before_spurious_dragon = self.is_before_spurious_dragon; + let (acc, is_cold) = self.load_code(address, db)?; + + let exist = if is_before_spurious_dragon { + !acc.is_not_existing || acc.is_touched } else { - let exists = !acc.is_empty(); - (is_cold, exists) - } + !acc.is_empty() + }; + Ok((is_cold, exist)) } - pub fn load_code(&mut self, address: H160, db: &mut DB) -> (&mut Account, bool) { - let is_cold = self.load_account(address, db); - let acc = self.state.get_mut(&address).unwrap(); + pub fn load_code( + &mut self, + address: H160, + db: &mut DB, + ) -> Result<(&mut Account, bool), DB::Error> { + let (acc, is_cold) = self.load_account(address, db)?; if acc.info.code.is_none() { if acc.info.code_hash == KECCAK_EMPTY { let empty = Bytecode::new(); acc.info.code = Some(empty); } else { - let code = db.code_by_hash(acc.info.code_hash); + let code = db.code_by_hash(acc.info.code_hash)?; acc.info.code = Some(code); } } - (acc, is_cold) + Ok((acc, is_cold)) } // account is already present and loaded. - pub fn sload(&mut self, address: H160, key: U256, db: &mut DB) -> (U256, bool) { + pub fn sload( + &mut self, + address: H160, + key: U256, + db: &mut DB, + ) -> Result<(U256, bool), DB::Error> { let account = self.state.get_mut(&address).unwrap(); // asume acc is hot let load = match account.storage.entry(key) { Entry::Occupied(occ) => (occ.get().present_value, false), @@ -508,7 +543,7 @@ impl JournaledState { let value = if account.storage_cleared { U256::zero() } else { - db.storage(address, key) + db.storage(address, key)? }; // add it to journal as cold loaded. self.journal @@ -525,7 +560,7 @@ impl JournaledState { (value, true) } }; - load + Ok(load) } /// account should already be present in our state. @@ -536,9 +571,9 @@ impl JournaledState { key: U256, new: U256, db: &mut DB, - ) -> (U256, U256, U256, bool) { + ) -> Result<(U256, U256, U256, bool), DB::Error> { // assume that acc exists and load the slot. - let (present, is_cold) = self.sload(address, key, db); + let (present, is_cold) = self.sload(address, key, db)?; let acc = self.state.get_mut(&address).unwrap(); // if there is no original value in dirty return present value, that is our original. @@ -546,7 +581,7 @@ impl JournaledState { // new value is same as present, we dont need to do anything if present == new { - return (slot.original_value, present, new, is_cold); + return Ok((slot.original_value, present, new, is_cold)); } self.journal @@ -559,7 +594,7 @@ impl JournaledState { }); // insert value into present state. slot.present_value = new; - (slot.original_value, present, new, is_cold) + Ok((slot.original_value, present, new, is_cold)) } /// push log into subroutine @@ -567,3 +602,64 @@ impl JournaledState { self.logs.push(log); } } + +fn is_precompile(address: H160, num_of_precompiles: usize) -> bool { + if !address[..18].iter().all(|i| *i == 0) { + return false; + } + let num = u16::from_be_bytes([address[18], address[19]]); + num.wrapping_sub(1) < num_of_precompiles as u16 +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_is_precompile() { + assert_eq!( + is_precompile( + H160([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + 3 + ), + false, + "Zero is not precompile" + ); + + assert_eq!( + is_precompile( + H160([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9]), + 3 + ), + false, + "0x100..0 is not precompile" + ); + + assert_eq!( + is_precompile( + H160([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4]), + 3 + ), + false, + "0x000..4 is not precompile" + ); + + assert_eq!( + is_precompile( + H160([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]), + 3 + ), + true, + "0x00..01 is precompile" + ); + + assert_eq!( + is_precompile( + H160([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3]), + 3 + ), + true, + "0x000..3 is precompile" + ); + } +} diff --git a/crates/revm/src/models.rs b/crates/revm/src/models.rs index 76075246e5..24b524f074 100644 --- a/crates/revm/src/models.rs +++ b/crates/revm/src/models.rs @@ -322,7 +322,7 @@ pub struct Log { #[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))] pub struct SelfDestructResult { pub had_value: bool, - pub exists: bool, + pub target_exists: bool, pub is_cold: bool, pub previously_destroyed: bool, } diff --git a/crates/revm/src/specification.rs b/crates/revm/src/specification.rs index 5f817e5956..bfa92fcef4 100644 --- a/crates/revm/src/specification.rs +++ b/crates/revm/src/specification.rs @@ -2,33 +2,43 @@ use core::convert::TryFrom; use num_enum::TryFromPrimitive; use revm_precompiles::SpecId as PrecompileId; +/// SpecId and their activation block +/// Information was obtained from: https://github.com/ethereum/execution-specs #[repr(u8)] #[derive(Debug, Copy, Clone, TryFromPrimitive, Eq, PartialEq, Hash, Ord, PartialOrd)] #[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))] #[allow(non_camel_case_types)] pub enum SpecId { - FRONTIER = 1, - HOMESTEAD = 2, - TANGERINE = 3, - SPURIOUS_DRAGON = 4, - BYZANTIUM = 5, - CONSTANTINOPLE = 6, - PETERSBURG = 7, - ISTANBUL = 8, - MUIRGLACIER = 9, - BERLIN = 10, - LONDON = 11, - MERGE = 12, - LATEST = 13, + FRONTIER = 0, // Frontier 0 + FRONTIER_THAWING = 1, // Frontier Thawing 200000 + HOMESTEAD = 2, // Homestead 1150000 + DAO_FORK = 3, // DAO Fork 1920000 + TANGERINE = 4, // Tangerine Whistle 2463000 + SPURIOUS_DRAGON = 5, // Spurious Dragon 2675000 + BYZANTIUM = 6, // Byzantium 4370000 + CONSTANTINOPLE = 7, // Constantinople 7280000 is overwriten with PETERSBURG + PETERSBURG = 8, // Petersburg 7280000 + ISTANBUL = 9, // Istanbul 9069000 + MUIR_GLACIER = 10, // Muir Glacier 9200000 + BERLIN = 11, // Berlin 12244000 + LONDON = 12, // London 12965000 + ARROW_GLACIER = 13, // Arrow Glacier 13773000 + GRAY_GLACIER = 14, // Gray Glacier 15050000 + MERGE = 15, // Paris/Merge TBD (Depends on difficulty) + LATEST = 16, } impl SpecId { pub const fn to_precompile_id(self) -> u8 { match self { - FRONTIER | HOMESTEAD | TANGERINE | SPURIOUS_DRAGON => PrecompileId::HOMESTEAD as u8, + FRONTIER | FRONTIER_THAWING | HOMESTEAD | DAO_FORK | TANGERINE | SPURIOUS_DRAGON => { + PrecompileId::HOMESTEAD as u8 + } BYZANTIUM | CONSTANTINOPLE | PETERSBURG => PrecompileId::BYZANTIUM as u8, - ISTANBUL | MUIRGLACIER => PrecompileId::ISTANBUL as u8, - BERLIN | LONDON | MERGE | LATEST => PrecompileId::BERLIN as u8, + ISTANBUL | MUIR_GLACIER => PrecompileId::ISTANBUL as u8, + BERLIN | LONDON | ARROW_GLACIER | GRAY_GLACIER | MERGE | LATEST => { + PrecompileId::BERLIN as u8 + } } } @@ -50,7 +60,7 @@ impl From<&str> for SpecId { "Constantinople" => SpecId::CONSTANTINOPLE, "Petersburg" => SpecId::PETERSBURG, "Istanbul" => SpecId::ISTANBUL, - "MuirGlacier" => SpecId::MUIRGLACIER, + "MuirGlacier" => SpecId::MUIR_GLACIER, "Berlin" => SpecId::BERLIN, "London" => SpecId::LONDON, "Merge" => SpecId::MERGE, @@ -119,17 +129,33 @@ pub(crate) mod spec_impl { }; } - spec!(LATEST); - spec!(MERGE); - spec!(LONDON); - spec!(BERLIN); - spec!(ISTANBUL); - spec!(BYZANTIUM); spec!(FRONTIER); + // FRONTIER_THAWING no EVM spec change + spec!(HOMESTEAD); + // DAO_FORK no EVM spec change + spec!(TANGERINE); + spec!(SPURIOUS_DRAGON); + spec!(BYZANTIUM); + // CONSTANTINOPLE was overriden with PETERSBURG + spec!(PETERSBURG); + spec!(ISTANBUL); + // MUIR_GLACIER no EVM spec change + spec!(BERLIN); + spec!(LONDON); + // ARROW_GLACIER no EVM spec change + // GRAT_GLACIER no EVM spec change + spec!(MERGE); + spec!(LATEST); } -pub use spec_impl::{ - BERLIN::SpecImpl as BerlinSpec, BYZANTIUM::SpecImpl as ByzantiumSpec, - FRONTIER::SpecImpl as FrontierSpec, ISTANBUL::SpecImpl as IstanbulSpec, - LATEST::SpecImpl as LatestSpec, LONDON::SpecImpl as LondonSpec, MERGE::SpecImpl as MergeSpec, -}; +pub use spec_impl::BERLIN::SpecImpl as BerlinSpec; +pub use spec_impl::BYZANTIUM::SpecImpl as ByzantiumSpec; +pub use spec_impl::FRONTIER::SpecImpl as FrontierSpec; +pub use spec_impl::HOMESTEAD::SpecImpl as HomesteadSpec; +pub use spec_impl::ISTANBUL::SpecImpl as IstanbulSpec; +pub use spec_impl::LATEST::SpecImpl as LatestSpec; +pub use spec_impl::LONDON::SpecImpl as LondonSpec; +pub use spec_impl::MERGE::SpecImpl as MergeSpec; +pub use spec_impl::PETERSBURG::SpecImpl as PetersburgSpec; +pub use spec_impl::SPURIOUS_DRAGON::SpecImpl as SpuriousDragonSpec; +pub use spec_impl::TANGERINE::SpecImpl as TangerineSpec; diff --git a/crates/revm_precompiles/Cargo.toml b/crates/revm_precompiles/Cargo.toml index 7a06adf949..2ce63b7367 100644 --- a/crates/revm_precompiles/Cargo.toml +++ b/crates/revm_precompiles/Cargo.toml @@ -18,6 +18,7 @@ ripemd = { version = "0.1", default-features = false } secp256k1 = { version = "0.24.0", default-features = false, features = ["alloc", "recovery"], optional = true } sha2 = { version = "0.10.2", default-features = false } sha3 = { version = "0.10.1", default-features = false } +hashbrown = { version = "0.12" } [dev-dependencies] hex = "0.4" diff --git a/crates/revm_precompiles/src/lib.rs b/crates/revm_precompiles/src/lib.rs index 35103f9344..98ce5d63bc 100644 --- a/crates/revm_precompiles/src/lib.rs +++ b/crates/revm_precompiles/src/lib.rs @@ -16,9 +16,10 @@ pub use error::Return; /// libraries for no_std flag #[macro_use] extern crate alloc; -use alloc::collections::BTreeMap; use alloc::vec::Vec; +use hashbrown::HashMap; + pub fn calc_linear_cost_u32(len: usize, base: u64, word: u64) -> u64 { (len as u64 + 32 - 1) / 32 * word + base } @@ -61,7 +62,7 @@ pub type StandardPrecompileFn = fn(&[u8], u64) -> PrecompileResult; pub type CustomPrecompileFn = fn(&[u8], u64) -> PrecompileResult; pub struct Precompiles { - fun: BTreeMap, + fun: HashMap, } impl Default for Precompiles { @@ -91,39 +92,40 @@ impl SpecId { impl Precompiles { pub fn new() -> Self { - let mut fun: BTreeMap = BTreeMap::new(); + let mut fun: HashMap = HashMap::new(); let mut insert_fun = |precompile: (Address, Precompile)| fun.insert(precompile.0, precompile.1); if SpecId::HOMESTEAD.enabled(SPEC_ID) { + insert_fun(secp256k1::ECRECOVER); insert_fun(hash::SHA256); insert_fun(hash::RIPEMD160); - insert_fun(secp256k1::ECRECOVER); insert_fun(identity::FUN); } if SpecId::ISTANBUL.enabled(SPEC_ID) { - // EIP-152: Add BLAKE2 compression function `F` precompile + // EIP-152: Add BLAKE2 compression function `F` precompile. insert_fun(blake2::FUN); } if SpecId::ISTANBUL.enabled(SPEC_ID) { - // EIP-1108: Reduce alt_bn128 precompile gas costs + // EIP-1108: Reduce alt_bn128 precompile gas costs. insert_fun(bn128::add::ISTANBUL); insert_fun(bn128::mul::ISTANBUL); insert_fun(bn128::pair::ISTANBUL); } else if SpecId::BYZANTIUM.enabled(SPEC_ID) { - // EIP-196: Precompiled contracts for addition and scalar multiplication on the elliptic curve alt_bn128 - // EIP-197: Precompiled contracts for optimal ate pairing check on the elliptic curve alt_bn128 + // EIP-196: Precompiled contracts for addition and scalar multiplication on the elliptic curve alt_bn128. + // EIP-197: Precompiled contracts for optimal ate pairing check on the elliptic curve alt_bn128. insert_fun(bn128::add::BYZANTIUM); insert_fun(bn128::mul::BYZANTIUM); insert_fun(bn128::pair::BYZANTIUM); } if SpecId::BERLIN.enabled(SPEC_ID) { + // EIP-2565: ModExp Gas Cost. insert_fun(modexp::BERLIN); } else if SpecId::BYZANTIUM.enabled(SPEC_ID) { - //EIP-198: Big integer modular exponentiation + // EIP-198: Big integer modular exponentiation. insert_fun(modexp::BYZANTIUM); } @@ -142,41 +144,15 @@ impl Precompiles { //return None; self.fun.get(address).cloned() } -} - -/// Matches the address given to Homestead precompiles. -// impl<'backend, 'config> executor::Precompiles> for Precompiles { -// fn run( -// &self, -// address: Address, -// input: &[u8], -// target_gas: Option, -// context: &CallContext, -// state: &mut AuroraStackState, -// is_static: bool, -// ) -> Option { -// let target_gas = match target_gas { -// Some(t) => t, -// None => return Some(EvmPrecompileResult::Err(Return::OutOfGas)), -// }; - -// let output = self.get_fun(&address).map(|fun| { -// let mut res = (fun)(input, target_gas, context, is_static); -// if let Ok(output) = &mut res { -// if let Some(promise) = output.promise.take() { -// state.add_promise(promise) -// } -// } -// res -// }); -// output.map(|res| res.map(Into::into)) -// } + pub fn is_empty(&self) -> bool { + self.fun.len() == 0 + } -// fn addresses(&self) -> &[Address] { -// &self.addresses -// } -// } + pub fn len(&self) -> usize { + self.fun.len() + } +} /// const fn for making an address by concatenating the bytes from two given numbers, /// Note that 32 + 128 = 160 = 20 bytes (the length of an address). This function is used @@ -214,56 +190,3 @@ pub fn u256_to_arr(value: &U256) -> [u8; 32] { value.to_big_endian(&mut result); result } - -/* -#[cfg(test)] -mod tests { - use crate::precompiles::{Byzantium, Istanbul}; - use crate::prelude::Address; - use rand::Rng; - - #[test] - fn test_precompile_addresses() { - assert_eq!(super::secp256k1::ECRecover::ADDRESS, u8_to_address(1)); - assert_eq!(super::hash::SHA256::ADDRESS, u8_to_address(2)); - assert_eq!(super::hash::RIPEMD160::ADDRESS, u8_to_address(3)); - assert_eq!(super::identity::Identity::ADDRESS, u8_to_address(4)); - assert_eq!(super::ModExp::::ADDRESS, u8_to_address(5)); - assert_eq!(super::Bn128Add::::ADDRESS, u8_to_address(6)); - assert_eq!(super::Bn128Mul::::ADDRESS, u8_to_address(7)); - assert_eq!(super::Bn128Pair::::ADDRESS, u8_to_address(8)); - assert_eq!(super::blake2::Blake2F::ADDRESS, u8_to_address(9)); - } - - #[test] - fn test_make_address() { - for i in 0..u8::MAX { - assert_eq!(super::make_address(0, i as u128), u8_to_address(i)); - } - - let mut rng = rand::thread_rng(); - for _ in 0..u8::MAX { - let address: Address = Address(rng.gen()); - let (x, y) = split_address(address); - assert_eq!(address, super::make_address(x, y)) - } - } - - fn u8_to_address(x: u8) -> Address { - let mut bytes = [0u8; 20]; - bytes[19] = x; - Address(bytes) - } - - // Inverse function of `super::make_address`. - fn split_address(a: Address) -> (u32, u128) { - let mut x_bytes = [0u8; 4]; - let mut y_bytes = [0u8; 16]; - - x_bytes.copy_from_slice(&a[0..4]); - y_bytes.copy_from_slice(&a[4..20]); - - (u32::from_be_bytes(x_bytes), u128::from_be_bytes(y_bytes)) - } -} -*/