Skip to content

Commit 1981e86

Browse files
abulenokwar-inMaksymilianDemitraszekpiotmag769
authored
Add mock_call (#385)
<!-- Reference any GitHub issues resolved by this PR --> Closes # ## Introduced changes <!-- A brief description of the changes --> - Add `start_mock_call` cheatcode (with tests and docs) - Add `stop_mock_call` cheatcode (with tests and docs) ## Breaking changes <!-- List of all breaking changes, if applicable --> ## 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: Marcin Warchoł <[email protected]> Co-authored-by: Maksymilian Demitraszek <[email protected]> Co-authored-by: Piotr Magiera <[email protected]>
1 parent 1a6bddc commit 1981e86

File tree

18 files changed

+1172
-3
lines changed

18 files changed

+1172
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1717
- Print support for basic numeric data types
1818
- Functions `parse_txt` and `TxtParser<T>::deserialize_txt` to load data from plain text files and serialize it
1919
- `get_class_hash` cheatcode
20+
- `mock_call` cheatcode
2021

2122
#### Changed
2223

crates/cheatnet/src/cheatcodes.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use thiserror::Error;
1111
pub mod declare;
1212
pub mod deploy;
1313
pub mod get_class_hash;
14+
pub mod mock_call;
1415
pub mod prank;
1516
pub mod roll;
1617
pub mod warp;
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
use crate::CheatnetState;
2+
use starknet_api::core::{ContractAddress, EntryPointSelector};
3+
use starknet_api::hash::StarkFelt;
4+
use std::collections::HashMap;
5+
6+
impl CheatnetState {
7+
pub fn start_mock_call(
8+
&mut self,
9+
contract_address: ContractAddress,
10+
function_name: EntryPointSelector,
11+
ret_data: Vec<StarkFelt>,
12+
) {
13+
let contract_mocked_functions = self
14+
.cheatcode_state
15+
.mocked_functions
16+
.entry(contract_address)
17+
.or_insert_with(HashMap::new);
18+
19+
contract_mocked_functions.insert(function_name, ret_data);
20+
}
21+
22+
pub fn stop_mock_call(
23+
&mut self,
24+
contract_address: ContractAddress,
25+
function_name: EntryPointSelector,
26+
) {
27+
if let std::collections::hash_map::Entry::Occupied(mut e) = self
28+
.cheatcode_state
29+
.mocked_functions
30+
.entry(contract_address)
31+
{
32+
let contract_mocked_functions = e.get_mut();
33+
contract_mocked_functions.remove(&function_name);
34+
}
35+
}
36+
}

crates/cheatnet/src/rpc.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@ use cairo_vm::{
1212
vm_core::VirtualMachine,
1313
},
1414
};
15+
use std::collections::HashSet;
1516
use std::{any::Any, collections::HashMap, sync::Arc};
1617

