Skip to content

Commit

Permalink
Return mocked data, even if the contract does not exist (#653)
Browse files Browse the repository at this point in the history
<!-- Reference any GitHub issues resolved by this PR -->

Closes #645


## Checklist

<!-- Make sure all of these are complete -->

- [x] Linked relevant issue
- [x] Updated relevant documentation
- [x] Added relevant tests
- [x] Performed self-review of the code
- [x] Added changes to `CHANGELOG.md`

---------

Co-authored-by: Piotr Magiera <piotrmagiera150@gmail.com>
  • Loading branch information
Arcticae and piotmag769 authored Sep 13, 2023
1 parent 3a29c43 commit fde4822
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 43 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `deploy_at` cheatcode
- printing failures summary at the end of an execution
- filtering tests now uses an absolute module tree path — it is possible to filter tests by module names, etc.
- fixed mocking functions even if the contract does not exist

### Fixed

Expand Down
42 changes: 2 additions & 40 deletions crates/cheatnet/src/execution/cairo1_execution.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::collections::HashSet;

use super::syscalls::CheatableSyscallHandler;
use crate::state::CheatcodeState;
use blockifier::{
Expand All @@ -10,23 +8,21 @@ use blockifier::{
},
contract_class::{ContractClassV1, EntryPointV1},
entry_point::{
CallEntryPoint, CallExecution, CallInfo, EntryPointExecutionContext,
EntryPointExecutionResult, ExecutionResources, Retdata,
CallEntryPoint, CallInfo, EntryPointExecutionContext, EntryPointExecutionResult,
ExecutionResources,
},
errors::{EntryPointExecutionError, VirtualMachineExecutionError},
execution_utils::Args,
},
state::state_api::State,
};
use cairo_vm::vm::runners::cairo_runner::ExecutionResources as VmExecutionResources;
use cairo_vm::{
hint_processor::hint_processor_definition::HintProcessor,
vm::{
runners::cairo_runner::{CairoArg, CairoRunner},
vm_core::VirtualMachine,
},
};
use starknet_api::hash::StarkFelt;

// blockifier/src/execution/cairo1_execution.rs:48 (execute_entry_point_call)
pub fn execute_entry_point_call_cairo1(
Expand All @@ -37,25 +33,6 @@ pub fn execute_entry_point_call_cairo1(
resources: &mut ExecutionResources,
context: &mut EntryPointExecutionContext,
) -> EntryPointExecutionResult<CallInfo> {
// region: Modified blockifier code
if let Some(ret_data) = get_ret_data_by_call_entry_point(&call, cheatcode_state) {
return Ok(CallInfo {
call,
execution: CallExecution {
retdata: Retdata(ret_data.clone()),
events: vec![],
l2_to_l1_messages: vec![],
failed: false,
gas_consumed: 0,
},
vm_resources: VmExecutionResources::default(),
inner_calls: vec![],
storage_read_values: vec![],
accessed_storage_keys: HashSet::new(),
});
}
// endregion

let VmExecutionContext {
mut runner,
mut vm,
Expand Down Expand Up @@ -137,18 +114,3 @@ pub fn cheatable_run_entry_point(

Ok(())
}

fn get_ret_data_by_call_entry_point<'a>(
call: &CallEntryPoint,
cheatcode_state: &'a CheatcodeState,
) -> Option<&'a Vec<StarkFelt>> {
if let Some(contract_address) = call.code_address {
if let Some(contract_functions) = cheatcode_state.mocked_functions.get(&contract_address) {
let entrypoint_selector = call.entry_point_selector;

let ret_data = contract_functions.get(&entrypoint_selector);
return ret_data;
}
}
None
}
38 changes: 38 additions & 0 deletions crates/cheatnet/src/execution/entry_point.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use super::cairo1_execution::execute_entry_point_call_cairo1;
use crate::state::CheatcodeState;
use blockifier::execution::entry_point::{CallExecution, Retdata};
use blockifier::{
execution::{
contract_class::ContractClass,
Expand All @@ -12,12 +13,14 @@ use blockifier::{
},
state::state_api::State,
};
use cairo_vm::vm::runners::cairo_runner::ExecutionResources as VmExecutionResources;
use starknet_api::{
core::ClassHash,
deprecated_contract_class::EntryPointType,
hash::StarkFelt,
transaction::{Calldata, TransactionVersion},
};
use std::collections::HashSet;

// blockifier/src/execution/entry_point.rs:180 (CallEntryPoint::execute)
#[allow(clippy::module_name_repetitions)]
Expand All @@ -30,6 +33,9 @@ pub fn execute_call_entry_point(
) -> EntryPointExecutionResult<CallInfo> {
// region: Modified blockifier code
// We skip recursion depth validation here.
if let Some(ret_data) = get_ret_data_by_call_entry_point(entry_point, cheatcode_state) {
return Ok(mocked_call_info(entry_point.clone(), ret_data.clone()));
}
// endregion

// Validate contract is deployed.
Expand Down Expand Up @@ -136,3 +142,35 @@ pub fn execute_constructor_entry_point(
)
// endregion
}

fn get_ret_data_by_call_entry_point<'a>(
call: &CallEntryPoint,
cheatcode_state: &'a CheatcodeState,
) -> Option<&'a Vec<StarkFelt>> {
if let Some(contract_address) = call.code_address {
if let Some(contract_functions) = cheatcode_state.mocked_functions.get(&contract_address) {
let entrypoint_selector = call.entry_point_selector;

let ret_data = contract_functions.get(&entrypoint_selector);
return ret_data;
}
}
None
}

fn mocked_call_info(call: CallEntryPoint, ret_data: Vec<StarkFelt>) -> CallInfo {
CallInfo {
call,
execution: CallExecution {
retdata: Retdata(ret_data),
events: vec![],
l2_to_l1_messages: vec![],
failed: false,
gas_consumed: 0,
},
vm_resources: VmExecutionResources::default(),
inner_calls: vec![],
storage_read_values: vec![],
accessed_storage_keys: HashSet::new(),
}
}
21 changes: 21 additions & 0 deletions crates/cheatnet/tests/cheatcodes/mock_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::{
use cairo_felt::Felt252;
use cheatnet::rpc::call_contract;
use conversions::StarknetConversions;
use starknet_api::core::ContractAddress;

#[test]
fn mock_call_simple() {
Expand Down Expand Up @@ -358,3 +359,23 @@ fn mock_call_two_methods() {

assert_success!(output, ret_data);
}

#[test]
fn mock_call_nonexisting_contract() {
let mut state = create_cheatnet_state();

let selector = felt_selector_from_name("get_thing");
let ret_data = vec![Felt252::from(123)];

let contract_address = ContractAddress::from(218_u8);

state.start_mock_call(
contract_address,
&"get_thing".to_owned().to_felt252(),
&ret_data,
);

let output = call_contract(&contract_address, &selector, &[], &mut state).unwrap();

assert_success!(output, ret_data);
}
4 changes: 1 addition & 3 deletions docs/src/appendix/cheatcodes/start_mock_call.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
Mocks contract call to a `function_name` of a contract at the given address. A call to function `function_name` will
return data provided in `ret_data` argument.

If there is a contract deployed at the given address, mocked function won't be executed. Address with no contract can be
mocked as well.
Mock can be canceled with [`stop_mock_call`](./stop_mock_call.md).
An address with no contract can be mocked as well. Mock can be canceled with [`stop_mock_call`](./stop_mock_call.md).

- `contract_address` - target contract address
- `function_name` - name of the function in a contract at the `contract_address` that will be mocked
Expand Down

0 comments on commit fde4822

Please sign in to comment.