use crate::{Cheatcode, Cheatcodes, CheatsCtxt, Error, Result, Vm::*};
use alloy_primitives::{
address, hex,
map::{hash_map::Entry, HashMap},
Address, Bytes, LogData as RawLog, U256,
};
use alloy_sol_types::{SolError, SolValue};
use foundry_common::ContractsByArtifact;
use foundry_evm_core::decode::RevertDecoder;
use revm::interpreter::{
return_ok, InstructionResult, Interpreter, InterpreterAction, InterpreterResult,
};
use spec::Vm;
static DUMMY_CALL_OUTPUT: Bytes = Bytes::from_static(&[0u8; 8192]);
const DUMMY_CREATE_ADDRESS: Address = address!("0000000000000000000000000000000000000001");
pub type ExpectedCallTracker = HashMap<Address, HashMap<Bytes, (ExpectedCallData, u64)>>;
#[derive(Clone, Debug)]
pub struct ExpectedCallData {
pub value: Option<U256>,
pub gas: Option<u64>,
pub min_gas: Option<u64>,
pub count: u64,
pub call_type: ExpectedCallType,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ExpectedCallType {
NonCount,
Count,
}
#[derive(Clone, Debug)]
pub enum ExpectedRevertKind {
Default,
Cheatcode { pending_processing: bool },
}
#[derive(Clone, Debug)]
pub struct ExpectedRevert {
pub reason: Option<Vec<u8>>,
pub depth: u64,
pub kind: ExpectedRevertKind,
pub partial_match: bool,
pub reverter: Option<Address>,
pub reverted_by: Option<Address>,
}
#[derive(Clone, Debug)]
pub struct ExpectedEmit {
pub depth: u64,
pub log: Option<RawLog>,
pub checks: [bool; 5],
pub address: Option<Address>,
pub anonymous: bool,
pub found: bool,
}
impl Cheatcode for expectCall_0Call {
fn apply(&self, state: &mut Cheatcodes) -> Result {
let Self { callee, data } = self;
expect_call(state, callee, data, None, None, None, 1, ExpectedCallType::NonCount)
}
}
impl Cheatcode for expectCall_1Call {
fn apply(&self, state: &mut Cheatcodes) -> Result {
let Self { callee, data, count } = self;
expect_call(state, callee, data, None, None, None, *count, ExpectedCallType::Count)
}
}
impl Cheatcode for expectCall_2Call {
fn apply(&self, state: &mut Cheatcodes) -> Result {
let Self { callee, msgValue, data } = self;
expect_call(state, callee, data, Some(msgValue), None, None, 1, ExpectedCallType::NonCount)
}
}
impl Cheatcode for expectCall_3Call {
fn apply(&self, state: &mut Cheatcodes) -> Result {
let Self { callee, msgValue, data, count } = self;
expect_call(
state,
callee,
data,
Some(msgValue),
None,
None,
*count,
ExpectedCallType::Count,
)
}
}
impl Cheatcode for expectCall_4Call {
fn apply(&self, state: &mut Cheatcodes) -> Result {
let Self { callee, msgValue, gas, data } = self;
expect_call(
state,
callee,
data,
Some(msgValue),
Some(*gas),
None,
1,
ExpectedCallType::NonCount,
)
}
}
impl Cheatcode for expectCall_5Call {
fn apply(&self, state: &mut Cheatcodes) -> Result {
let Self { callee, msgValue, gas, data, count } = self;
expect_call(
state,
callee,
data,
Some(msgValue),
Some(*gas),
None,
*count,
ExpectedCallType::Count,
)
}
}
impl Cheatcode for expectCallMinGas_0Call {
fn apply(&self, state: &mut Cheatcodes) -> Result {
let Self { callee, msgValue, minGas, data } = self;
expect_call(
state,
callee,
data,
Some(msgValue),
None,
Some(*minGas),
1,
ExpectedCallType::NonCount,
)
}
}
impl Cheatcode for expectCallMinGas_1Call {
fn apply(&self, state: &mut Cheatcodes) -> Result {
let Self { callee, msgValue, minGas, data, count } = self;
expect_call(
state,
callee,
data,
Some(msgValue),
None,
Some(*minGas),
*count,
ExpectedCallType::Count,
)
}
}
impl Cheatcode for expectEmit_0Call {
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
let Self { checkTopic1, checkTopic2, checkTopic3, checkData } = *self;
expect_emit(
ccx.state,
ccx.ecx.journaled_state.depth(),
[true, checkTopic1, checkTopic2, checkTopic3, checkData],
None,
false,
)
}
}
impl Cheatcode for expectEmit_1Call {
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
let Self { checkTopic1, checkTopic2, checkTopic3, checkData, emitter } = *self;
expect_emit(
ccx.state,
ccx.ecx.journaled_state.depth(),
[true, checkTopic1, checkTopic2, checkTopic3, checkData],
Some(emitter),
false,
)
}
}
impl Cheatcode for expectEmit_2Call {
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
let Self {} = self;
expect_emit(ccx.state, ccx.ecx.journaled_state.depth(), [true; 5], None, false)
}
}
impl Cheatcode for expectEmit_3Call {
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
let Self { emitter } = *self;
expect_emit(ccx.state, ccx.ecx.journaled_state.depth(), [true; 5], Some(emitter), false)
}
}
impl Cheatcode for expectEmitAnonymous_0Call {
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
let Self { checkTopic0, checkTopic1, checkTopic2, checkTopic3, checkData } = *self;
expect_emit(
ccx.state,
ccx.ecx.journaled_state.depth(),
[checkTopic0, checkTopic1, checkTopic2, checkTopic3, checkData],
None,
true,
)
}
}
impl Cheatcode for expectEmitAnonymous_1Call {
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
let Self { checkTopic0, checkTopic1, checkTopic2, checkTopic3, checkData, emitter } = *self;
expect_emit(
ccx.state,
ccx.ecx.journaled_state.depth(),
[checkTopic0, checkTopic1, checkTopic2, checkTopic3, checkData],
Some(emitter),
true,
)
}
}
impl Cheatcode for expectEmitAnonymous_2Call {
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
let Self {} = self;
expect_emit(ccx.state, ccx.ecx.journaled_state.depth(), [true; 5], None, true)
}
}
impl Cheatcode for expectEmitAnonymous_3Call {
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
let Self { emitter } = *self;
expect_emit(ccx.state, ccx.ecx.journaled_state.depth(), [true; 5], Some(emitter), true)
}
}
impl Cheatcode for expectRevert_0Call {
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
let Self {} = self;
expect_revert(ccx.state, None, ccx.ecx.journaled_state.depth(), false, false, None)
}
}
impl Cheatcode for expectRevert_1Call {
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
let Self { revertData } = self;
expect_revert(
ccx.state,
Some(revertData.as_ref()),
ccx.ecx.journaled_state.depth(),
false,
false,
None,
)
}
}
impl Cheatcode for expectRevert_2Call {
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
let Self { revertData } = self;
expect_revert(
ccx.state,
Some(revertData),
ccx.ecx.journaled_state.depth(),
false,
false,
None,
)
}
}
impl Cheatcode for expectRevert_3Call {
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
let Self { reverter } = self;
expect_revert(
ccx.state,
None,
ccx.ecx.journaled_state.depth(),
false,
false,
Some(*reverter),
)
}
}
impl Cheatcode for expectRevert_4Call {
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
let Self { revertData, reverter } = self;
expect_revert(
ccx.state,
Some(revertData.as_ref()),
ccx.ecx.journaled_state.depth(),
false,
false,
Some(*reverter),
)
}
}
impl Cheatcode for expectRevert_5Call {
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
let Self { revertData, reverter } = self;
expect_revert(
ccx.state,
Some(revertData),
ccx.ecx.journaled_state.depth(),
false,
false,
Some(*reverter),
)
}
}
impl Cheatcode for expectPartialRevert_0Call {
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
let Self { revertData } = self;
expect_revert(
ccx.state,
Some(revertData.as_ref()),
ccx.ecx.journaled_state.depth(),
false,
true,
None,
)
}
}
impl Cheatcode for expectPartialRevert_1Call {
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
let Self { revertData, reverter } = self;
expect_revert(
ccx.state,
Some(revertData.as_ref()),
ccx.ecx.journaled_state.depth(),
false,
true,
Some(*reverter),
)
}
}
impl Cheatcode for _expectCheatcodeRevert_0Call {
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
expect_revert(ccx.state, None, ccx.ecx.journaled_state.depth(), true, false, None)
}
}
impl Cheatcode for _expectCheatcodeRevert_1Call {
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
let Self { revertData } = self;
expect_revert(
ccx.state,
Some(revertData.as_ref()),
ccx.ecx.journaled_state.depth(),
true,
false,
None,
)
}
}
impl Cheatcode for _expectCheatcodeRevert_2Call {
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
let Self { revertData } = self;
expect_revert(
ccx.state,
Some(revertData),
ccx.ecx.journaled_state.depth(),
true,
false,
None,
)
}
}
impl Cheatcode for expectSafeMemoryCall {
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
let Self { min, max } = *self;
expect_safe_memory(ccx.state, min, max, ccx.ecx.journaled_state.depth())
}
}
impl Cheatcode for stopExpectSafeMemoryCall {
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
let Self {} = self;
ccx.state.allowed_mem_writes.remove(&ccx.ecx.journaled_state.depth());
Ok(Default::default())
}
}
impl Cheatcode for expectSafeMemoryCallCall {
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
let Self { min, max } = *self;
expect_safe_memory(ccx.state, min, max, ccx.ecx.journaled_state.depth() + 1)
}
}
#[allow(clippy::too_many_arguments)] fn expect_call(
state: &mut Cheatcodes,
target: &Address,
calldata: &Bytes,
value: Option<&U256>,
mut gas: Option<u64>,
mut min_gas: Option<u64>,
count: u64,
call_type: ExpectedCallType,
) -> Result {
let expecteds = state.expected_calls.entry(*target).or_default();
if let Some(val) = value {
if *val > U256::ZERO {
let positive_value_cost_stipend = 2300;
if let Some(gas) = &mut gas {
*gas += positive_value_cost_stipend;
}
if let Some(min_gas) = &mut min_gas {
*min_gas += positive_value_cost_stipend;
}
}
}
match call_type {
ExpectedCallType::Count => {
ensure!(
!expecteds.contains_key(calldata),
"counted expected calls can only bet set once"
);
expecteds.insert(
calldata.clone(),
(ExpectedCallData { value: value.copied(), gas, min_gas, count, call_type }, 0),
);
}
ExpectedCallType::NonCount => {
match expecteds.entry(calldata.clone()) {
Entry::Occupied(mut entry) => {
let (expected, _) = entry.get_mut();
ensure!(
expected.call_type == ExpectedCallType::NonCount,
"cannot overwrite a counted expectCall with a non-counted expectCall"
);
expected.count += 1;
}
Entry::Vacant(entry) => {
entry.insert((
ExpectedCallData { value: value.copied(), gas, min_gas, count, call_type },
0,
));
}
}
}
}
Ok(Default::default())
}
fn expect_emit(
state: &mut Cheatcodes,
depth: u64,
checks: [bool; 5],
address: Option<Address>,
anonymous: bool,
) -> Result {
let expected_emit = ExpectedEmit { depth, checks, address, found: false, log: None, anonymous };
if let Some(found_emit_pos) = state.expected_emits.iter().position(|emit| emit.found) {
state.expected_emits.insert(found_emit_pos, expected_emit);
} else {
state.expected_emits.push_back(expected_emit);
}
Ok(Default::default())
}
pub(crate) fn handle_expect_emit(
state: &mut Cheatcodes,
log: &alloy_primitives::Log,
interpreter: &mut Interpreter,
) {
if state.expected_emits.iter().all(|expected| expected.found) {
return
}
let should_fill_logs = state.expected_emits.iter().any(|expected| expected.log.is_none());
let index_to_fill_or_check = if should_fill_logs {
state
.expected_emits
.iter()
.position(|emit| emit.found)
.unwrap_or(state.expected_emits.len())
.saturating_sub(1)
} else {
0
};
let mut event_to_fill_or_check = state
.expected_emits
.remove(index_to_fill_or_check)
.expect("we should have an emit to fill or check");
let Some(expected) = &event_to_fill_or_check.log else {
if event_to_fill_or_check.anonymous || !log.topics().is_empty() {
event_to_fill_or_check.log = Some(log.data.clone());
state.expected_emits.insert(index_to_fill_or_check, event_to_fill_or_check);
} else {
interpreter.instruction_result = InstructionResult::Revert;
interpreter.next_action = InterpreterAction::Return {
result: InterpreterResult {
output: Error::encode("use vm.expectEmitAnonymous to match anonymous events"),
gas: interpreter.gas,
result: InstructionResult::Revert,
},
};
}
return
};
event_to_fill_or_check.found = || -> bool {
if expected.topics().len() != log.topics().len() {
return false
}
if !log
.topics()
.iter()
.enumerate()
.filter(|(i, _)| event_to_fill_or_check.checks[*i])
.all(|(i, topic)| topic == &expected.topics()[i])
{
return false
}
if event_to_fill_or_check.address.is_some_and(|addr| addr != log.address) {
return false;
}
if event_to_fill_or_check.checks[4] && expected.data.as_ref() != log.data.data.as_ref() {
return false
}
true
}();
if event_to_fill_or_check.found {
state.expected_emits.push_back(event_to_fill_or_check);
} else {
state.expected_emits.push_front(event_to_fill_or_check);
}
}
fn expect_revert(
state: &mut Cheatcodes,
reason: Option<&[u8]>,
depth: u64,
cheatcode: bool,
partial_match: bool,
reverter: Option<Address>,
) -> Result {
ensure!(
state.expected_revert.is_none(),
"you must call another function prior to expecting a second revert"
);
state.expected_revert = Some(ExpectedRevert {
reason: reason.map(<[_]>::to_vec),
depth,
kind: if cheatcode {
ExpectedRevertKind::Cheatcode { pending_processing: true }
} else {
ExpectedRevertKind::Default
},
partial_match,
reverter,
reverted_by: None,
});
Ok(Default::default())
}
pub(crate) fn handle_expect_revert(
is_cheatcode: bool,
is_create: bool,
expected_revert: &ExpectedRevert,
status: InstructionResult,
retdata: Bytes,
known_contracts: &Option<ContractsByArtifact>,
) -> Result<(Option<Address>, Bytes)> {
let success_return = || {
if is_create {
(Some(DUMMY_CREATE_ADDRESS), Bytes::new())
} else {
(None, DUMMY_CALL_OUTPUT.clone())
}
};
ensure!(!matches!(status, return_ok!()), "next call did not revert as expected");
if let (Some(expected_reverter), Some(actual_reverter)) =
(expected_revert.reverter, expected_revert.reverted_by)
{
if expected_reverter != actual_reverter {
return Err(fmt_err!(
"Reverter != expected reverter: {} != {}",
actual_reverter,
expected_reverter
));
}
}
let expected_reason = expected_revert.reason.as_deref();
let Some(expected_reason) = expected_reason else {
return Ok(success_return());
};
if !expected_reason.is_empty() && retdata.is_empty() {
bail!("call reverted as expected, but without data");
}
let mut actual_revert: Vec<u8> = retdata.into();
if expected_revert.partial_match && actual_revert.get(..4) == expected_reason.get(..4) {
return Ok(success_return())
}
if matches!(
actual_revert.get(..4).map(|s| s.try_into().unwrap()),
Some(Vm::CheatcodeError::SELECTOR | alloy_sol_types::Revert::SELECTOR)
) {
if let Ok(decoded) = Vec::<u8>::abi_decode(&actual_revert[4..], false) {
actual_revert = decoded;
}
}
if actual_revert == expected_reason ||
(is_cheatcode && memchr::memmem::find(&actual_revert, expected_reason).is_some())
{
Ok(success_return())
} else {
let (actual, expected) = if let Some(contracts) = known_contracts {
let decoder = RevertDecoder::new().with_abis(contracts.iter().map(|(_, c)| &c.abi));
(
&decoder.decode(actual_revert.as_slice(), Some(status)),
&decoder.decode(expected_reason, Some(status)),
)
} else {
let stringify = |data: &[u8]| {
if let Ok(s) = String::abi_decode(data, true) {
return s;
}
if data.is_ascii() {
return std::str::from_utf8(data).unwrap().to_owned();
}
hex::encode_prefixed(data)
};
(&stringify(&actual_revert), &stringify(expected_reason))
};
Err(fmt_err!("Error != expected error: {} != {}", actual, expected,))
}
}
fn expect_safe_memory(state: &mut Cheatcodes, start: u64, end: u64, depth: u64) -> Result {
ensure!(start < end, "memory range start ({start}) is greater than end ({end})");
#[allow(clippy::single_range_in_vec_init)] let offsets = state.allowed_mem_writes.entry(depth).or_insert_with(|| vec![0..0x60]);
offsets.push(start..end);
Ok(Default::default())
}