Skip to content

Commit bd46d55

Browse files
starknet_os: initial test, measure call contract resources
1 parent 26755a8 commit bd46d55

10 files changed

Lines changed: 227 additions & 5 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/constants.cairo

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ const ENTRY_POINT_INITIAL_BUDGET = 10000;
104104
const SYSCALL_BASE_GAS_COST = 10000;
105105

106106
// Syscall gas costs.
107-
const CALL_CONTRACT_GAS_COST = 91560;
107+
const CALL_CONTRACT_GAS_COST = 91160;
108108
const DEPLOY_GAS_COST = 147120;
109109
const DEPLOY_CALLDATA_FACTOR_GAS_COST = 4850;
110110
const GET_BLOCK_HASH_GAS_COST = 10840;
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"os": "0x2a4b5650b64c1a13a4d2754353ddd85d4b970766063220253a4f96643ba23f6",
3-
"virtual_os": "0x39f55918423cade9e95a6a52286b56bed1c5c9b6fe39aa00301361457a3c604",
2+
"os": "0x1569abd39792bd43ec8d3ff0bf9baf4c8dab6e0528ffdf7b6f2c57a1e7e3188",
3+
"virtual_os": "0x1c3f2f4f0a1b23ec07e236928c02ae3b13cb0137af1c8e3048994401b4b8e74",
44
"aggregator": "0x700786d51b3854af43d8e12180380bda3029be6c1767e007858de6ca2edac40",
55
"aggregator_with_prefix": "0xe08d300e3f5996e43d6d7cc5a20068e0e58cf1309089f2348317ac580f6c1f"
66
}

