Skip to content

Commit 548f88a

Browse files
authored
Support cheats in meta_tx_v0 (#3649)
<!-- Reference any GitHub issues resolved by this PR --> Closes #3575 ## Introduced changes <!-- A brief description of the changes --> - Implement handling of` meta_tx_v0 `syscall in `snforge`, ensuring that cheat codes are properly supported and can be executed during tests. ## Checklist <!-- Make sure all of these are complete --> - [x] Linked relevant issue - [ ] Updated relevant documentation - [x] Added relevant tests - [x] Performed self-review of the code - [x] Added changes to `CHANGELOG.md`
1 parent f800fa8 commit 548f88a

File tree

24 files changed

+841
-6
lines changed

24 files changed

+841
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
#### Added
1313

14+
- Support for `meta_tx_v0` syscall with cheatcode compatibility
1415
- `snforge` now supports [oracles](https://docs.swmansion.com/cairo-oracle/) with `--experimental-oracles` flag.
1516
- `--trace-components` flag to allow selecting which components of the trace to do display. Read more [here](https://foundry-rs.github.io/starknet-foundry/snforge-advanced-features/debugging.html#trace-components)
1617

crates/cheatnet/src/runtime_extensions/call_to_blockifier_runtime_extension/execution/cheated_syscalls.rs

Lines changed: 168 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,25 @@ use super::calls::{execute_inner_call, execute_library_call};
22
use super::execution_info::get_cheated_exec_info_ptr;
33
use crate::runtime_extensions::call_to_blockifier_runtime_extension::CheatnetState;
44
use crate::runtime_extensions::call_to_blockifier_runtime_extension::execution::entry_point::execute_constructor_entry_point;
5+
use blockifier::context::TransactionContext;
6+
use blockifier::execution::common_hints::ExecutionMode;
7+
use blockifier::execution::execution_utils::ReadOnlySegment;
58
use blockifier::execution::syscalls::hint_processor::{
6-
SyscallExecutionError, SyscallHintProcessor,
9+
INVALID_ARGUMENT, SyscallExecutionError, SyscallHintProcessor,
710
};
811
use blockifier::execution::syscalls::syscall_base::SyscallResult;
912
use blockifier::execution::syscalls::vm_syscall_utils::{
1013
CallContractRequest, CallContractResponse, DeployRequest, DeployResponse, EmptyRequest,
1114
GetBlockHashRequest, GetBlockHashResponse, GetExecutionInfoResponse, LibraryCallRequest,
12-
LibraryCallResponse, StorageReadRequest, StorageReadResponse, StorageWriteRequest,
13-
StorageWriteResponse, SyscallSelector,
15+
LibraryCallResponse, MetaTxV0Request, MetaTxV0Response, StorageReadRequest,
16+
StorageReadResponse, StorageWriteRequest, StorageWriteResponse, SyscallSelector,
17+
TryExtractRevert,
1418
};
1519
use blockifier::execution::{call_info::CallInfo, entry_point::ConstructorContext};
1620
use blockifier::state::errors::StateError;
21+
use blockifier::transaction::objects::{
22+
CommonAccountFields, DeprecatedTransactionInfo, TransactionInfo,
23+
};
1724
use blockifier::{
1825
execution::entry_point::{
1926
CallEntryPoint, CallType, EntryPointExecutionContext, EntryPointExecutionResult,
@@ -24,15 +31,24 @@ use blockifier::{
2431
execution::execution_utils::update_remaining_gas,
2532
execution::syscalls::hint_processor::create_retdata_segment,
2633
};
34+
use cairo_vm::Felt252;
2735
use cairo_vm::vm::vm_core::VirtualMachine;
2836
use conversions::string::TryFromHexStr;
2937
use runtime::starknet::constants::TEST_ADDRESS;
30-
use starknet_api::core::calculate_contract_address;
38+
use starknet_api::abi::abi_utils::selector_from_name;
39+
use starknet_api::core::EntryPointSelector;
40+
use starknet_api::transaction::constants::EXECUTE_ENTRY_POINT_NAME;
41+
use starknet_api::transaction::fields::TransactionSignature;
42+
use starknet_api::transaction::{TransactionHasher, TransactionOptions, signed_tx_version};
3143
use starknet_api::{
3244
contract_class::EntryPointType,
33-
core::{ClassHash, ContractAddress},
34-
transaction::fields::Calldata,
45+
core::{ClassHash, ContractAddress, Nonce, calculate_contract_address},
46+
transaction::{
47+
InvokeTransactionV0, TransactionVersion,
48+
fields::{Calldata, Fee},
49+
},
3550
};
51+
use std::sync::Arc;
3652

3753
#[expect(clippy::result_large_err)]
3854
pub fn get_execution_info_syscall(
@@ -224,6 +240,152 @@ pub fn call_contract_syscall(
224240
// endregion
225241
}
226242

243+
// blockifier/src/execution/syscalls/hint_processor.rs:637 (meta_tx_v0)
244+
#[allow(clippy::result_large_err)]
245+
pub fn meta_tx_v0_syscall(
246+
request: MetaTxV0Request,
247+
vm: &mut VirtualMachine,
248+
syscall_handler: &mut SyscallHintProcessor<'_>,
249+
cheatnet_state: &mut CheatnetState,
250+
remaining_gas: &mut u64,
251+
) -> SyscallResult<MetaTxV0Response> {
252+
let storage_address = request.contract_address;
253+
let selector = request.entry_point_selector;
254+
255+
// region: Modified blockifier code
256+
let retdata_segment = meta_tx_v0(
257+
syscall_handler,
258+
vm,
259+
cheatnet_state,
260+
storage_address,
261+
selector,
262+
request.calldata,
263+
request.signature,
264+
remaining_gas,
265+
)?;
266+
// endregion
267+
268+
Ok(MetaTxV0Response {
269+
segment: retdata_segment,
270+
})
271+
}
272+
273+
// blockifier/src/execution/syscalls/syscall_base.rs:278 (meta_tx_v0)
274+
#[allow(clippy::result_large_err, clippy::too_many_arguments)]
275+
fn meta_tx_v0(
276+
syscall_handler: &mut SyscallHintProcessor<'_>,
277+
vm: &mut VirtualMachine,
278+
cheatnet_state: &mut CheatnetState,
279+
contract_address: ContractAddress,
280+
entry_point_selector: EntryPointSelector,
281+
calldata: Calldata,
282+
signature: TransactionSignature,
283+
remaining_gas: &mut u64,
284+
) -> SyscallResult<ReadOnlySegment> {
285+
syscall_handler.increment_linear_factor_by(&SyscallSelector::MetaTxV0, calldata.0.len());
286+
287+
if syscall_handler.base.context.execution_mode == ExecutionMode::Validate {
288+
//region: Modified blockifier code
289+
unreachable!(
290+
"`ExecutionMode::Validate` should never occur as execution mode is hardcoded to `Execute`"
291+
);
292+
// endregion
293+
}
294+
295+
if entry_point_selector != selector_from_name(EXECUTE_ENTRY_POINT_NAME) {
296+
return Err(SyscallExecutionError::Revert {
297+
error_data: vec![Felt252::from_hex(INVALID_ARGUMENT).unwrap()],
298+
});
299+
}
300+
301+
let mut entry_point = CallEntryPoint {
302+
class_hash: None,
303+
code_address: Some(contract_address),
304+
entry_point_type: EntryPointType::External,
305+
entry_point_selector,
306+
calldata: calldata.clone(),
307+
storage_address: contract_address,
308+
caller_address: ContractAddress::default(),
309+
call_type: CallType::Call,
310+
// NOTE: this value might be overridden later on.
311+
initial_gas: *remaining_gas,
312+
};
313+
314+
let old_tx_context = syscall_handler.base.context.tx_context.clone();
315+
let only_query = old_tx_context.tx_info.only_query();
316+
317+
// Compute meta-transaction hash.
318+
let transaction_hash = InvokeTransactionV0 {
319+
max_fee: Fee(0),
320+
signature: signature.clone(),
321+
contract_address,
322+
entry_point_selector,
323+
calldata,
324+
}
325+
.calculate_transaction_hash(
326+
&syscall_handler
327+
.base
328+
.context
329+
.tx_context
330+
.block_context
331+
.chain_info()
332+
.chain_id,
333+
&signed_tx_version(
334+
&TransactionVersion::ZERO,
335+
&TransactionOptions { only_query },
336+
),
337+
)?;
338+
339+
let class_hash = syscall_handler
340+
.base
341+
.state
342+
.get_class_hash_at(contract_address)?;
343+
344+
// Replace `tx_context`.
345+
let new_tx_info = TransactionInfo::Deprecated(DeprecatedTransactionInfo {
346+
common_fields: CommonAccountFields {
347+
transaction_hash,
348+
version: TransactionVersion::ZERO,
349+
signature,
350+
nonce: Nonce(0.into()),
351+
sender_address: contract_address,
352+
only_query,
353+
},
354+
max_fee: Fee(0),
355+
});
356+
syscall_handler.base.context.tx_context = Arc::new(TransactionContext {
357+
block_context: old_tx_context.block_context.clone(),
358+
tx_info: new_tx_info,
359+
});
360+
361+
// region: Modified blockifier code
362+
// No error should be propagated until we restore the old `tx_context`.
363+
let retdata_segment = execute_inner_call(
364+
&mut entry_point,
365+
vm,
366+
syscall_handler,
367+
cheatnet_state,
368+
remaining_gas,
369+
)
370+
.map_err(|error| {
371+
SyscallExecutionError::from_self_or_revert(error.try_extract_revert().map_original(
372+
|error| {
373+
error.as_call_contract_execution_error(
374+
class_hash,
375+
contract_address,
376+
entry_point_selector,
377+
)
378+
},
379+
))
380+
})?;
381+
// endregion
382+
383+
// Restore the old `tx_context`.
384+
syscall_handler.base.context.tx_context = old_tx_context;
385+
386+
Ok(retdata_segment)
387+
}
388+
227389
#[expect(clippy::needless_pass_by_value, clippy::result_large_err)]
228390
pub fn get_block_hash_syscall(
229391
request: GetBlockHashRequest,

crates/cheatnet/src/runtime_extensions/cheatable_starknet_runtime_extension.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,14 @@ impl<'a> ExtensionLogic for CheatableStarknetRuntimeExtension<'a> {
100100
SyscallSelector::StorageWrite,
101101
)
102102
.map(|()| SyscallHandlingResult::Handled),
103+
SyscallSelector::MetaTxV0 => self
104+
.execute_syscall(
105+
syscall_handler,
106+
vm,
107+
cheated_syscalls::meta_tx_v0_syscall,
108+
SyscallSelector::MetaTxV0,
109+
)
110+
.map(|()| SyscallHandlingResult::Handled),
103111
_ => Ok(SyscallHandlingResult::Forwarded),
104112
}
105113
}

0 commit comments

Comments
 (0)