diff --git a/.github/workflows/starknet_transaction_prover_ci.yml b/.github/workflows/starknet_transaction_prover_ci.yml index 5ee93a14789..f213679bb63 100644 --- a/.github/workflows/starknet_transaction_prover_ci.yml +++ b/.github/workflows/starknet_transaction_prover_ci.yml @@ -12,11 +12,14 @@ on: - ".github/workflows/starknet_transaction_prover_ci.yml" - "Cargo.lock" - "Cargo.toml" + - "crates/apollo_starknet_os_program/**" - "crates/blockifier/**" - "crates/blockifier_reexecution/**" + - "crates/blockifier_test_utils/**" - "crates/proving_utils/**" - "crates/starknet_committer/**" - "crates/starknet_os/**" + - "crates/starknet_os_flow_tests/**" - "crates/starknet_patricia/**" - "crates/starknet_transaction_prover/**" - "scripts/build_starknet_transaction_prover.sh" @@ -110,6 +113,10 @@ jobs: working-directory: crates/starknet_transaction_prover run: cargo test --release -p starknet_transaction_prover --features stwo_proving virtual_snos_prover_test -- --ignored --test-threads=1 + - name: Run virtual OS multicall proving test + working-directory: crates/starknet_transaction_prover + run: cargo test --release -p starknet_os_flow_tests --features starknet_transaction_prover/stwo_proving prove_and_verify_multicall_tx -- --ignored --test-threads=1 + # Test that the starknet_transaction_prover Docker image builds successfully. docker-build: runs-on: namespace-profile-medium-ubuntu-24-04-amd64 diff --git a/Cargo.lock b/Cargo.lock index 81c7ead2892..99cdfb34249 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12736,6 +12736,7 @@ dependencies = [ "starknet_os", "starknet_patricia", "starknet_patricia_storage", + "starknet_proof_verifier", "starknet_transaction_prover", "strum", "tokio", diff --git a/crates/blockifier/src/bouncer_test.rs b/crates/blockifier/src/bouncer_test.rs index 305ea990f8b..9a5a1b4e462 100644 --- a/crates/blockifier/src/bouncer_test.rs +++ b/crates/blockifier/src/bouncer_test.rs @@ -804,11 +804,11 @@ fn class_hash_migration_data_from_state( if should_migrate { expect![[r#" - 108608775 + 110756293 "#]] .assert_debug_eq(&migration_sierra_gas.0); expect![[r#" - 266780662 + 272117746 "#]] .assert_debug_eq(&migration_proving_gas.0); } else { diff --git a/crates/blockifier/src/execution/stack_trace_regression/test_contract_ctor_frame_stack_trace_cairo1_casm.txt b/crates/blockifier/src/execution/stack_trace_regression/test_contract_ctor_frame_stack_trace_cairo1_casm.txt index fcb106d2470..800df228b11 100644 --- a/crates/blockifier/src/execution/stack_trace_regression/test_contract_ctor_frame_stack_trace_cairo1_casm.txt +++ b/crates/blockifier/src/execution/stack_trace_regression/test_contract_ctor_frame_stack_trace_cairo1_casm.txt @@ -2,7 +2,7 @@ Transaction execution has failed: 0: Error in the called contract (contract address: 0x00000000000000000000000000000000000000000000000000000000c0020000, class hash: 0x0000000000000000000000000000000000000000000000000000000080020000, selector: 0x015d40a3d6ca2ac30f4031e42be28da9b056fef9bb7357ac5e85627ee876e5ad): Error at pc=0:539: 1: Error in the called contract (contract address: 0x00000000000000000000000000000000000000000000000000000000c0020000, class hash: 0x0000000000000000000000000000000000000000000000000000000080020000, selector: 0x02730079d734ee55315f4f141eaed376bddd8c2133523d223a344c5604e0f7f8): -Error at pc=0:781: +Error at pc=0:950: 2: Error in the contract class constructor (contract address: 0x0103ee82605273496eed8d9141c5b3ad967baa08be63aa5bc49ffae5eae454cc, class hash: 0x0000000000000000000000000000000000000000000000000000000080040000, selector: 0x028ffe4ff0f226a9107253e17a904099aa4f63a02a5621de0576e5aa71bc5194): Execution failed. Failure reason: Error in contract (contract address: 0x0103ee82605273496eed8d9141c5b3ad967baa08be63aa5bc49ffae5eae454cc, class hash: 0x0000000000000000000000000000000000000000000000000000000080040000, selector: 0x028ffe4ff0f226a9107253e17a904099aa4f63a02a5621de0576e5aa71bc5194): diff --git a/crates/blockifier_test_utils/resources/feature_contracts/cairo1/account_with_dummy_validate.cairo b/crates/blockifier_test_utils/resources/feature_contracts/cairo1/account_with_dummy_validate.cairo index e518695f54b..e433fcac12e 100644 --- a/crates/blockifier_test_utils/resources/feature_contracts/cairo1/account_with_dummy_validate.cairo +++ b/crates/blockifier_test_utils/resources/feature_contracts/cairo1/account_with_dummy_validate.cairo @@ -1,6 +1,7 @@ #[starknet::contract(account)] mod Account { use array::{ArrayTrait, SpanTrait}; + use starknet::account::Call; use starknet::{ClassHash, ContractAddress, call_contract_syscall}; use starknet::info::SyscallResultTrait; use starknet::syscalls; @@ -34,6 +35,9 @@ mod Account { starknet::VALIDATED } + // TODO(Yoni): replace this single-call `__execute__` with the multicall-shaped + // `multi_call` below, so this account can execute INVOKE transactions whose + // calldata is `Array` natively (matching the standard account ABI). #[external(v0)] #[raw_output] fn __execute__( @@ -52,6 +56,32 @@ mod Account { ).unwrap_syscall() } + /// Executes a sequence of calls and returns their concatenated return values. + /// The intended invocation in tests is via `__execute__`, with `__execute__` + /// forwarding `(self_address, "multi_call", serialized calls)` to this entry point. + #[external(v0)] + fn multi_call( + self: @ContractState, mut calls: Array + ) -> Array> { + let mut result = ArrayTrait::new(); + loop { + match calls.pop_front() { + Option::Some(call) => { + let res = call_contract_syscall( + address: call.to, + entry_point_selector: call.selector, + calldata: call.calldata, + ).unwrap_syscall(); + result.append(res); + }, + Option::None => { + break; + }, + }; + }; + result + } + #[external(v0)] fn deploy_contract( self: @ContractState, diff --git a/crates/starknet_os_flow_tests/Cargo.toml b/crates/starknet_os_flow_tests/Cargo.toml index 604032a4144..46650899585 100644 --- a/crates/starknet_os_flow_tests/Cargo.toml +++ b/crates/starknet_os_flow_tests/Cargo.toml @@ -34,6 +34,7 @@ starknet_committer = { workspace = true, features = ["testing"] } starknet_os = { workspace = true, features = ["include_program_output", "testing"] } starknet_patricia = { workspace = true, features = ["testing"] } starknet_patricia_storage = { workspace = true, features = ["testing"] } +starknet_proof_verifier.workspace = true starknet_transaction_prover.workspace = true strum.workspace = true tokio.workspace = true diff --git a/crates/starknet_os_flow_tests/src/fuzz_tests.rs b/crates/starknet_os_flow_tests/src/fuzz_tests.rs index 1b3f9a12bbb..70c722fde0b 100644 --- a/crates/starknet_os_flow_tests/src/fuzz_tests.rs +++ b/crates/starknet_os_flow_tests/src/fuzz_tests.rs @@ -88,15 +88,15 @@ static IS_CAIRO1: LazyLock> = LazyLock::new(|| { /// Initial fuzz contract addresses. static FUZZ_ADDRESS_ORCHESTRATOR_EXPECT: Expect = - expect!["0x4c885880af2af2afc2b57ed77bd4dfacd8da1768c0de60a14d854d6aa681678"]; + expect!["0x727742e0b8d4b0ba3e4d1bac6cea5e523cfde238c694ddf78006f113ae1f7d6"]; static FUZZ_ADDRESS_CAIRO1_A_EXPECT: Expect = - expect!["0x3b22209e355bb17880121aad4621a7658a2daea2c0c7dd5159595c93ddfac67"]; + expect!["0x1ee6d084c36adff47c27ffaac99ae535ca9608067508fa722ceeedefbc82460"]; static FUZZ_ADDRESS_CAIRO1_B_EXPECT: Expect = - expect!["0x2c1ef35cf09a851d642b1b21141e19a49aacce41a65eeeb46decd24fc55da54"]; + expect!["0x48a60831f25e99646cdf88a1de37140e2809bfb99f5791e504707ab899d86cb"]; static FUZZ_ADDRESS_CAIRO0_A_EXPECT: Expect = - expect!["0x209b4338889acf7e7c4a0654b51e51bc874df786a12d77e95a2362711cfac6c"]; + expect!["0x515caf6295458c854d5a577786d6cdd615bed0c02432e09822546c9f408fda1"]; static FUZZ_ADDRESS_CAIRO0_B_EXPECT: Expect = - expect!["0x437a3c2706d0fe102ddccafa7169b616c8f4b4124f0761edc08e9283592caf0"]; + expect!["0x682ede4f50d9b42fe07911e67993682449ed63a69ba1f8c69764ff1d22e5e5d"]; static FUZZ_ADDRESS_ORCHESTRATOR: LazyLock = LazyLock::new(|| { ContractAddress::try_from(felt!(FUZZ_ADDRESS_ORCHESTRATOR_EXPECT.data())).unwrap() }); diff --git a/crates/starknet_os_flow_tests/src/tests.rs b/crates/starknet_os_flow_tests/src/tests.rs index 2837a01d73d..d7a91ac71d2 100644 --- a/crates/starknet_os_flow_tests/src/tests.rs +++ b/crates/starknet_os_flow_tests/src/tests.rs @@ -1265,7 +1265,7 @@ async fn test_experimental_libfuncs_contract(#[values(true, false)] use_kzg_da: .copied() .unwrap_or(0); expect![[r#" - 564 + 569 "#]] .assert_debug_eq(&blakes); diff --git a/crates/starknet_os_flow_tests/src/virtual_os_test.rs b/crates/starknet_os_flow_tests/src/virtual_os_test.rs index 87fc9b36d5a..de1d419b94d 100644 --- a/crates/starknet_os_flow_tests/src/virtual_os_test.rs +++ b/crates/starknet_os_flow_tests/src/virtual_os_test.rs @@ -274,6 +274,46 @@ async fn test_reverted_tx_os_error() { .run_virtual_expect_error("Reverted transactions are not supported in virtual OS mode"); } +/// Proves and verifies a virtual OS run of a single transaction that performs a +/// multicall through the account contract. The inner calls can be extended over +/// time to grow syscall/builtin coverage. +#[tokio::test(flavor = "multi_thread")] +#[ignore] +async fn prove_and_verify_multicall_tx() { + let test_contract = FeatureContract::TestContract(CairoVersion::Cairo1(RunnableCairo1::Casm)); + + let (mut test_builder, [contract_address]) = + TestBuilder::create_standard_virtual([(test_contract, calldata![Felt::ONE, Felt::TWO])]) + .await; + + // Appends a single `Call` entry to `multi_call_args` as + // `[to, selector, calldata_len, *calldata]` and bumps the leading `num_calls` + // counter. All inner calls in this test target the same test contract. + let mut multi_call_args: Vec = vec![Felt::ZERO]; + let mut serialize_call = |func_name: &str, args: &[Felt]| { + multi_call_args[0] += Felt::ONE; + multi_call_args.push(*contract_address.0.key()); + multi_call_args.push(selector_from_name(func_name).0); + multi_call_args.push(Felt::from(args.len())); + multi_call_args.extend_from_slice(args); + }; + + // TODO(Yoni): add more inner calls (e.g. sha256, secp256k1, send_message_to_l1). + // TODO(Yoni): restore the keccak inner call once the keccak syscall is allowed in + // virtual OS mode (added in a follow-up PR stacked on top of this one). + // serialize_call("test_keccak", &[]); + serialize_call("test_ec_op", &[]); + + // The dummy account's `__execute__(contract_address, selector, calldata)` forwards + // to its own `multi_call` entry point. + let calldata = create_calldata(*FUNDED_ACCOUNT_ADDRESS, "multi_call", &multi_call_args); + test_builder.add_funded_account_invoke(invoke_tx_args! { calldata }); + + let output = test_builder.build().await.run_virtual().prove().await; + starknet_proof_verifier::verify_proof(output.proof_facts, output.proof) + .expect("proof verification should succeed"); +} + /// Generates proof fixtures for the proof-flow integration test. /// To run manually: `cargo +nightly-2025-07-14 test -p starknet_os_flow_tests --features /// starknet_transaction_prover/stwo_proving --release generate_proof_fixtures -- --ignored` diff --git a/crates/starknet_transaction_prover/src/proving/virtual_snos_prover_test.rs b/crates/starknet_transaction_prover/src/proving/virtual_snos_prover_test.rs index 70ce5b926df..58f313e2c26 100644 --- a/crates/starknet_transaction_prover/src/proving/virtual_snos_prover_test.rs +++ b/crates/starknet_transaction_prover/src/proving/virtual_snos_prover_test.rs @@ -44,44 +44,6 @@ use crate::test_utils::{ STRK_TOKEN_ADDRESS_SEPOLIA, }; -/// Integration test for the full prover pipeline with a `balanceOf` transaction. -/// Runs on a Sepolia environment; in live/recording mode requires a Sepolia RPC node via -/// `NODE_URL`. -#[rstest] -#[tokio::test(flavor = "multi_thread")] -#[ignore] -async fn test_prove_balance_of_transaction() { - let test_mode = resolve_test_mode("test_prove_balance_of_transaction").await; - - // Creates an RPC invoke transaction that calls `balanceOf` on the STRK token. - let strk_token = ContractAddress::try_from(STRK_TOKEN_ADDRESS_SEPOLIA).unwrap(); - let account = ContractAddress::try_from(DUMMY_ACCOUNT_ADDRESS).unwrap(); - - // Calldata matches dummy account's __execute__(contract_address, selector, calldata). - let calldata = create_calldata(strk_token, "balanceOf", &[account.into()]); - let rpc_tx = build_client_side_rpc_invoke(account, calldata); - - let factory = runner_factory(&test_mode.rpc_url()); - let prover = VirtualSnosProver::from_runner(factory); - - // Run the full prover pipeline: OS execution → proof generation. - let result = prover.prove_transaction(BlockId::Latest, rpc_tx).await; - - // Finalize recording before asserting so records are saved even on failure. - test_mode.finalize(); - - // Verify execution and proving succeeded. - let output = result.expect("prove_transaction should succeed"); - - // Verify the proof against the proof facts. - let proof_facts = output.proof_facts.clone(); - let proof = output.proof.clone(); - tokio::task::spawn_blocking(move || verify_proof(proof_facts, proof)) - .await - .expect("proof verification task panicked") - .expect("proof verification should succeed"); -} - /// Integration test for the full prover pipeline with a STRK `transfer` transaction. /// Runs on a Sepolia environment; in live/recording mode requires a Sepolia RPC node via /// `NODE_URL`.