Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(revm): Evm Context Tests and test-utils Feature #903

Merged
merged 3 commits into from Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/revm/Cargo.toml
Expand Up @@ -44,6 +44,8 @@ std = ["revm-interpreter/std", "revm-precompile/std"]
serde = ["dep:serde", "dep:serde_json", "revm-interpreter/serde"]
arbitrary = ["revm-interpreter/arbitrary"]

test-utils = []

optimism = ["revm-interpreter/optimism", "revm-precompile/optimism"]

ethersdb = ["std", "tokio", "futures", "ethers-providers", "ethers-core"]
Expand Down
169 changes: 167 additions & 2 deletions crates/revm/src/evm_context.rs
Expand Up @@ -266,13 +266,11 @@ impl<'a, DB: Database> EvmContext<'a, DB> {
inputs.transfer.value,
self.db,
) {
//println!("transfer error");
self.journaled_state.checkpoint_revert(checkpoint);
return return_result(e);
}

if let Some(precompile) = self.precompiles.get(&inputs.contract) {
//println!("Call precompile");
let result = self.call_precompile(precompile, inputs, gas);
if matches!(result.result, return_ok!()) {
self.journaled_state.checkpoint_commit();
Expand Down Expand Up @@ -432,3 +430,170 @@ impl<'a, DB: Database> EvmContext<'a, DB> {
(interpreter_result, address)
}
}

/// Test utilities for the [`EvmContext`].
#[cfg(any(test, feature = "test-utils"))]
pub(crate) mod test_utils {
use super::*;
use crate::db::CacheDB;
use crate::db::EmptyDB;
use crate::primitives::address;
use crate::primitives::SpecId;

/// Mock caller address.
pub const MOCK_CALLER: Address = address!("0000000000000000000000000000000000000000");

/// Creates `CallInputs` that calls a provided contract address from the mock caller.
pub fn create_mock_call_inputs(to: Address) -> CallInputs {
CallInputs {
contract: to,
transfer: revm_interpreter::Transfer {
source: MOCK_CALLER,
target: to,
value: U256::ZERO,
},
input: Bytes::new(),
gas_limit: 0,
context: revm_interpreter::CallContext {
address: MOCK_CALLER,
caller: MOCK_CALLER,
code_address: MOCK_CALLER,
apparent_value: U256::ZERO,
scheme: revm_interpreter::CallScheme::Call,
},
is_static: false,
}
}

/// Creates an evm context with a cache db backend.
/// Additionally loads the mock caller account into the db,
/// and sets the balance to the provided U256 value.
pub fn create_cache_db_evm_context_with_balance<'a>(
env: &'a mut Env,
db: &'a mut CacheDB<EmptyDB>,
balance: U256,
) -> EvmContext<'a, CacheDB<EmptyDB>> {
db.insert_account_info(
test_utils::MOCK_CALLER,
crate::primitives::AccountInfo {
nonce: 0,
balance,
code_hash: B256::default(),
code: None,
},
);
create_cache_db_evm_context(env, db)
}

/// Creates a cached db evm context.
pub fn create_cache_db_evm_context<'a>(
env: &'a mut Env,
db: &'a mut CacheDB<EmptyDB>,
) -> EvmContext<'a, CacheDB<EmptyDB>> {
EvmContext {
env,
journaled_state: JournaledState::new(SpecId::CANCUN, vec![]),
db,
error: None,
precompiles: Precompiles::default(),
#[cfg(feature = "optimism")]
l1_block_info: None,
}
}

