Skip to content

feat: mock_call with dynamic return data #2904

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

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0ff1663
feat(snforge_std): add "dynamic" return value for mock_call
ptisserand Jan 31, 2025
f21e510
feat(cheatnet): add "dynamic" return value fir mock_call
ptisserand Jan 31, 2025
3c37814
forge: update test to support new mock_call cheatcode
ptisserand Jan 31, 2025
d1921ea
fix mock_call test
ptisserand Jan 31, 2025
9594b5a
cheatnet: fixup clippy
ptisserand Jan 31, 2025
c097e3c
snforge_std: revert breaking changes on `mock_call`
ptisserand Feb 3, 2025
e70b4b1
fix(cheatnet): default to any calldata entry when specific CheatSpan:…
ptisserand Feb 3, 2025
02a7dcd
docs: add `mock_call_when` cheatcode
ptisserand Feb 4, 2025
3498701
use `calldata` instead of `call_data`
ptisserand Feb 11, 2025
f4bc282
cheatnet: add MockCalldata enum
ptisserand Feb 18, 2025
e15e00f
Merge remote-tracking branch 'upstream/master'
ptisserand Apr 30, 2025
90cff00
fix: add missing argument for run_test_case
ptisserand May 2, 2025
2cd43a4
scarb fmt
ptisserand May 2, 2025
e1293a8
Merge remote-tracking branch 'upstream/master' into feat/dynamic-mock…
ptisserand May 17, 2025
2c5ec93
remove workaround for #2927
ptisserand May 17, 2025
3aeb496
add test cases with interleaved start/stop mock_call and mock_call_when
ptisserand Jul 15, 2025
1a20bbd
Merge remote-tracking branch 'upstream/master' into feat/dynamic-mock…
ptisserand Jul 15, 2025
f235bd7
cargo fmt
ptisserand Jul 15, 2025
da9bc28
Merge remote-tracking branch 'upstream/master' into master-merged
ptisserand Aug 5, 2025
3ae2a28
add missing use for cheatcodes
ptisserand Aug 5, 2025
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Rust is no longer required to use `snforge` if using Scarb >= 2.10.0 on supported platforms - precompiled `snforge_scarb_plugin` plugin binaries are now published to [package registry](https://scarbs.xyz) for new versions.
- Added a suggestion for using the `--max-n-steps` flag when the Cairo VM returns the error: `Could not reach the end of the program. RunResources has no remaining steps`.
- `mock_call_when`, `start_mock_call_when`, `stop_mock_call_when` cheatcodes.

#### Fixed

Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/cheatnet/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ bimap.workspace = true
camino.workspace = true
starknet_api.workspace = true
starknet-types-core.workspace = true
starknet-crypto.workspace = true
cairo-lang-casm.workspace = true
cairo-lang-runner.workspace = true
cairo-lang-utils.workspace = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::cell::RefCell;
use super::cairo1_execution::execute_entry_point_call_cairo1;
use crate::runtime_extensions::call_to_blockifier_runtime_extension::execution::deprecated::cairo0_execution::execute_entry_point_call_cairo0;
use crate::runtime_extensions::call_to_blockifier_runtime_extension::CheatnetState;
use crate::state::{CallTrace, CallTraceNode, CheatStatus, EncounteredError};
use crate::state::{CallTrace, CallTraceNode, CheatSpan, CheatStatus, EncounteredError};
use blockifier::execution::call_info::{CallExecution, Retdata};
use blockifier::{
execution::{
Expand All @@ -18,11 +18,13 @@ use blockifier::{
state::state_api::State,
};
use cairo_vm::vm::runners::cairo_runner::{CairoRunner, ExecutionResources};
use num_traits::Zero;
use starknet_api::{
core::ClassHash,
deprecated_contract_class::EntryPointType,
transaction::{Calldata, TransactionVersion},
};
use starknet_crypto::poseidon_hash_many;
use std::collections::HashSet;
use std::rc::Rc;
use blockifier::execution::deprecated_syscalls::hint_processor::SyscallCounter;
Expand Down Expand Up @@ -268,11 +270,25 @@ fn get_mocked_function_cheat_status<'a>(
if call.call_type == CallType::Delegate {
return None;
}

cheatnet_state
match cheatnet_state
.mocked_functions
.get_mut(&call.storage_address)
.and_then(|contract_functions| contract_functions.get_mut(&call.entry_point_selector))
{
None => None,
Some(contract_functions) => {
let calldata_hash = poseidon_hash_many(call.calldata.0.iter());
let key = (call.entry_point_selector, calldata_hash);
let key_zero = (call.entry_point_selector, Felt::zero());

match contract_functions.get(&key) {
Some(CheatStatus::Cheated(_, CheatSpan::TargetCalls(0))) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After calling get_mocked_function_cheat_status, we call .decrement_cheat_span on the returned value. It changes the CheatSpan to Uncheated after it decreases to 0.

I don't think there can be a case where TargetCalls is equal to 0 unless explicitly set to this value somewhere.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this logic match arm was added to handle the case where entrypoint for specific calldata is no longer cheated but for any calldata still is, but it should already be handled by _ case anyway.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turns out technically it could be set to 0 but it shouldn't be allowed.

Created #2927

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure to understand what should I do here: remove my workaround?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't have to match for the case where cheat span is CheatSpan::TargetCalls(0). Technically, user could have somehow created it like this (as explained in #2927), but during normal operation it will either be CheatSpan::TargetCalls(some value > 0) or CheatStatus::Uncheated.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See this

pub fn decrement_cheat_span(&mut self) {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Workaround removed

contract_functions.get_mut(&key_zero)
}
Some(CheatStatus::Cheated(_, _)) => contract_functions.get_mut(&key),
_ => contract_functions.get_mut(&key_zero),
}
}
}
}

fn mocked_call_info(call: CallEntryPoint, ret_data: Vec<Felt>) -> CallInfo {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use crate::state::{CheatSpan, CheatStatus};
use crate::state::{CheatSpan, CheatStatus, MockCalldata};
use crate::CheatnetState;
use num_traits::Zero;
use starknet_api::core::{ContractAddress, EntryPointSelector};
use starknet_crypto::poseidon_hash_many;
use starknet_types_core::felt::Felt;
use std::collections::hash_map::Entry;

Expand All @@ -9,26 +11,30 @@ impl CheatnetState {
&mut self,
contract_address: ContractAddress,
function_selector: EntryPointSelector,
calldata: MockCalldata,
ret_data: &[Felt],
span: CheatSpan,
) {
let contract_mocked_functions = self.mocked_functions.entry(contract_address).or_default();

contract_mocked_functions.insert(
function_selector,
CheatStatus::Cheated(ret_data.to_vec(), span),
);
let calldata_hash = match calldata {
MockCalldata::Values(data) => poseidon_hash_many(data.iter()),
MockCalldata::Any => Felt::zero(),
};
let key = (function_selector, calldata_hash);
contract_mocked_functions.insert(key, CheatStatus::Cheated(ret_data.to_vec(), span));
}

pub fn start_mock_call(
&mut self,
contract_address: ContractAddress,
function_selector: EntryPointSelector,
calldata: MockCalldata,
ret_data: &[Felt],
) {
self.mock_call(
contract_address,
function_selector,
calldata,
ret_data,
CheatSpan::Indefinite,
);
Expand All @@ -38,10 +44,15 @@ impl CheatnetState {
&mut self,
contract_address: ContractAddress,
function_selector: EntryPointSelector,
calldata: MockCalldata,
) {
if let Entry::Occupied(mut e) = self.mocked_functions.entry(contract_address) {
let contract_mocked_functions = e.get_mut();
contract_mocked_functions.remove(&function_selector);
let calldata_hash = match calldata {
MockCalldata::Values(data) => poseidon_hash_many(data.iter()),
MockCalldata::Any => Felt::zero(),
};
contract_mocked_functions.remove(&(function_selector, calldata_hash));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,26 +87,31 @@ impl<'a> ExtensionLogic for ForgeExtension<'a> {
"mock_call" => {
let contract_address = input_reader.read()?;
let function_selector = input_reader.read()?;
let calldata = input_reader.read()?;
let span = input_reader.read()?;

let ret_data: Vec<_> = input_reader.read()?;

extended_runtime
.extended_runtime
.extension
.cheatnet_state
.mock_call(contract_address, function_selector, &ret_data, span);
.mock_call(
contract_address,
function_selector,
calldata,
&ret_data,
span,
);
Ok(CheatcodeHandlingResult::from_serializable(()))
}
"stop_mock_call" => {
let contract_address = input_reader.read()?;
let function_selector = input_reader.read()?;

let calldata = input_reader.read()?;
extended_runtime
.extended_runtime
.extension
.cheatnet_state
.stop_mock_call(contract_address, function_selector);
.stop_mock_call(contract_address, function_selector, calldata);
Ok(CheatcodeHandlingResult::from_serializable(()))
}
"replace_bytecode" => {
Expand Down
9 changes: 8 additions & 1 deletion crates/cheatnet/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ pub enum CheatSpan {
TargetCalls(usize),
}

#[derive(CairoDeserialize, Clone, Debug, PartialEq, Eq)]
pub enum MockCalldata {
Any,
Values(Vec<Felt>),
}

#[derive(Debug)]
pub struct ExtendedStateReader {
pub dict_state_reader: DictStateReader,
Expand Down Expand Up @@ -326,12 +332,13 @@ pub struct EncounteredError {
pub class_hash: ClassHash,
}

type MockedFunctionKey = (EntryPointSelector, Felt);
pub struct CheatnetState {
pub cheated_execution_info_contracts: HashMap<ContractAddress, ExecutionInfoMock>,
pub global_cheated_execution_info: ExecutionInfoMock,

pub mocked_functions:
HashMap<ContractAddress, HashMap<EntryPointSelector, CheatStatus<Vec<Felt>>>>,
HashMap<ContractAddress, HashMap<MockedFunctionKey, CheatStatus<Vec<Felt>>>>,
pub replaced_bytecode_contracts: HashMap<ContractAddress, ClassHash>,
pub detected_events: Vec<Event>,
pub detected_messages_to_l1: Vec<MessageToL1>,
Expand Down
41 changes: 33 additions & 8 deletions crates/cheatnet/tests/cheatcodes/mock_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{
common::{deploy_contract, get_contracts},
};
use cheatnet::runtime_extensions::forge_runtime_extension::cheatcodes::declare::declare;
use cheatnet::state::{CheatSpan, CheatnetState};
use cheatnet::state::{CheatSpan, CheatnetState, MockCalldata};
use conversions::IntoConv;
use starknet::core::utils::get_selector_from_name;
use starknet_api::core::ContractAddress;
Expand Down Expand Up @@ -38,15 +38,19 @@ impl MockCallTrait for TestEnvironment {
self.cheatnet_state.mock_call(
*contract_address,
function_selector.into_(),
MockCalldata::Any,
&ret_data,
span,
);
}

fn stop_mock_call(&mut self, contract_address: &ContractAddress, function_name: &str) {
let function_selector = get_selector_from_name(function_name).unwrap();
self.cheatnet_state
.stop_mock_call(*contract_address, function_selector.into_());
self.cheatnet_state.stop_mock_call(
*contract_address,
function_selector.into_(),
MockCalldata::Any,
);
}
}

Expand All @@ -68,6 +72,7 @@ fn mock_call_simple() {
cheatnet_state.start_mock_call(
contract_address,
felt_selector_from_name("get_thing"),
MockCalldata::Any,
&ret_data,
);

Expand Down Expand Up @@ -100,6 +105,7 @@ fn mock_call_stop() {
cheatnet_state.start_mock_call(
contract_address,
felt_selector_from_name("get_thing"),
MockCalldata::Any,
&ret_data,
);

Expand All @@ -113,7 +119,11 @@ fn mock_call_stop() {

assert_success(output, &ret_data);

cheatnet_state.stop_mock_call(contract_address, felt_selector_from_name("get_thing"));
cheatnet_state.stop_mock_call(
contract_address,
felt_selector_from_name("get_thing"),
MockCalldata::Any,
);

let output = call_contract(
&mut cached_state,
Expand All @@ -140,7 +150,11 @@ fn mock_call_stop_no_start() {

let selector = felt_selector_from_name("get_thing");

cheatnet_state.stop_mock_call(contract_address, felt_selector_from_name("get_thing"));
cheatnet_state.stop_mock_call(
contract_address,
felt_selector_from_name("get_thing"),
MockCalldata::Any,
);

let output = call_contract(
&mut cached_state,
Expand Down Expand Up @@ -168,10 +182,10 @@ fn mock_call_double() {
let selector = felt_selector_from_name("get_thing");

let ret_data = [Felt::from(123)];
cheatnet_state.start_mock_call(contract_address, selector, &ret_data);
cheatnet_state.start_mock_call(contract_address, selector, MockCalldata::Any, &ret_data);

let ret_data = [Felt::from(999)];
cheatnet_state.start_mock_call(contract_address, selector, &ret_data);
cheatnet_state.start_mock_call(contract_address, selector, MockCalldata::Any, &ret_data);

let output = call_contract(
&mut cached_state,
Expand All @@ -183,7 +197,7 @@ fn mock_call_double() {

assert_success(output, &ret_data);

cheatnet_state.stop_mock_call(contract_address, selector);
cheatnet_state.stop_mock_call(contract_address, selector, MockCalldata::Any);

let output = call_contract(
&mut cached_state,
Expand Down Expand Up @@ -214,6 +228,7 @@ fn mock_call_double_call() {
cheatnet_state.start_mock_call(
contract_address,
felt_selector_from_name("get_thing"),
MockCalldata::Any,
&ret_data,
);

Expand Down Expand Up @@ -255,6 +270,7 @@ fn mock_call_proxy() {
cheatnet_state.start_mock_call(
contract_address,
felt_selector_from_name("get_thing"),
MockCalldata::Any,
&ret_data,
);

Expand Down Expand Up @@ -303,6 +319,7 @@ fn mock_call_proxy_with_other_syscall() {
cheatnet_state.start_mock_call(
contract_address,
felt_selector_from_name("get_thing"),
MockCalldata::Any,
&ret_data,
);

Expand Down Expand Up @@ -352,6 +369,7 @@ fn mock_call_inner_call_no_effect() {
cheatnet_state.start_mock_call(
contract_address,
felt_selector_from_name("get_thing"),
MockCalldata::Any,
&ret_data,
);

Expand Down Expand Up @@ -407,6 +425,7 @@ fn mock_call_library_call_no_effect() {
cheatnet_state.start_mock_call(
contract_address,
felt_selector_from_name("get_constant_thing"),
MockCalldata::Any,
&ret_data,
);

Expand Down Expand Up @@ -440,6 +459,7 @@ fn mock_call_before_deployment() {
cheatnet_state.start_mock_call(
precalculated_address,
felt_selector_from_name("get_thing"),
MockCalldata::Any,
&ret_data,
);

Expand Down Expand Up @@ -482,6 +502,7 @@ fn mock_call_not_implemented() {
cheatnet_state.start_mock_call(
contract_address,
felt_selector_from_name("get_thing_not_implemented"),
MockCalldata::Any,
&ret_data,
);

Expand Down Expand Up @@ -512,6 +533,7 @@ fn mock_call_in_constructor() {
cheatnet_state.start_mock_call(
balance_contract_address,
felt_selector_from_name("get_balance"),
MockCalldata::Any,
&ret_data,
);

Expand Down Expand Up @@ -559,12 +581,14 @@ fn mock_call_two_methods() {
cheatnet_state.start_mock_call(
contract_address,
felt_selector_from_name("get_thing"),
MockCalldata::Any,
&ret_data,
);

cheatnet_state.start_mock_call(
contract_address,
felt_selector_from_name("get_constant_thing"),
MockCalldata::Any,
&ret_data,
);

Expand Down Expand Up @@ -602,6 +626,7 @@ fn mock_call_nonexisting_contract() {
cheatnet_state.start_mock_call(
contract_address,
felt_selector_from_name("get_thing"),
MockCalldata::Any,
&ret_data,
);

Expand Down
Loading
Loading