crates/blockifier/resources/blockifier_versioned_constants_0_14_3.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@
225225
"os_resources": {
226226
"execute_syscalls": {
227227
"CallContract": {
228-
"n_steps": 903,
228+
"n_steps": 899,
229229
"n_memory_holes": 0,
230230
"builtin_instance_counter": {
231231
"range_check_builtin": 18

crates/blockifier/src/blockifier_versioned_constants.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ pub struct RawVersionedConstants {
9999

100100
#[cfg(any(test, feature = "testing"))]
101101
impl RawVersionedConstants {
102-
fn to_string_pretty(&self) -> String {
102+
pub fn to_string_pretty(&self) -> String {
103103
let mut buffer = Vec::new();
104104
let formatter = serde_json::ser::PrettyFormatter::with_indent(b" ");
105105
let mut serializer = serde_json::Serializer::with_formatter(&mut buffer, formatter);

crates/starknet_api/src/versioned_constants_logic.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ pub trait VersionedConstantsTrait: Debug {
2525
/// Gets the first version with versioned constants.
2626
fn first_version() -> StarknetVersion;
2727

28+
/// Gets the path to the JSON file for the specified Starknet version.
29+
fn json_path(version: &StarknetVersion) -> Result<String, Self::Error>;
30+
2831
/// Gets the contents of the JSON file for the specified Starknet version.
2932
fn json_str(version: &StarknetVersion) -> Result<&'static str, Self::Error>;
3033

@@ -161,6 +164,17 @@ macro_rules! define_versioned_constants_inner {
161164
$first_version
162165
}
163166

167+
fn json_path(version: &StarknetVersion) -> Result<String, Self::Error> {
168+
Ok(format!(
169+
"{}/src/{}",
170+
env!("CARGO_MANIFEST_DIR"),
171+
match version {
172+
$(StarknetVersion::$variant => $path_to_json.to_string(),)*
173+
_ => return Err(Self::Error::InvalidStarknetVersion(*version)),
174+
}
175+
))
176+
}
177+
164178
fn json_str(
165179
version: &starknet_api::block::StarknetVersion
166180
) -> Result<&'static str, Self::Error> {

crates/starknet_os/src/hint_processor/os_logger.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,11 @@ impl SyscallTrace {
9696
pub fn push_inner_syscall(&mut self, inner: SyscallTrace) {
9797
self.inner_syscalls.push(inner);
9898
}
99+
100+
#[cfg(any(test, feature = "testing"))]
101+
pub fn get_selector(&self) -> SyscallSelector {
102+
self.selector
103+
}
99104
}
100105

101106
impl ResourceFinalizer for SyscallTrace {
@@ -156,6 +161,11 @@ impl OsTransactionTrace {
156161
pub fn push_syscall(&mut self, syscall: SyscallTrace) {
157162
self.syscalls.push(syscall);
158163
}
164+
165+
#[cfg(any(test, feature = "testing"))]
166+
pub fn get_syscalls(&self) -> &Vec<SyscallTrace> {
167+
&self.syscalls
168+
}
159169
}
160170

161171
impl ResourceFinalizer for OsTransactionTrace {

crates/starknet_os_flow_tests/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ cairo-lang-starknet-classes.workspace = true
2121
cairo-vm.workspace = true
2222
chrono.workspace = true
2323
expect-test.workspace = true
24+
indexmap.workspace = true
2425
itertools.workspace = true
2526
rand.workspace = true
2627
rand_chacha.workspace = true

crates/starknet_os_flow_tests/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
pub(crate) mod fuzz_tests;
44
pub(crate) mod initial_state;
5+
pub(crate) mod os_resources_test;
56
pub(crate) mod special_contracts;
67
pub(crate) mod test_manager;
78
pub(crate) mod tests;
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
use std::collections::HashSet;
2+
3+
use blockifier::blockifier_versioned_constants::{
4+
RawVersionedConstants,
5+
VariableResourceParams,
6+
VersionedConstants,
7+
};
8+
use blockifier::context::BlockContext;
9+
use blockifier::execution::deprecated_syscalls::DeprecatedSyscallSelector as Selector;
10+
use blockifier::test_utils::dict_state_reader::DictStateReader;
11+
use blockifier_test_utils::cairo_versions::RunnableCairo1;
12+
use blockifier_test_utils::contracts::FeatureContract;
13+
use cairo_vm::vm::runners::cairo_runner::ExecutionResources;
14+
use expect_test::expect_file;
15+
use indexmap::IndexMap;
16+
use starknet_api::block::StarknetVersion;
17+
use starknet_api::contract_class::SierraVersion;
18+
use starknet_api::executable_transaction::InvokeTransaction;
19+
use starknet_api::test_utils::invoke::invoke_tx;
20+
use starknet_api::versioned_constants_logic::VersionedConstantsTrait;
21+
use starknet_api::{calldata, invoke_tx_args};
22+
use starknet_os::hint_processor::os_logger::ResourceFinalizer;
23+
use strum::IntoEnumIterator;
24+
25+
use crate::initial_state::create_default_initial_state_data;
26+
use crate::test_manager::{TestBuilder, TestBuilderConfig};
27+
use crate::tests::NON_TRIVIAL_RESOURCE_BOUNDS;
28+
use crate::utils::get_class_hash_of_feature_contract;
29+
30+
// TODO(Dori): Delete this, or at least reduce it to a minimal set of unmeasurable syscalls.
31+
const UNMEASURABLE_SYSCALLS: [Selector; 34] = [
32+
Selector::DelegateCall,
33+
Selector::DelegateL1Handler,
34+
Selector::Deploy,
35+
Selector::EmitEvent,
36+
Selector::GetBlockHash,
37+
Selector::GetBlockNumber,
38+
Selector::GetBlockTimestamp,
39+
Selector::GetCallerAddress,
40+
Selector::GetClassHashAt,
41+
Selector::GetContractAddress,
42+
Selector::GetExecutionInfo,
43+
Selector::GetSequencerAddress,
44+
Selector::GetTxInfo,
45+
Selector::GetTxSignature,
46+
Selector::Keccak,
47+
Selector::KeccakRound,
48+
Selector::Sha256ProcessBlock,
49+
Selector::LibraryCall,
50+
Selector::LibraryCallL1Handler,
51+
Selector::MetaTxV0,
52+
Selector::ReplaceClass,
53+
Selector::Secp256k1Add,
54+
Selector::Secp256k1GetPointFromX,
55+
Selector::Secp256k1GetXy,
56+
Selector::Secp256k1Mul,
57+
Selector::Secp256k1New,
58+
Selector::Secp256r1Add,
59+
Selector::Secp256r1GetPointFromX,
60+
Selector::Secp256r1GetXy,
61+
Selector::Secp256r1Mul,
62+
Selector::Secp256r1New,
63+
Selector::SendMessageToL1,
64+
Selector::StorageRead,
65+
Selector::StorageWrite,
66+
];
67+
68+
#[tokio::test]
69+
async fn test_os_resources_regression() {
70+
let os_resources_contract = FeatureContract::OsResourcesTest(RunnableCairo1::Casm);
71+
let os_resources_class_hash = get_class_hash_of_feature_contract(os_resources_contract);
72+
73+
// Setup the test initial state and test builder.
74+
// Need to explicitly set up the state to be able to override the minimal sierra version for gas
75+
// tracking, in order to force step tracking mode.
76+
let (mut initial_state_data, [os_resources_contract_address]) =
77+
create_default_initial_state_data::<DictStateReader, 1>([(
78+
os_resources_contract,
79+
calldata![],
80+
)])
81+
.await;
82+
initial_state_data.initial_state.block_context = {
83+
let block_context = &initial_state_data.initial_state.block_context;
84+
let mut vc = block_context.versioned_constants().clone();
85+
vc.min_sierra_version_for_sierra_gas = SierraVersion::new(99, 99, 99);
86+
BlockContext::new(
87+
block_context.block_info().clone(),
88+
block_context.chain_info().clone(),
89+
vc,
90+
block_context.bouncer_config.clone(),
91+
)
92+
};
93+
let virtual_os = false;
94+
let mut test_builder = TestBuilder::new_with_initial_state_data(
95+
initial_state_data,
96+
TestBuilderConfig::default(),
97+
virtual_os,
98+
);
99+
100+
// Fund the contract - it will be used as the account.
101+
// Then, move on to the next block, so the syscall-measurement tx is in it's own block.
102+
test_builder.add_fund_address_tx_with_default_amount(os_resources_contract_address);
103+
test_builder.move_to_next_block();
104+
105+
// Add the syscall-measurement tx.
106+
let tx = InvokeTransaction::create(
107+
invoke_tx(invoke_tx_args! {
108+
sender_address: os_resources_contract_address,
109+
calldata: calldata![*os_resources_class_hash, **os_resources_contract_address],
110+
resource_bounds: *NON_TRIVIAL_RESOURCE_BOUNDS,
111+
}),
112+
&test_builder.chain_id(),
113+
)
114+
.unwrap();
115+
test_builder.add_invoke_tx(tx, None, None);
116+
117+
// Run test. Grab the execution info from the runner (for later) before consuming it.
118+
let test_runner = test_builder.build().await;
119+
let inner_calls = test_runner
120+
.os_hints
121+
.os_input
122+
.os_block_inputs
123+
.last()
124+
.unwrap()
125+
.tx_execution_infos
126+
.last()
127+
.unwrap()
128+
.execute_call_info
129+
.as_ref()
130+
.unwrap()
131+
.inner_calls
132+
.clone();
133+
let test_output = test_runner.run();
134+
test_output.perform_default_validations();
135+
136+
// Extract syscall resources consumed, per (measurable) syscall.
137+
let syscall_traces = test_output.runner_output.txs_trace.last().unwrap().get_syscalls();
138+
139+
// Measure each syscall overhead. If the syscall incurs an inner call, subtract the inner call
140+
// overhead.
141+
let mut inner_calls_iter = inner_calls.into_iter();
142+
let mut visited_syscalls = HashSet::new();
143+
let measurements: IndexMap<Selector, ExecutionResources> = syscall_traces
144+
.iter()
145+
.filter_map(|syscall_trace| {
146+
let selector = syscall_trace.get_selector();
147+
if UNMEASURABLE_SYSCALLS.contains(&selector) {
148+
return None;
149+
}
150+
151+
// Ensure we don't visit the same syscall twice.
152+
assert!(
153+
!visited_syscalls.contains(&selector),
154+
"Syscall {selector:?} was visited twice."
155+
);
156+
visited_syscalls.insert(selector);
157+
158+
// If this syscall incurs an inner call, it should be the next inner call in the
159+
// iterator.
160+
let inner_overhead = if selector.is_calling_syscall() {
161+
inner_calls_iter.next().unwrap().resources.vm_resources
162+
} else {
163+
ExecutionResources::default()
164+
};
165+
166+
Some((
167+
selector,
168+
(syscall_trace.get_resources().unwrap() - &inner_overhead).filter_unused_builtins(),
169+
))
170+
})
171+
.collect();
172+
173+
// Make sure we covered all syscalls we expect to.
174+
assert_eq!(
175+
visited_syscalls,
176+
Selector::iter()
177+
.collect::<HashSet<_>>()
178+
.difference(&UNMEASURABLE_SYSCALLS.iter().cloned().collect::<HashSet<_>>())
179+
.copied()
180+
.collect::<HashSet<_>>()
181+
);
182+
183+
// Compare the measurements with the expected values on the latest VC.
184+
let version = StarknetVersion::LATEST;
185+
let mut raw_vc: RawVersionedConstants =
186+
serde_json::from_str(VersionedConstants::json_str(&version).unwrap()).unwrap();
187+
for (syscall, resources) in measurements {
188+
raw_vc
189+
.os_resources
190+
.execute_syscalls
191+
.insert(syscall, VariableResourceParams::Constant(resources));
192+
}
193+
expect_file![VersionedConstants::json_path(&version).unwrap()]
194+
.assert_eq(&raw_vc.to_string_pretty());
195+
}

0 commit comments

Comments
 (0)