1718
use crate::{
1819
constants::{build_block_context, build_transaction_context, TEST_ACCOUNT_CONTRACT_ADDRESS},
1920
CheatnetState,
2021
};
22+
use blockifier::execution::entry_point::CallExecution;
23+
use blockifier::execution::entry_point::Retdata;
2124
use blockifier::{
2225
abi::constants,
2326
execution::{
@@ -56,6 +59,7 @@ use cairo_lang_casm::{
5659
hints::{Hint, StarknetHint},
5760
operand::{BinOpOperand, DerefOrImmediate, Operation, Register, ResOperand},
5861
};
62+
use cairo_vm::vm::runners::cairo_runner::ExecutionResources as VmExecutionResources;
5963
use starknet_api::{
6064
core::{ClassHash, ContractAddress, EntryPointSelector, PatriciaKey},
6165
deprecated_contract_class::EntryPointType,
@@ -693,6 +697,21 @@ pub fn execute_inner_call(
693697
Ok(retdata_segment)
694698
}
695699

700+
fn get_ret_data_by_call_entry_point<'a>(
701+
call: &CallEntryPoint,
702+
cheatcode_state: &'a CheatcodeState,
703+
) -> Option<&'a Vec<StarkFelt>> {
704+
if let Some(contract_address) = call.code_address {
705+
if let Some(contract_functions) = cheatcode_state.mocked_functions.get(&contract_address) {
706+
let entrypoint_selector = call.entry_point_selector;
707+
708+
let ret_data = contract_functions.get(&entrypoint_selector);
709+
return ret_data;
710+
}
711+
}
712+
None
713+
}
714+
696715
/// Executes a specific call to a contract entry point and returns its output.
697716
fn execute_entry_point_call_cairo1(
698717
call: CallEntryPoint,
@@ -702,6 +721,23 @@ fn execute_entry_point_call_cairo1(
702721
resources: &mut ExecutionResources,
703722
context: &mut EntryPointExecutionContext,
704723
) -> EntryPointExecutionResult<CallInfo> {
724+
if let Some(ret_data) = get_ret_data_by_call_entry_point(&call, cheatcode_state) {
725+
return Ok(CallInfo {
726+
call,
727+
execution: CallExecution {
728+
retdata: Retdata(ret_data.clone()),
729+
events: vec![],
730+
l2_to_l1_messages: vec![],
731+
failed: false,
732+
gas_consumed: 0,
733+
},
734+
vm_resources: VmExecutionResources::default(),
735+
inner_calls: vec![],
736+
storage_read_values: vec![],
737+
accessed_storage_keys: HashSet::new(),
738+
});
739+
}
740+
705741
let VmExecutionContext {
706742
mut runner,
707743
mut vm,

crates/cheatnet/src/state.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use blockifier::{
77
},
88
};
99
use cairo_felt::Felt252;
10+
use starknet_api::core::EntryPointSelector;
1011
use starknet_api::{
1112
core::{ClassHash, CompiledClassHash, ContractAddress, Nonce},
1213
hash::StarkFelt,
@@ -86,6 +87,7 @@ pub struct CheatcodeState {
8687
pub rolled_contracts: HashMap<ContractAddress, Felt252>,
8788
pub pranked_contracts: HashMap<ContractAddress, ContractAddress>,
8889
pub warped_contracts: HashMap<ContractAddress, Felt252>,
90+
pub mocked_functions: HashMap<ContractAddress, HashMap<EntryPointSelector, Vec<StarkFelt>>>,
8991
}
9092

9193
impl CheatcodeState {
@@ -95,6 +97,7 @@ impl CheatcodeState {
9597
rolled_contracts: HashMap::new(),
9698
pranked_contracts: HashMap::new(),
9799
warped_contracts: HashMap::new(),
100+
mocked_functions: HashMap::new(),
98101
}
99102
}
100103
}

crates/forge/src/cheatcodes_hint_processor.rs

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ use std::path::PathBuf;
44

55
use crate::scarb::StarknetContractArtifacts;
66
use anyhow::{anyhow, Result};
7+
use blockifier::abi::abi_utils::selector_from_name;
78
use blockifier::execution::execution_utils::{felt_to_stark_felt, stark_felt_to_felt};
8-
99
use cairo_felt::Felt252;
1010
use cairo_vm::hint_processor::hint_processor_definition::HintProcessorLogic;
1111
use cairo_vm::hint_processor::hint_processor_definition::HintReference;
@@ -226,7 +226,42 @@ impl CairoHintProcessor<'_> {
226226
self.cheatnet_state.stop_prank(contract_address);
227227
Ok(())
228228
}
229-
"mock_call" => todo!(),
229+
"start_mock_call" => {
230+
let contract_address = ContractAddress(PatriciaKey::try_from(StarkFelt::new(
231+
inputs[0].clone().to_be_bytes(),
232+
)?)?);
233+
let function_name = inputs[1].clone();
234+
let function_name = as_cairo_short_string(&function_name).unwrap_or_else(|| {
235+
panic!("Failed to convert {function_name:?} to Cairo short str")
236+
});
237+
let function_name = selector_from_name(function_name.as_str());
238+
239+
let ret_data_length = inputs[2]
240+
.to_usize()
241+
.expect("Missing ret_data len in inputs");
242+
let mut ret_data = vec![];
243+
for felt in inputs.iter().skip(3).take(ret_data_length) {
244+
ret_data.push(felt_to_stark_felt(&felt.clone()));
245+
}
246+
247+
self.cheatnet_state
248+
.start_mock_call(contract_address, function_name, ret_data);
249+
Ok(())
250+
}
251+
"stop_mock_call" => {
252+
let contract_address = ContractAddress(PatriciaKey::try_from(StarkFelt::new(
253+
inputs[0].clone().to_be_bytes(),
254+
)?)?);
255+
let function_name = inputs[1].clone();
256+
let function_name = as_cairo_short_string(&function_name).unwrap_or_else(|| {
257+
panic!("Failed to convert {function_name:?} to Cairo short str")
258+
});
259+
let function_name = selector_from_name(function_name.as_str());
260+
261+
self.cheatnet_state
262+
.stop_mock_call(contract_address, function_name);
263+
Ok(())
264+
}
230265
"declare" => {
231266
let contract_name = inputs[0].clone();
232267

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use starknet::ContractAddress;
2+
3+
#[starknet::interface]
4+
trait IConstructorMockChecker<TContractState> {
5+
fn get_stored_thing(ref self: TContractState) -> felt252;
6+
fn get_constant_thing(ref self: TContractState) -> felt252;
7+
}
8+
9+
#[starknet::contract]
10+
mod ConstructorMockChecker {
11+
use super::IConstructorMockChecker;
12+
13+
#[storage]
14+
struct Storage {
15+
stored_thing: felt252,
16+
}
17+
18+
#[constructor]
19+
fn constructor(ref self: ContractState) {
20+
let const_thing = self.get_constant_thing();
21+
self.stored_thing.write(const_thing);
22+
}
23+
24+
#[external(v0)]
25+
impl IConstructorMockCheckerImpl of super::IConstructorMockChecker<ContractState> {
26+
fn get_constant_thing(ref self: ContractState) -> felt252 {
27+
13
28+
}
29+
30+
fn get_stored_thing(ref self: ContractState) -> felt252 {
31+
self.stored_thing.read()
32+
}
33+
}
34+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#[derive(Serde, Drop)]
2+
struct StructThing {
3+
item_one: felt252,
4+
item_two: felt252,
5+
}
6+
7+
#[starknet::interface]
8+
trait IMockChecker<TContractState> {
9+
fn get_thing(ref self: TContractState) -> felt252;
10+
fn get_thing_wrapper(ref self: TContractState) -> felt252;
11+
fn get_constant_thing(ref self: TContractState) -> felt252;
12+
fn get_struct_thing(ref self: TContractState) -> StructThing;
13+
fn get_arr_thing(ref self: TContractState) -> Array<StructThing>;
14+
}
15+
16+
#[starknet::contract]
17+
mod MockChecker {
18+
use super::IMockChecker;
19+
use super::StructThing;
20+
use array::ArrayTrait;
21+
22+
#[storage]
23+
struct Storage {
24+
stored_thing: felt252
25+
}
26+
27+
#[constructor]
28+
fn constructor(ref self: ContractState, arg1: felt252) {
29+
self.stored_thing.write(arg1)
30+
}
31+
32+
#[external(v0)]
33+
impl IMockCheckerImpl of super::IMockChecker<ContractState> {
34+
fn get_thing(ref self: ContractState) -> felt252 {
35+
self.stored_thing.read()
36+
}
37+
38+
fn get_thing_wrapper(ref self: ContractState) -> felt252 {
39+
self.get_thing()
40+
}
41+
42+
fn get_constant_thing(ref self: ContractState) -> felt252 {
43+
13
44+
}
45+
46+
fn get_struct_thing(ref self: ContractState) -> StructThing {
47+
StructThing {item_one: 12, item_two: 21}
48+
}
49+
50+
fn get_arr_thing(ref self: ContractState) -> Array<StructThing> {
51+
array![StructThing {item_one: 12, item_two: 21}]
52+
}
53+
}
54+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
use starknet::ClassHash;
2+
3+
#[starknet::interface]
4+
trait IMockChecker<TContractState> {
5+
fn get_constant_thing(ref self: TContractState) -> felt252;
6+
}
7+
8+
#[starknet::interface]
9+
trait IMockCheckerLibCall<TContractState> {
10+
fn get_constant_thing_with_lib_call(ref self: TContractState, class_hash: ClassHash) -> felt252;
11+
}
12+
13+
#[starknet::contract]
14+
mod MockCheckerLibCall {
15+
use super::{IMockCheckerDispatcherTrait, IMockCheckerLibraryDispatcher};
16+
use starknet::ClassHash;
17+
18+
#[storage]
19+
struct Storage {}
20+
21+
#[external(v0)]
22+
impl IMockCheckerLibCall of super::IMockCheckerLibCall<ContractState> {
23+
fn get_constant_thing_with_lib_call(ref self: ContractState, class_hash: ClassHash) -> felt252 {
24+
let mock_checker = IMockCheckerLibraryDispatcher { class_hash };
25+
mock_checker.get_constant_thing()
26+
}
27+
}
28+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
use starknet::ContractAddress;
2+
3+
#[starknet::interface]
4+
trait IMockChecker<TContractState> {
5+
fn get_thing(ref self: TContractState) -> felt252;
6+
}
7+
8+
9+
#[starknet::interface]
10+
trait IMockCheckerProxy<TContractState> {
11+
fn get_thing_from_contract(ref self: TContractState, address: ContractAddress) -> felt252;
12+
fn get_thing_from_contract_and_emit_event(ref self: TContractState, address: ContractAddress) -> felt252;
13+
}
14+
15+
#[starknet::contract]
16+
mod MockCheckerProxy {
17+
use starknet::ContractAddress;
18+
use super::IMockCheckerDispatcherTrait;
19+
use super::IMockCheckerDispatcher;
20+
21+
#[storage]
22+
struct Storage {}
23+
24+
#[event]
25+
#[derive(Drop, starknet::Event)]
26+
enum Event {
27+
ThingEmitted: ThingEmitted
28+
}
29+
30+
#[derive(Drop, starknet::Event)]
31+
struct ThingEmitted {
32+
thing: felt252
33+
}
34+
35+
#[external(v0)]
36+
impl IMockCheckerProxy of super::IMockCheckerProxy<ContractState> {
37+
fn get_thing_from_contract(ref self: ContractState, address: ContractAddress) -> felt252 {
38+
let dispatcher = IMockCheckerDispatcher { contract_address: address };
39+
dispatcher.get_thing()
40+
}
41+
42+
fn get_thing_from_contract_and_emit_event(ref self: ContractState, address: ContractAddress) -> felt252 {
43+
let dispatcher = IMockCheckerDispatcher { contract_address: address };
44+
let thing = dispatcher.get_thing();
45+
self.emit(Event::ThingEmitted(ThingEmitted { thing }));
46+
thing
47+
}
48+
}
49+
}

0 commit comments

Comments
 (0)