Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 90 additions & 1 deletion executor/evm/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,9 @@ where
expected: "StoredValue::ByteCode",
found: stored_value.type_name(),
}),
None => Ok(Bytecode::default()),
// Empty-code accounts do not require a stored bytecode record.
None if code_hash == evm::EMPTY_CODE_HASH => Ok(Bytecode::default()),
None => Err(DbError::MissingBytecode { key: Box::new(key) }),
}
}

Expand Down Expand Up @@ -186,3 +188,90 @@ fn cl_value_to_u256(key: Key, cl_value: CLValue, wei_per_mote: u64) -> Result<U2
balance_wei.to_big_endian(&mut bytes);
Ok(U256::from_be_slice(&bytes[32..]))
}

#[cfg(test)]
mod tests {
use casper_storage::{
block_store::lmdb::LmdbBlockStore,
global_state::state::{lmdb::make_temporary_global_state, StateProvider},
};
use casper_types::{ByteCode, ByteCodeKind};

use super::*;

fn read_bytecode(
code_hash: evm::Hash,
stored_value: Option<StoredValue>,
) -> Result<Bytecode, DbError> {
let key = Key::Evm(EvmAddr::ByteCode(code_hash));
let (state, root, _tempdir) =
make_temporary_global_state(stored_value.map(|value| (key, value)));
let reader = state.checkout(root).unwrap().unwrap();
let mut tracking_copy = TrackingCopy::new(reader, 5, false);
let data_access_layer = DataAccessLayer {
block_store: LmdbBlockStore::new_temporary(64 * 1024 * 1024).unwrap(),
state,
max_query_depth: 5,
enable_addressable_entity: false,
};
CasperDb::new(&data_access_layer, &mut tracking_copy, 1)
.code_by_hash(tx::to_revm_hash(code_hash))
}

#[test]
fn empty_code_hash_does_not_require_stored_bytecode() {
assert!(read_bytecode(evm::EMPTY_CODE_HASH, None)
.unwrap()
.is_empty());
}

#[test]
fn non_empty_code_hash_requires_stored_bytecode() {
// The all-zero hash is not the canonical empty-code hash.
for hash in [evm::Hash::new([0x42; 32]), evm::Hash::new([0; 32])] {
let error = read_bytecode(hash, None).unwrap_err();
assert!(matches!(
error,
DbError::MissingBytecode { key } if *key == Key::Evm(EvmAddr::ByteCode(hash))
));
}
}

#[test]
fn invalid_bytecode_records_preserve_type_errors() {
let wrong_value = StoredValue::CLValue(CLValue::from_t(1u64).unwrap());
let mut cases = vec![(
wrong_value.clone(),
"StoredValue::ByteCode",
wrong_value.type_name(),
)];
for kind in [
ByteCodeKind::Empty,
ByteCodeKind::V1CasperWasm,
ByteCodeKind::V2CasperWasm,
] {
cases.push((
StoredValue::ByteCode(ByteCode::new(kind, vec![])),
"EVM bytecode kind",
kind.to_string(),
));
}

for (value, expected_type, found_type) in cases {
let hash = evm::Hash::new([0x42; 32]);
let error = read_bytecode(hash, Some(value)).unwrap_err();
match error {
DbError::TypeMismatch {
key,
expected,
found,
} => {
assert_eq!(*key, Key::Evm(EvmAddr::ByteCode(hash)));
assert_eq!(expected, expected_type);
assert_eq!(found, found_type);
}
other => panic!("expected bytecode type mismatch, got {other:?}"),
}
}
}
}
6 changes: 6 additions & 0 deletions executor/evm/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ pub enum DbError {
/// Failed while reading from the tracking copy.
#[error(transparent)]
TrackingCopy(#[from] TrackingCopyError),
/// The bytecode referenced by a non-empty EVM code hash is missing.
#[error("missing referenced EVM bytecode at {key}")]
MissingBytecode {
/// Global-state key of the missing bytecode.
key: Box<Key>,
},
/// The value stored under an EVM key has an unexpected variant.
#[error("unexpected stored value for {key}: expected {expected}, found {found}")]
TypeMismatch {
Expand Down
203 changes: 203 additions & 0 deletions executor/evm/tests/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,209 @@ fn delegation_code(delegate: evm::Address) -> Vec<u8> {
code
}

#[test]
fn missing_bytecode_rejects_signed_value_transfer_without_state_changes() {
let executor = executor(EvmSpec::Prague);
let (mut tracking_copy, data_access_layer, _tempdir) = tracking_copy();
let target = evm::Address::new([0xa1; 20]);
let transaction = legacy_transaction_to(
Some(7),
to_alloy_address(target),
U256::from(DEFAULT_WEI_PER_MOTE),
100_000,
);
let sender = transaction.from();
seed_evm_balance(&mut tracking_copy, sender, U512::from(100));
seed_evm_balance(&mut tracking_copy, target, U512::zero());
seed_evm_code(&mut tracking_copy, target, reverting_runtime());
let missing_key = Key::Evm(EvmAddr::ByteCode(read_code_hash(
&mut tracking_copy,
target,
)));
tracking_copy.prune(missing_key);

let error = executor
.execute(
&data_access_layer,
&mut tracking_copy,
ExecuteRequest {
block: block(),
kind: ExecuteKind::Transaction(Box::new(transaction)),
},
)
.expect_err("missing contract bytecode must abort execution");

assert!(matches!(
error,
Error::Database(DbError::MissingBytecode { key }) if *key == missing_key
));
assert_eq!(read_balance(&mut tracking_copy, sender), U512::from(100));
assert_eq!(read_balance(&mut tracking_copy, target), U512::zero());
assert_eq!(read_evm_nonce(&mut tracking_copy, sender), 0);
}

#[test]
fn empty_accounts_accept_value_transfers_without_stored_bytecode() {
for has_code_hash in [true, false] {
let executor = executor(EvmSpec::Prague);
let (mut tracking_copy, data_access_layer, _tempdir) = tracking_copy();
let target = evm::Address::new([0xa1; 20]);
let transaction = legacy_transaction_to(
Some(7),
to_alloy_address(target),
U256::from(DEFAULT_WEI_PER_MOTE),
21_000,
);
let sender = transaction.from();
seed_evm_balance(&mut tracking_copy, sender, U512::from(100));
seed_evm_balance(&mut tracking_copy, target, U512::zero());
if !has_code_hash {
tracking_copy.prune(Key::Evm(EvmAddr::CodeHash(target)));
}
assert!(read_code(&mut tracking_copy, EMPTY_CODE_HASH).is_none());

let outcome = execute_transaction(
&executor,
&data_access_layer,
&mut tracking_copy,
transaction,
);

assert_eq!(outcome.status, ExecutionStatus::Success);
assert_eq!(read_balance(&mut tracking_copy, sender), U512::from(99));
assert_eq!(read_balance(&mut tracking_copy, target), U512::one());
assert_eq!(read_evm_nonce(&mut tracking_copy, sender), 1);
assert_eq!(read_code_hash(&mut tracking_copy, target), EMPTY_CODE_HASH);
assert!(read_code(&mut tracking_copy, EMPTY_CODE_HASH).is_none());
}
}

#[test]
fn missing_bytecode_aborts_nested_execution_without_state_changes() {
for instruction in [opcode::CALL, opcode::EXTCODESIZE, opcode::EXTCODECOPY] {
let executor = executor(EvmSpec::Prague);
let (mut tracking_copy, data_access_layer, _tempdir) = tracking_copy();
let sender = evm::Address::new([0xa0; 20]);
let outer = evm::Address::new([0xa1; 20]);
let inner = evm::Address::new([0xa2; 20]);
seed_evm_balance(&mut tracking_copy, sender, U512::from(100));
seed_evm_balance(&mut tracking_copy, outer, U512::zero());
seed_evm_code(&mut tracking_copy, inner, reverting_runtime());
let missing_key = Key::Evm(EvmAddr::ByteCode(read_code_hash(&mut tracking_copy, inner)));
tracking_copy.prune(missing_key);

// Write storage before loading the corrupt account to verify that the
// database error discards earlier execution as well as the value transfer.
let mut runtime = vec![opcode::PUSH1, 1, opcode::PUSH0, opcode::SSTORE];
match instruction {
opcode::CALL => runtime.extend_from_slice(&[
opcode::PUSH0, // return size
opcode::PUSH0, // return offset
opcode::PUSH0, // input size
opcode::PUSH0, // input offset
opcode::PUSH0, // value
]),
opcode::EXTCODECOPY => runtime.extend_from_slice(&[
opcode::PUSH1,
32, // size
opcode::PUSH0, // code offset
opcode::PUSH0, // memory offset
]),
_ => {}
}
runtime.push(opcode::PUSH20);
runtime.extend_from_slice(inner.as_bytes());
if instruction == opcode::CALL {
runtime.push(opcode::GAS);
}
runtime.extend_from_slice(&[instruction, opcode::STOP]);
seed_evm_code(&mut tracking_copy, outer, runtime);

let error = executor
.execute(
&data_access_layer,
&mut tracking_copy,
checked_call_request(sender, Some(outer), Vec::new(), CasperU256::one()),
)
.expect_err("missing nested bytecode must abort execution");

assert!(matches!(
error,
Error::Database(DbError::MissingBytecode { key }) if *key == missing_key
));
assert_eq!(
read_storage(&mut tracking_copy, outer, CasperU256::zero()),
None
);
assert_eq!(read_balance(&mut tracking_copy, sender), U512::from(100));
assert_eq!(read_balance(&mut tracking_copy, outer), U512::zero());
assert_eq!(read_evm_nonce(&mut tracking_copy, sender), 0);
}
}

#[test]
fn missing_bytecode_rejects_delegated_call() {
let executor = executor(EvmSpec::Prague);
let (mut tracking_copy, data_access_layer, _tempdir) = tracking_copy();
let authority = evm::Address::new([0xa1; 20]);
let delegate = evm::Address::new([0xa2; 20]);
seed_evm_code(&mut tracking_copy, authority, delegation_code(delegate));
seed_evm_code(&mut tracking_copy, delegate, reverting_runtime());
let missing_key = Key::Evm(EvmAddr::ByteCode(read_code_hash(
&mut tracking_copy,
delegate,
)));
tracking_copy.prune(missing_key);

let error = executor
.execute(
&data_access_layer,
&mut tracking_copy,
call_request(
evm::Address::ZERO,
Some(authority),
Vec::new(),
CasperU256::zero(),
),
)
.expect_err("missing delegated bytecode must abort execution");

assert!(matches!(
error,
Error::Database(DbError::MissingBytecode { key }) if *key == missing_key
));
}

#[test]
fn missing_bytecode_rejects_system_call() {
let executor = executor(EvmSpec::Prague);
let (mut tracking_copy, data_access_layer, _tempdir) = tracking_copy();
let target = evm::Address::new([0xa1; 20]);
seed_evm_code(&mut tracking_copy, target, reverting_runtime());
let missing_key = Key::Evm(EvmAddr::ByteCode(read_code_hash(
&mut tracking_copy,
target,
)));
tracking_copy.prune(missing_key);

let error = executor
.execute_system_call(
&data_access_layer,
&mut tracking_copy,
SystemCallRequest {
block: block(),
target,
input: Vec::new(),
},
)
.expect_err("missing system-call bytecode must abort execution");

assert!(matches!(
error,
Error::Database(DbError::MissingBytecode { key }) if *key == missing_key
));
}

#[test]
fn prague_bls12_g1_add_precompile_delegates_to_revm() {
let executor = executor(EvmSpec::Prague);
Expand Down
Loading