/// Returns a new `EvmContext` with an empty journaled state.
pub fn create_empty_evm_context<'a>(
env: &'a mut Env,
db: &'a mut EmptyDB,
) -> EvmContext<'a, EmptyDB> {
EvmContext {
env,
journaled_state: JournaledState::new(SpecId::CANCUN, vec![]),
db,
error: None,
precompiles: Precompiles::default(),
#[cfg(feature = "optimism")]
l1_block_info: None,
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::db::{CacheDB, EmptyDB};
use crate::primitives::address;
use crate::JournalEntry;
use test_utils::*;

// Tests that the `EVMContext::make_call_frame` function returns an error if the
// call stack is too deep.
#[test]
fn test_make_call_frame_stack_too_deep() {
let mut env = Env::default();
let mut db = EmptyDB::default();
let mut evm_context = test_utils::create_empty_evm_context(&mut env, &mut db);
evm_context.journaled_state.depth = CALL_STACK_LIMIT as usize + 1;
let contract = address!("dead10000000000000000000000000000001dead");
let call_inputs = test_utils::create_mock_call_inputs(contract);
let res = evm_context.make_call_frame(&call_inputs, 0..0);
let err = res.unwrap_err();
assert_eq!(err.result, InstructionResult::CallTooDeep);
}

// Tests that the `EVMContext::make_call_frame` function returns an error if the
// transfer fails on the journaled state. It also verifies that the revert was
// checkpointed on the journaled state correctly.
#[test]
fn test_make_call_frame_transfer_revert() {
let mut env = Env::default();
let mut db = EmptyDB::default();
let mut evm_context = test_utils::create_empty_evm_context(&mut env, &mut db);
let contract = address!("dead10000000000000000000000000000001dead");
let mut call_inputs = test_utils::create_mock_call_inputs(contract);
call_inputs.transfer.value = U256::from(1);
let res = evm_context.make_call_frame(&call_inputs, 0..0);
let err = res.unwrap_err();
assert_eq!(err.result, InstructionResult::OutOfFund);
let checkpointed = vec![vec![JournalEntry::AccountLoaded { address: contract }]];
assert_eq!(evm_context.journaled_state.journal, checkpointed);
assert_eq!(evm_context.journaled_state.depth, 0);
}

#[test]
fn test_make_call_frame_missing_code_context() {
let mut env = Env::default();
let mut cdb = CacheDB::new(EmptyDB::default());
let bal = U256::from(3_000_000_000_u128);
let mut evm_context = create_cache_db_evm_context_with_balance(&mut env, &mut cdb, bal);
let contract = address!("dead10000000000000000000000000000001dead");
let call_inputs = test_utils::create_mock_call_inputs(contract);
let res = evm_context.make_call_frame(&call_inputs, 0..0);
assert_eq!(res.unwrap_err().result, InstructionResult::Stop);
}

#[test]
fn test_make_call_frame_succeeds() {
let mut env = Env::default();
let mut cdb = CacheDB::new(EmptyDB::default());
let bal = U256::from(3_000_000_000_u128);
let by = Bytecode::new_raw(Bytes::from(vec![0x60, 0x00, 0x60, 0x00]));
let contract = address!("dead10000000000000000000000000000001dead");
cdb.insert_account_info(
contract,
crate::primitives::AccountInfo {
nonce: 0,
balance: bal,
code_hash: by.clone().hash_slow(),
code: Some(by),
},
);
let mut evm_context = create_cache_db_evm_context_with_balance(&mut env, &mut cdb, bal);
let call_inputs = test_utils::create_mock_call_inputs(contract);
let res = evm_context.make_call_frame(&call_inputs, 0..0);
let frame = res.unwrap();
assert!(!frame.is_create);
assert_eq!(frame.created_address, None);
assert_eq!(frame.subcall_return_memory_range, 0..0);
}
}
3 changes: 3 additions & 0 deletions crates/revm/src/lib.rs
Expand Up @@ -12,6 +12,9 @@ compile_error!("`with-serde` feature has been renamed to `serde`.");
#[macro_use]
extern crate alloc;

#[cfg(any(test, feature = "test-utils"))]
pub mod test_utils;

pub mod db;
mod evm;
mod evm_context;
Expand Down
2 changes: 2 additions & 0 deletions crates/revm/src/test_utils.rs
@@ -0,0 +1,2 @@
#[doc(hidden)]
pub use crate::evm_context::test_utils::*;