diff --git a/CHANGELOG.md b/CHANGELOG.md index e02eaf5e48..b4c980482d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.14.5 (2026-04-23) + +- Fixed note script compilation: all note scripts are now compiled as libraries ([#2822](https://github.com/0xMiden/protocol/pull/2822)). + +## 0.14.4 (2026-04-09) + +- Fixed AggLayer `write_mint_note_storage` stack padding before loading the mint serial number ([#2749](https://github.com/0xMiden/protocol/pull/2749)). + ## 0.14.3 (2026-04-07) - [BREAKING] Updated for compatibility with miden-vm v0.22.1 (`Arc` return types, `MastArtifact`/`PackageKind` removal) ([#2742](https://github.com/0xMiden/protocol/pull/2742)). diff --git a/Cargo.lock b/Cargo.lock index 60f048bd64..c19ab28e38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -282,10 +282,12 @@ version = "0.1.0" dependencies = [ "anyhow", "criterion 0.6.0", + "miden-agglayer", "miden-protocol", "miden-standards", "miden-testing", "miden-tx", + "rand 0.9.3", "serde", "serde_json", "tokio", @@ -1534,7 +1536,7 @@ dependencies = [ [[package]] name = "miden-agglayer" -version = "0.14.3" +version = "0.14.5" dependencies = [ "alloy-sol-types", "fs-err", @@ -1611,7 +1613,7 @@ dependencies = [ [[package]] name = "miden-block-prover" -version = "0.14.3" +version = "0.14.5" dependencies = [ "miden-protocol", "thiserror", @@ -1686,7 +1688,7 @@ dependencies = [ "p3-miden-lifted-stark", "p3-symmetric", "p3-util", - "rand 0.9.2", + "rand 0.9.3", "rand_chacha", "rand_core 0.9.5", "rand_hc", @@ -1739,7 +1741,7 @@ dependencies = [ "p3-field", "p3-goldilocks", "paste", - "rand 0.10.0", + "rand 0.10.1", "serde", "subtle", "thiserror", @@ -1856,7 +1858,7 @@ dependencies = [ [[package]] name = "miden-protocol" -version = "0.14.3" +version = "0.14.5" dependencies = [ "anyhow", "assert_matches", @@ -1877,7 +1879,7 @@ dependencies = [ "miden-utils-sync", "miden-verifier", "pprof", - "rand 0.9.2", + "rand 0.9.3", "rand_chacha", "rand_xoshiro", "regex", @@ -1892,7 +1894,7 @@ dependencies = [ [[package]] name = "miden-protocol-macros" -version = "0.14.3" +version = "0.14.5" dependencies = [ "miden-protocol", "proc-macro2", @@ -1930,7 +1932,7 @@ dependencies = [ [[package]] name = "miden-standards" -version = "0.14.3" +version = "0.14.5" dependencies = [ "anyhow", "assert_matches", @@ -1941,7 +1943,7 @@ dependencies = [ "miden-processor", "miden-protocol", "miden-standards", - "rand 0.9.2", + "rand 0.9.3", "regex", "thiserror", "walkdir", @@ -1949,7 +1951,7 @@ dependencies = [ [[package]] name = "miden-testing" -version = "0.14.3" +version = "0.14.5" dependencies = [ "anyhow", "assert_matches", @@ -1966,7 +1968,7 @@ dependencies = [ "miden-tx", "miden-tx-batch-prover", "primitive-types", - "rand 0.9.2", + "rand 0.9.3", "rand_chacha", "rstest", "serde", @@ -1977,7 +1979,7 @@ dependencies = [ [[package]] name = "miden-tx" -version = "0.14.3" +version = "0.14.5" dependencies = [ "anyhow", "assert_matches", @@ -1994,7 +1996,7 @@ dependencies = [ [[package]] name = "miden-tx-batch-prover" -version = "0.14.3" +version = "0.14.5" dependencies = [ "miden-protocol", "miden-tx", @@ -2335,7 +2337,7 @@ dependencies = [ "p3-maybe-rayon", "p3-util", "paste", - "rand 0.10.0", + "rand 0.10.1", "serde", "tracing", ] @@ -2356,7 +2358,7 @@ dependencies = [ "p3-symmetric", "p3-util", "paste", - "rand 0.10.0", + "rand 0.10.1", "serde", ] @@ -2381,7 +2383,7 @@ dependencies = [ "p3-field", "p3-maybe-rayon", "p3-util", - "rand 0.10.0", + "rand 0.10.1", "serde", "tracing", ] @@ -2405,7 +2407,7 @@ dependencies = [ "p3-field", "p3-symmetric", "p3-util", - "rand 0.10.0", + "rand 0.10.1", ] [[package]] @@ -2436,7 +2438,7 @@ dependencies = [ "p3-miden-lmcs", "p3-miden-transcript", "p3-util", - "rand 0.10.0", + "rand 0.10.1", "thiserror", "tracing", ] @@ -2476,7 +2478,7 @@ dependencies = [ "p3-miden-transcript", "p3-symmetric", "p3-util", - "rand 0.10.0", + "rand 0.10.1", "serde", "thiserror", "tracing", @@ -2522,7 +2524,7 @@ dependencies = [ "p3-symmetric", "p3-util", "paste", - "rand 0.10.0", + "rand 0.10.1", "serde", "spin 0.10.0", "tracing", @@ -2536,7 +2538,7 @@ checksum = "6a018b618e3fa0aec8be933b1d8e404edd23f46991f6bf3f5c2f3f95e9413fe9" dependencies = [ "p3-field", "p3-symmetric", - "rand 0.10.0", + "rand 0.10.1", ] [[package]] @@ -2549,7 +2551,7 @@ dependencies = [ "p3-mds", "p3-symmetric", "p3-util", - "rand 0.10.0", + "rand 0.10.1", ] [[package]] @@ -2810,7 +2812,7 @@ checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" dependencies = [ "bitflags 2.11.0", "num-traits", - "rand 0.9.2", + "rand 0.9.3", "rand_chacha", "rand_xorshift", "regex-syntax", @@ -2883,9 +2885,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "7ec095654a25171c2124e9e3393a930bddbffdc939556c914957a4c3e0a87166" dependencies = [ "rand_chacha", "rand_core 0.9.5", @@ -2893,9 +2895,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ "rand_core 0.10.0", ] @@ -3081,7 +3083,7 @@ checksum = "c141e807189ad38a07276942c6623032d3753c8859c146104ac2e4d68865945a" dependencies = [ "proptest", "rand 0.8.5", - "rand 0.9.2", + "rand 0.9.3", "ruint-macro", "serde_core", "valuable", diff --git a/Cargo.toml b/Cargo.toml index 5fc25f6118..da664076ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ homepage = "https://miden.xyz" license = "MIT" repository = "https://github.com/0xMiden/protocol" rust-version = "1.90" -version = "0.14.3" +version = "0.14.5" [profile.release] codegen-units = 1 diff --git a/bin/bench-note-checker/src/lib.rs b/bin/bench-note-checker/src/lib.rs index bb2c4cd546..0dc338dbfe 100644 --- a/bin/bench-note-checker/src/lib.rs +++ b/bin/bench-note-checker/src/lib.rs @@ -85,7 +85,7 @@ pub fn setup_mixed_notes_benchmark(config: MixedNotesConfig) -> anyhow::Result Result { .disable_debug_mode() .build() } + +// CLAIM NOTE SETUPS +// ================================================================================================ + +/// Sets up and returns the transaction context for executing a CLAIM note against the bridge +/// account. +/// +/// This requires executing prerequisite transactions (CONFIG_AGG_BRIDGE and UPDATE_GER) during +/// setup to prepare the bridge account state. Only the returned CLAIM transaction context is +/// benchmarked — the prerequisite transactions are not included in cycle/time measurements. +/// +/// The `data_source` parameter selects between L1-to-Miden and L2-to-Miden test vectors. +pub async fn tx_consume_claim_note(data_source: ClaimDataSource) -> Result { + let mut builder = MockChain::builder(); + + // CREATE BRIDGE ADMIN ACCOUNT (sends CONFIG_AGG_BRIDGE notes) + let bridge_admin = builder.add_existing_wallet(Auth::BasicAuth { + auth_scheme: AuthScheme::Falcon512Poseidon2, + })?; + + // CREATE GER MANAGER ACCOUNT (sends the UPDATE_GER note) + let ger_manager = builder.add_existing_wallet(Auth::BasicAuth { + auth_scheme: AuthScheme::Falcon512Poseidon2, + })?; + + // CREATE BRIDGE ACCOUNT + let bridge_seed = builder.rng_mut().draw_word(); + let bridge_account = + create_existing_bridge_account(bridge_seed, bridge_admin.id(), ger_manager.id()); + builder.add_account(bridge_account.clone())?; + + // GET CLAIM DATA FROM JSON + let (proof_data, leaf_data, ger, _cgi_chain_hash) = data_source.get_data(); + + // CREATE AGGLAYER FAUCET ACCOUNT + let token_symbol = "AGG"; + let decimals = 8u8; + let max_supply = Felt::new(FungibleAsset::MAX_AMOUNT); + let agglayer_faucet_seed = builder.rng_mut().draw_word(); + + let origin_token_address = leaf_data.origin_token_address; + let origin_network = leaf_data.origin_network; + let scale = 10u8; + + let agglayer_faucet = create_existing_agglayer_faucet( + agglayer_faucet_seed, + token_symbol, + decimals, + max_supply, + Felt::ZERO, + bridge_account.id(), + &origin_token_address, + origin_network, + scale, + leaf_data.metadata_hash, + ); + builder.add_account(agglayer_faucet.clone())?; + + // CREATE SENDER ACCOUNT (for creating the claim note) + let sender_account_builder = + miden_protocol::account::Account::builder(builder.rng_mut().random()) + .with_component(miden_standards::account::wallets::BasicWallet); + let sender_account = builder.add_account_from_builder( + Auth::IncrNonce, + sender_account_builder, + miden_testing::AccountState::Exists, + )?; + + // CREATE CLAIM NOTE + let miden_claim_amount = leaf_data + .amount + .scale_to_token_amount(scale as u32) + .expect("amount should scale successfully"); + + let claim_inputs = ClaimNoteStorage { + proof_data, + leaf_data, + miden_claim_amount, + }; + + let claim_note = create_claim_note( + claim_inputs, + bridge_account.id(), + sender_account.id(), + builder.rng_mut(), + )?; + + builder.add_output_note(RawOutputNote::Full(claim_note.clone())); + + // CREATE CONFIG_AGG_BRIDGE NOTE + let config_note = ConfigAggBridgeNote::create( + agglayer_faucet.id(), + &origin_token_address, + bridge_admin.id(), + bridge_account.id(), + builder.rng_mut(), + )?; + builder.add_output_note(RawOutputNote::Full(config_note.clone())); + + // CREATE UPDATE_GER NOTE + let update_ger_note = + UpdateGerNote::create(ger, ger_manager.id(), bridge_account.id(), builder.rng_mut())?; + builder.add_output_note(RawOutputNote::Full(update_ger_note.clone())); + + // BUILD MOCK CHAIN + let mut mock_chain = builder.build()?; + + // TX0: EXECUTE CONFIG_AGG_BRIDGE NOTE TO REGISTER FAUCET IN BRIDGE + let config_tx_context = mock_chain + .build_tx_context(bridge_account.id(), &[config_note.id()], &[])? + .build()?; + let config_executed = config_tx_context.execute().await?; + + mock_chain.add_pending_executed_transaction(&config_executed)?; + mock_chain.prove_next_block()?; + + // TX1: EXECUTE UPDATE_GER NOTE TO STORE GER IN BRIDGE ACCOUNT + let update_ger_tx_context = mock_chain + .build_tx_context(bridge_account.id(), &[update_ger_note.id()], &[])? + .build()?; + let update_ger_executed = update_ger_tx_context.execute().await?; + + mock_chain.add_pending_executed_transaction(&update_ger_executed)?; + mock_chain.prove_next_block()?; + + // TX2: BUILD CLAIM NOTE TRANSACTION CONTEXT (ready to execute) + let faucet_foreign_inputs = mock_chain.get_foreign_account_inputs(agglayer_faucet.id())?; + let claim_tx_context = mock_chain + .build_tx_context(bridge_account.id(), &[], &[claim_note])? + .foreign_accounts(vec![faucet_foreign_inputs]) + .disable_debug_mode() + .build()?; + + Ok(claim_tx_context) +} + +// B2AGG NOTE SETUPS +// ================================================================================================ + +/// Sets up and returns the transaction context for executing a B2AGG (bridge-out) note against +/// the bridge account. +/// +/// This requires executing a prerequisite CONFIG_AGG_BRIDGE transaction during setup to register +/// the faucet in the bridge. Only the returned B2AGG transaction context is benchmarked — the +/// prerequisite CONFIG_AGG_BRIDGE transaction is not included in cycle/time measurements. +/// +/// The setup uses the first entry from the MTF (Merkle Tree Frontier) test vectors for destination +/// data. +pub async fn tx_consume_b2agg_note() -> Result { + let vectors = &*miden_agglayer::testing::SOLIDITY_MTF_VECTORS; + + let mut builder = MockChain::builder(); + + // CREATE BRIDGE ADMIN ACCOUNT (sends CONFIG_AGG_BRIDGE notes) + let bridge_admin = builder.add_existing_wallet(Auth::BasicAuth { + auth_scheme: AuthScheme::Falcon512Poseidon2, + })?; + + // CREATE GER MANAGER ACCOUNT (not used in bridge-out, but required for bridge creation) + let ger_manager = builder.add_existing_wallet(Auth::BasicAuth { + auth_scheme: AuthScheme::Falcon512Poseidon2, + })?; + + // CREATE BRIDGE ACCOUNT + let bridge_account = create_existing_bridge_account( + builder.rng_mut().draw_word(), + bridge_admin.id(), + ger_manager.id(), + ); + builder.add_account(bridge_account.clone())?; + + // CREATE AGGLAYER FAUCET ACCOUNT (with conversion metadata for FPI) + let origin_token_address = EthAddress::from_hex(&vectors.origin_token_address) + .expect("valid shared origin token address"); + let origin_network = 64u32; + let scale = 0u8; + let bridge_amount: u64 = vectors.amounts[0].parse().expect("valid amount decimal string"); + + let faucet = create_existing_agglayer_faucet( + builder.rng_mut().draw_word(), + "AGG", + 8, + Felt::new(FungibleAsset::MAX_AMOUNT), + Felt::new(bridge_amount), + bridge_account.id(), + &origin_token_address, + origin_network, + scale, + MetadataHash::from_token_info("AGG", "AGG", 8), + ); + builder.add_account(faucet.clone())?; + + // CREATE CONFIG_AGG_BRIDGE NOTE (registers faucet + token address in bridge) + let config_note = ConfigAggBridgeNote::create( + faucet.id(), + &origin_token_address, + bridge_admin.id(), + bridge_account.id(), + builder.rng_mut(), + )?; + builder.add_output_note(RawOutputNote::Full(config_note.clone())); + + // CREATE B2AGG NOTE + let destination_network = vectors.destination_networks[0]; + let destination_address = + EthAddress::from_hex(&vectors.destination_addresses[0]).expect("valid destination address"); + let bridge_asset: Asset = FungibleAsset::new(faucet.id(), bridge_amount)?.into(); + let b2agg_note = B2AggNote::create( + destination_network, + destination_address, + NoteAssets::new(vec![bridge_asset])?, + bridge_account.id(), + faucet.id(), + builder.rng_mut(), + )?; + builder.add_output_note(RawOutputNote::Full(b2agg_note.clone())); + + // BUILD MOCK CHAIN + let mut mock_chain = builder.build()?; + mock_chain.prove_next_block()?; + + // TX0: EXECUTE CONFIG_AGG_BRIDGE NOTE TO REGISTER FAUCET IN BRIDGE + let config_executed = mock_chain + .build_tx_context(bridge_account.id(), &[config_note.id()], &[])? + .build()? + .execute() + .await?; + mock_chain.add_pending_executed_transaction(&config_executed)?; + mock_chain.prove_next_block()?; + + // TX1: BUILD B2AGG NOTE TRANSACTION CONTEXT (ready to execute) + let burn_note_script = StandardNote::BURN.script(); + let foreign_account_inputs = mock_chain.get_foreign_account_inputs(faucet.id())?; + let b2agg_tx_context = mock_chain + .build_tx_context(bridge_account.id(), &[b2agg_note.id()], &[])? + .add_note_script(burn_note_script) + .foreign_accounts(vec![foreign_account_inputs]) + .disable_debug_mode() + .build()?; + + Ok(b2agg_tx_context) +} diff --git a/bin/bench-transaction/src/cycle_counting_benchmarks/mod.rs b/bin/bench-transaction/src/cycle_counting_benchmarks/mod.rs index 24801e09ba..39e24c64d5 100644 --- a/bin/bench-transaction/src/cycle_counting_benchmarks/mod.rs +++ b/bin/bench-transaction/src/cycle_counting_benchmarks/mod.rs @@ -7,6 +7,9 @@ pub enum ExecutionBenchmark { ConsumeSingleP2ID, ConsumeTwoP2ID, CreateSingleP2ID, + ConsumeClaimNoteL1ToMiden, + ConsumeClaimNoteL2ToMiden, + ConsumeB2AggNote, } impl fmt::Display for ExecutionBenchmark { @@ -15,6 +18,15 @@ impl fmt::Display for ExecutionBenchmark { ExecutionBenchmark::ConsumeSingleP2ID => write!(f, "consume single P2ID note"), ExecutionBenchmark::ConsumeTwoP2ID => write!(f, "consume two P2ID notes"), ExecutionBenchmark::CreateSingleP2ID => write!(f, "create single P2ID note"), + ExecutionBenchmark::ConsumeClaimNoteL1ToMiden => { + write!(f, "consume CLAIM note (L1 to Miden)") + }, + ExecutionBenchmark::ConsumeClaimNoteL2ToMiden => { + write!(f, "consume CLAIM note (L2 to Miden)") + }, + ExecutionBenchmark::ConsumeB2AggNote => { + write!(f, "consume B2AGG note (bridge-out)") + }, } } } diff --git a/bin/bench-transaction/src/main.rs b/bin/bench-transaction/src/main.rs index 651be7993b..2b9dae9848 100644 --- a/bin/bench-transaction/src/main.rs +++ b/bin/bench-transaction/src/main.rs @@ -7,6 +7,9 @@ use miden_protocol::transaction::TransactionMeasurements; mod context_setups; use context_setups::{ + ClaimDataSource, + tx_consume_b2agg_note, + tx_consume_claim_note, tx_consume_single_p2id_note, tx_consume_two_p2id_notes, tx_create_single_p2id_note, @@ -49,6 +52,33 @@ async fn main() -> Result<()> { .map(TransactionMeasurements::from)? .into(), ), + ( + ExecutionBenchmark::ConsumeClaimNoteL1ToMiden, + tx_consume_claim_note(ClaimDataSource::SimulatedL1ToMiden) + .await? + .execute() + .await + .map(TransactionMeasurements::from)? + .into(), + ), + ( + ExecutionBenchmark::ConsumeClaimNoteL2ToMiden, + tx_consume_claim_note(ClaimDataSource::SimulatedL2ToMiden) + .await? + .execute() + .await + .map(TransactionMeasurements::from)? + .into(), + ), + ( + ExecutionBenchmark::ConsumeB2AggNote, + tx_consume_b2agg_note() + .await? + .execute() + .await + .map(TransactionMeasurements::from)? + .into(), + ), ]; // store benchmark results in the JSON file diff --git a/bin/bench-transaction/src/time_counting_benchmarks/prove.rs b/bin/bench-transaction/src/time_counting_benchmarks/prove.rs index edad46c21e..62749a9df6 100644 --- a/bin/bench-transaction/src/time_counting_benchmarks/prove.rs +++ b/bin/bench-transaction/src/time_counting_benchmarks/prove.rs @@ -2,7 +2,13 @@ use std::hint::black_box; use std::time::Duration; use anyhow::Result; -use bench_transaction::context_setups::{tx_consume_single_p2id_note, tx_consume_two_p2id_notes}; +use bench_transaction::context_setups::{ + ClaimDataSource, + tx_consume_b2agg_note, + tx_consume_claim_note, + tx_consume_single_p2id_note, + tx_consume_two_p2id_notes, +}; use criterion::{BatchSize, Criterion, SamplingMode, criterion_group, criterion_main}; use miden_protocol::transaction::{ExecutedTransaction, ProvenTransaction}; use miden_tx::LocalTransactionProver; @@ -14,12 +20,24 @@ const BENCH_GROUP_EXECUTE: &str = "Execute transaction"; const BENCH_EXECUTE_TX_CONSUME_SINGLE_P2ID: &str = "Execute transaction which consumes single P2ID note"; const BENCH_EXECUTE_TX_CONSUME_TWO_P2ID: &str = "Execute transaction which consumes two P2ID notes"; +const BENCH_EXECUTE_TX_CONSUME_CLAIM_L1: &str = + "Execute transaction which consumes CLAIM note (L1 to Miden)"; +const BENCH_EXECUTE_TX_CONSUME_CLAIM_L2: &str = + "Execute transaction which consumes CLAIM note (L2 to Miden)"; +const BENCH_EXECUTE_TX_CONSUME_B2AGG: &str = + "Execute transaction which consumes B2AGG note (bridge-out)"; const BENCH_GROUP_EXECUTE_AND_PROVE: &str = "Execute and prove transaction"; const BENCH_EXECUTE_AND_PROVE_TX_CONSUME_SINGLE_P2ID: &str = "Execute and prove transaction which consumes single P2ID note"; const BENCH_EXECUTE_AND_PROVE_TX_CONSUME_TWO_P2ID: &str = "Execute and prove transaction which consumes two P2ID notes"; +const BENCH_EXECUTE_AND_PROVE_TX_CONSUME_CLAIM_L1: &str = + "Execute and prove transaction which consumes CLAIM note (L1 to Miden)"; +const BENCH_EXECUTE_AND_PROVE_TX_CONSUME_CLAIM_L2: &str = + "Execute and prove transaction which consumes CLAIM note (L2 to Miden)"; +const BENCH_EXECUTE_AND_PROVE_TX_CONSUME_B2AGG: &str = + "Execute and prove transaction which consumes B2AGG note (bridge-out)"; // CORE PROVING BENCHMARKS // ================================================================================================ @@ -67,6 +85,63 @@ fn core_benchmarks(c: &mut Criterion) { ); }); + execute_group.bench_function(BENCH_EXECUTE_TX_CONSUME_CLAIM_L1, |b| { + b.to_async(tokio::runtime::Builder::new_current_thread().build().unwrap()) + .iter_batched( + || { + // prepare the transaction context (async setup) + let rt = tokio::runtime::Builder::new_current_thread() + .build() + .expect("failed to build tokio runtime for setup"); + rt.block_on(tx_consume_claim_note(ClaimDataSource::SimulatedL1ToMiden)) + .expect("failed to create a context which consumes CLAIM note (L1)") + }, + |tx_context| async move { + // benchmark the transaction execution + black_box(tx_context.execute().await) + }, + BatchSize::SmallInput, + ); + }); + + execute_group.bench_function(BENCH_EXECUTE_TX_CONSUME_CLAIM_L2, |b| { + b.to_async(tokio::runtime::Builder::new_current_thread().build().unwrap()) + .iter_batched( + || { + // prepare the transaction context (async setup) + let rt = tokio::runtime::Builder::new_current_thread() + .build() + .expect("failed to build tokio runtime for setup"); + rt.block_on(tx_consume_claim_note(ClaimDataSource::SimulatedL2ToMiden)) + .expect("failed to create a context which consumes CLAIM note (L2)") + }, + |tx_context| async move { + // benchmark the transaction execution + black_box(tx_context.execute().await) + }, + BatchSize::SmallInput, + ); + }); + + execute_group.bench_function(BENCH_EXECUTE_TX_CONSUME_B2AGG, |b| { + b.to_async(tokio::runtime::Builder::new_current_thread().build().unwrap()) + .iter_batched( + || { + // prepare the transaction context (async setup) + let rt = tokio::runtime::Builder::new_current_thread() + .build() + .expect("failed to build tokio runtime for setup"); + rt.block_on(tx_consume_b2agg_note()) + .expect("failed to create a context which consumes B2AGG note") + }, + |tx_context| async move { + // benchmark the transaction execution + black_box(tx_context.execute().await) + }, + BatchSize::SmallInput, + ); + }); + execute_group.finish(); // EXECUTE AND PROVE GROUP @@ -127,6 +202,87 @@ fn core_benchmarks(c: &mut Criterion) { ); }); + execute_and_prove_group.bench_function(BENCH_EXECUTE_AND_PROVE_TX_CONSUME_CLAIM_L1, |b| { + b.to_async(tokio::runtime::Builder::new_current_thread().build().unwrap()) + .iter_batched( + || { + // prepare the transaction context (async setup) + let rt = tokio::runtime::Builder::new_current_thread() + .build() + .expect("failed to build tokio runtime for setup"); + rt.block_on(tx_consume_claim_note(ClaimDataSource::SimulatedL1ToMiden)) + .expect("failed to create a context which consumes CLAIM note (L1)") + }, + |tx_context| async move { + // benchmark the transaction execution and proving + black_box( + prove_transaction( + tx_context + .execute() + .await + .expect("execution of the CLAIM note (L1) consumption tx failed"), + ) + .await, + ) + }, + BatchSize::SmallInput, + ); + }); + + execute_and_prove_group.bench_function(BENCH_EXECUTE_AND_PROVE_TX_CONSUME_CLAIM_L2, |b| { + b.to_async(tokio::runtime::Builder::new_current_thread().build().unwrap()) + .iter_batched( + || { + // prepare the transaction context (async setup) + let rt = tokio::runtime::Builder::new_current_thread() + .build() + .expect("failed to build tokio runtime for setup"); + rt.block_on(tx_consume_claim_note(ClaimDataSource::SimulatedL2ToMiden)) + .expect("failed to create a context which consumes CLAIM note (L2)") + }, + |tx_context| async move { + // benchmark the transaction execution and proving + black_box( + prove_transaction( + tx_context + .execute() + .await + .expect("execution of the CLAIM note (L2) consumption tx failed"), + ) + .await, + ) + }, + BatchSize::SmallInput, + ); + }); + + execute_and_prove_group.bench_function(BENCH_EXECUTE_AND_PROVE_TX_CONSUME_B2AGG, |b| { + b.to_async(tokio::runtime::Builder::new_current_thread().build().unwrap()) + .iter_batched( + || { + // prepare the transaction context (async setup) + let rt = tokio::runtime::Builder::new_current_thread() + .build() + .expect("failed to build tokio runtime for setup"); + rt.block_on(tx_consume_b2agg_note()) + .expect("failed to create a context which consumes B2AGG note") + }, + |tx_context| async move { + // benchmark the transaction execution and proving + black_box( + prove_transaction( + tx_context + .execute() + .await + .expect("execution of the B2AGG note consumption tx failed"), + ) + .await, + ) + }, + BatchSize::SmallInput, + ); + }); + execute_and_prove_group.finish(); } diff --git a/crates/miden-agglayer/Cargo.toml b/crates/miden-agglayer/Cargo.toml index efe8b903c1..16e4ae2afa 100644 --- a/crates/miden-agglayer/Cargo.toml +++ b/crates/miden-agglayer/Cargo.toml @@ -17,7 +17,7 @@ bench = false [features] default = ["std"] std = ["miden-assembly/std", "miden-core/std"] -testing = ["miden-protocol/testing"] +testing = ["dep:serde", "dep:serde_json", "miden-protocol/testing"] [dependencies] # Miden dependencies @@ -36,6 +36,10 @@ thiserror = { workspace = true } # Crypto miden-crypto = { workspace = true } +# Optional testing dependencies +serde = { features = ["alloc", "derive"], optional = true, workspace = true } +serde_json = { default-features = false, features = ["alloc"], optional = true, version = "1.0" } + [dev-dependencies] miden-agglayer = { features = ["testing"], path = "." } serde = { features = ["derive"], workspace = true } diff --git a/crates/miden-agglayer/SPEC.md b/crates/miden-agglayer/SPEC.md index cddf6461fd..3a3038aa46 100644 --- a/crates/miden-agglayer/SPEC.md +++ b/crates/miden-agglayer/SPEC.md @@ -402,7 +402,7 @@ Keccak preimage format directly — the felt value does **not** equal the numeri | Field | Value | |-------|-------| | `serial_num` | Random (`rng.draw_word()`) | -| `script` | `B2AGG.masb` | +| `script` | `B2AGG.masl` | | `storage` | 6 felts -- see layout below | **Storage layout (6 felts):** @@ -461,7 +461,7 @@ token registry, and creates a MINT note targeting the faucet. | Field | Value | |-------|-------| | `serial_num` | Random (`rng.draw_word()`) | -| `script` | `CLAIM.masb` | +| `script` | `CLAIM.masl` | | `storage` | 569 felts -- see layout below | **Storage layout (569 felts):** @@ -530,7 +530,7 @@ The storage is divided into three logical regions: proof data (felts 0-535), lea | Field | Value | |-------|-------| | `serial_num` | Random (`rng.draw_word()`) | -| `script` | `CONFIG_AGG_BRIDGE.masb` | +| `script` | `CONFIG_AGG_BRIDGE.masl` | | `storage` | 7 felts -- see layout below | **Storage layout (7 felts):** @@ -577,7 +577,7 @@ CLAIM notes can be verified against it. | Field | Value | |-------|-------| | `serial_num` | Random (`rng.draw_word()`) | -| `script` | `UPDATE_GER.masb` | +| `script` | `UPDATE_GER.masl` | | `storage` | 8 felts -- see layout below | **Storage layout (8 felts):** diff --git a/crates/miden-agglayer/asm/agglayer/bridge/bridge_in.masm b/crates/miden-agglayer/asm/agglayer/bridge/bridge_in.masm index f101c90b40..0371a60e1e 100644 --- a/crates/miden-agglayer/asm/agglayer/bridge/bridge_in.masm +++ b/crates/miden-agglayer/asm/agglayer/bridge/bridge_in.masm @@ -889,7 +889,7 @@ proc write_mint_note_storage # => [] # Write SERIAL_NUM (PROOF_DATA_KEY) to MINT note storage [12..15] - mem_loadw_be.CLAIM_PROOF_DATA_KEY_MEM_ADDR + padw mem_loadw_be.CLAIM_PROOF_DATA_KEY_MEM_ADDR # => [SERIAL_NUM] mem_storew_le.MINT_NOTE_STORAGE_OUTPUT_SERIAL_NUM dropw diff --git a/crates/miden-agglayer/asm/note_scripts/B2AGG.masm b/crates/miden-agglayer/asm/note_scripts/B2AGG.masm index 0ae42e52e8..4bd5b321b9 100644 --- a/crates/miden-agglayer/asm/note_scripts/B2AGG.masm +++ b/crates/miden-agglayer/asm/note_scripts/B2AGG.masm @@ -50,7 +50,8 @@ const ERR_B2AGG_TARGET_ACCOUNT_MISMATCH="B2AGG note attachment target account do #! - The note does not contain exactly 6 storage items. #! - The note does not contain exactly 1 asset. #! - The note attachment does not target the consuming account. -begin +@note_script +pub proc main dropw # => [pad(16)] diff --git a/crates/miden-agglayer/asm/note_scripts/CLAIM.masm b/crates/miden-agglayer/asm/note_scripts/CLAIM.masm index 715ab6d7f9..292eec2128 100644 --- a/crates/miden-agglayer/asm/note_scripts/CLAIM.masm +++ b/crates/miden-agglayer/asm/note_scripts/CLAIM.masm @@ -75,7 +75,8 @@ const ERR_CLAIM_TARGET_ACCT_MISMATCH = "CLAIM note attachment target account doe #! Panics if: #! - account does not expose claim procedure. #! - note attachment target account does not match the consuming account. -begin +@note_script +pub proc main dropw # => [pad(16)] diff --git a/crates/miden-agglayer/asm/note_scripts/CONFIG_AGG_BRIDGE.masm b/crates/miden-agglayer/asm/note_scripts/CONFIG_AGG_BRIDGE.masm index 98df2690fd..532aa49ade 100644 --- a/crates/miden-agglayer/asm/note_scripts/CONFIG_AGG_BRIDGE.masm +++ b/crates/miden-agglayer/asm/note_scripts/CONFIG_AGG_BRIDGE.masm @@ -47,7 +47,8 @@ const ERR_CONFIG_AGG_BRIDGE_TARGET_ACCOUNT_MISMATCH = "CONFIG_AGG_BRIDGE note at #! - The note attachment target account does not match the consuming bridge account. #! - The note does not contain exactly 7 storage items. #! - The account does not expose the register_faucet procedure. -begin +@note_script +pub proc main dropw # => [pad(16)] diff --git a/crates/miden-agglayer/asm/note_scripts/UPDATE_GER.masm b/crates/miden-agglayer/asm/note_scripts/UPDATE_GER.masm index 070d181ef1..e9168144a8 100644 --- a/crates/miden-agglayer/asm/note_scripts/UPDATE_GER.masm +++ b/crates/miden-agglayer/asm/note_scripts/UPDATE_GER.masm @@ -39,7 +39,8 @@ const ERR_UPDATE_GER_TARGET_ACCOUNT_MISMATCH = "UPDATE_GER note attachment targe #! - account does not expose update_ger procedure. #! - target account ID does not match the consuming account ID. #! - number of note storage items is not exactly 8. -begin +@note_script +pub proc main dropw # => [pad(16)] diff --git a/crates/miden-agglayer/build.rs b/crates/miden-agglayer/build.rs index 4449d6d849..da8a994a50 100644 --- a/crates/miden-agglayer/build.rs +++ b/crates/miden-agglayer/build.rs @@ -5,7 +5,6 @@ use std::sync::Arc; use fs_err as fs; use miden_assembly::diagnostics::{IntoDiagnostic, NamedSource, Result, WrapErr}; -use miden_assembly::serde::Serializable; use miden_assembly::{Assembler, Library, Report}; use miden_crypto::hash::keccak::{Keccak256, Keccak256Digest}; use miden_protocol::account::{ @@ -43,7 +42,7 @@ const AGGLAYER_GLOBAL_CONSTANTS_FILE_NAME: &str = "agglayer_constants.rs"; /// Read and parse the contents from `./asm`. /// - Compiles the contents of asm/agglayer directory into a single agglayer.masl library. /// - Compiles the contents of asm/components directory into individual per-component .masl files. -/// - Compiles the contents of asm/note_scripts directory into individual .masb files. +/// - Compiles the contents of asm/note_scripts directory into individual `.masl` libraries. fn main() -> Result<()> { // re-build when the MASM code changes println!("cargo::rerun-if-changed={ASM_DIR}/"); @@ -124,16 +123,15 @@ fn compile_agglayer_lib( // COMPILE EXECUTABLE MODULES // ================================================================================================ -/// Reads all MASM files from the "{source_dir}", complies each file individually into a MASB -/// file, and stores the compiled files into the "{target_dir}". -/// -/// The source files are expected to contain executable programs. +/// Reads all MASM files from `{source_dir}`, compiles each file as a note script library with +/// [`Assembler::assemble_library`], and writes the serialized library as `.masl` via +/// [`Library::write_to_file`]. fn compile_note_scripts( source_dir: &Path, - target_dir: &Path, + note_scripts_target_dir: &Path, mut assembler: Assembler, ) -> Result<()> { - fs::create_dir_all(target_dir) + fs::create_dir_all(note_scripts_target_dir) .into_diagnostic() .wrap_err("failed to create note_scripts directory")?; @@ -141,22 +139,22 @@ fn compile_note_scripts( let standards_lib = miden_standards::StandardsLib::default(); assembler.link_static_library(standards_lib)?; - for masm_file_path in shared::get_masm_files(source_dir).unwrap() { - // read the MASM file, parse it, and serialize the parsed AST to bytes - let code = assembler.clone().assemble_program(masm_file_path.clone())?; - - let bytes = code.to_bytes(); + for note_file_path in shared::get_masm_files(source_dir).unwrap() { + // compile the note script library from the provided MASM file + let note_library = assembler.clone().assemble_library([note_file_path.clone()])?; - let masm_file_name = masm_file_path + let note_file_name = note_file_path .file_name() .expect("file name should exist") .to_str() .ok_or_else(|| Report::msg("failed to convert file name to &str"))?; - let mut masb_file_path = target_dir.join(masm_file_name); + let mut masl_file_path = note_scripts_target_dir.join(note_file_name); + masl_file_path.set_extension(Library::LIBRARY_EXTENSION); - // write the binary MASB to the output dir - masb_file_path.set_extension("masb"); - fs::write(masb_file_path, bytes).unwrap(); + // write the note script library to the output dir + note_library + .write_to_file(&masl_file_path) + .map_err(|e| Report::msg(format!("{e:#}")))?; } Ok(()) } diff --git a/crates/miden-agglayer/src/b2agg_note.rs b/crates/miden-agglayer/src/b2agg_note.rs index 5309449d93..82820c2082 100644 --- a/crates/miden-agglayer/src/b2agg_note.rs +++ b/crates/miden-agglayer/src/b2agg_note.rs @@ -6,8 +6,8 @@ use alloc::string::ToString; use alloc::vec::Vec; +use miden_assembly::Library; use miden_assembly::serde::Deserializable; -use miden_core::program::Program; use miden_core::{Felt, Word}; use miden_protocol::account::AccountId; use miden_protocol::crypto::rand::FeltRng; @@ -32,9 +32,10 @@ use crate::EthAddress; // Initialize the B2AGG note script only once static B2AGG_SCRIPT: LazyLock = LazyLock::new(|| { - let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/note_scripts/B2AGG.masb")); - let program = Program::read_from_bytes(bytes).expect("shipped B2AGG script is well-formed"); - NoteScript::new(program) + let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/note_scripts/B2AGG.masl")); + let library = + Library::read_from_bytes(bytes).expect("shipped B2AGG script library is well-formed"); + NoteScript::from_library(&library).expect("shipped B2AGG script is well-formed") }); // B2AGG NOTE diff --git a/crates/miden-agglayer/src/config_note.rs b/crates/miden-agglayer/src/config_note.rs index efdd9f6663..f5c8b4ef01 100644 --- a/crates/miden-agglayer/src/config_note.rs +++ b/crates/miden-agglayer/src/config_note.rs @@ -9,6 +9,7 @@ use alloc::string::ToString; use alloc::vec; use alloc::vec::Vec; +use miden_assembly::Library; use miden_assembly::serde::Deserializable; use miden_core::{Felt, Word}; use miden_protocol::account::AccountId; @@ -24,7 +25,6 @@ use miden_protocol::note::{ NoteStorage, NoteType, }; -use miden_protocol::vm::Program; use miden_standards::note::{NetworkAccountTarget, NoteExecutionHint}; use miden_utils_sync::LazyLock; @@ -36,10 +36,10 @@ use crate::EthAddress; // Initialize the CONFIG_AGG_BRIDGE note script only once static CONFIG_AGG_BRIDGE_SCRIPT: LazyLock = LazyLock::new(|| { let bytes = - include_bytes!(concat!(env!("OUT_DIR"), "/assets/note_scripts/CONFIG_AGG_BRIDGE.masb")); - let program = - Program::read_from_bytes(bytes).expect("shipped CONFIG_AGG_BRIDGE script is well-formed"); - NoteScript::new(program) + include_bytes!(concat!(env!("OUT_DIR"), "/assets/note_scripts/CONFIG_AGG_BRIDGE.masl")); + let library = Library::read_from_bytes(bytes) + .expect("shipped CONFIG_AGG_BRIDGE script library is well-formed"); + NoteScript::from_library(&library).expect("shipped CONFIG_AGG_BRIDGE script is well-formed") }); // CONFIG_AGG_BRIDGE NOTE diff --git a/crates/miden-agglayer/src/lib.rs b/crates/miden-agglayer/src/lib.rs index 97ceb05276..7d7945f2a6 100644 --- a/crates/miden-agglayer/src/lib.rs +++ b/crates/miden-agglayer/src/lib.rs @@ -15,7 +15,6 @@ use miden_protocol::account::{ }; use miden_protocol::asset::TokenSymbol; use miden_protocol::note::NoteScript; -use miden_protocol::vm::Program; use miden_standards::account::access::Ownable2Step; use miden_standards::account::auth::NoAuth; use miden_standards::account::mint_policies::OwnerControlled; @@ -28,6 +27,8 @@ pub mod config_note; pub mod errors; pub mod eth_types; pub mod faucet; +#[cfg(feature = "testing")] +pub mod testing; pub mod update_ger_note; pub mod utils; @@ -64,9 +65,10 @@ pub use utils::Keccak256Output; // Initialize the CLAIM note script only once static CLAIM_SCRIPT: LazyLock = LazyLock::new(|| { - let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/note_scripts/CLAIM.masb")); - let program = Program::read_from_bytes(bytes).expect("shipped CLAIM script is well-formed"); - NoteScript::new(program) + let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/note_scripts/CLAIM.masl")); + let library = + Library::read_from_bytes(bytes).expect("shipped CLAIM script library is well-formed"); + NoteScript::from_library(&library).expect("shipped CLAIM script is well-formed") }); /// Returns the CLAIM (Bridge from AggLayer) note script. diff --git a/crates/miden-agglayer/src/testing/mod.rs b/crates/miden-agglayer/src/testing/mod.rs new file mode 100644 index 0000000000..ee7a17ea9b --- /dev/null +++ b/crates/miden-agglayer/src/testing/mod.rs @@ -0,0 +1,297 @@ +//! Shared test vector types and embedded JSON constants for agglayer testing. +//! +//! This module is gated behind the `testing` feature and provides: +//! - Embedded JSON test vector files from `solidity-compat/test-vectors/` +//! - Serde helpers for deserializing Foundry-generated JSON +//! - Deserialized test vector structs (`LeafValueVector`, `ProofValueVector`, etc.) +//! - Lazy-parsed static instances of the test vectors +//! - `ClaimDataSource` enum for selecting between different claim data sources + +extern crate alloc; + +use alloc::string::{String, ToString}; +use alloc::vec::Vec; + +use miden_protocol::utils::hex_to_bytes; +use miden_protocol::utils::sync::LazyLock; +use serde::Deserialize; + +use crate::claim_note::{ProofData, SmtNode}; +use crate::{CgiChainHash, EthAddress, EthAmount, ExitRoot, GlobalIndex, LeafData, MetadataHash}; + +// EMBEDDED TEST VECTOR JSON FILES +// ================================================================================================ + +/// Claim asset test vectors JSON — contains both LeafData and ProofData from a real claimAsset +/// transaction. +pub const CLAIM_ASSET_VECTORS_JSON: &str = + include_str!("../../solidity-compat/test-vectors/claim_asset_vectors_real_tx.json"); + +/// Bridge asset test vectors JSON — contains test data for an L1 bridgeAsset transaction. +pub const BRIDGE_ASSET_VECTORS_JSON: &str = + include_str!("../../solidity-compat/test-vectors/claim_asset_vectors_local_tx.json"); + +/// Rollup deposit test vectors JSON — contains test data for a rollup deposit with two-level +/// Merkle proofs. +pub const ROLLUP_ASSET_VECTORS_JSON: &str = + include_str!("../../solidity-compat/test-vectors/claim_asset_vectors_rollup_tx.json"); + +/// Leaf data test vectors JSON from the Foundry-generated file. +pub const LEAF_VALUE_VECTORS_JSON: &str = + include_str!("../../solidity-compat/test-vectors/leaf_value_vectors.json"); + +/// Merkle proof verification vectors JSON from the Foundry-generated file. +pub const MERKLE_PROOF_VECTORS_JSON: &str = + include_str!("../../solidity-compat/test-vectors/merkle_proof_vectors.json"); + +/// Canonical zeros JSON from the Foundry-generated file. +pub const CANONICAL_ZEROS_JSON: &str = + include_str!("../../solidity-compat/test-vectors/canonical_zeros.json"); + +/// Merkle Tree Frontier (MTF) vectors JSON from the Foundry-generated file. +pub const MTF_VECTORS_JSON: &str = + include_str!("../../solidity-compat/test-vectors/merkle_tree_frontier_vectors.json"); + +// SERDE HELPERS +// ================================================================================================ + +/// Deserializes a JSON value that may be either a number or a string into a `String`. +/// +/// Foundry's `vm.serializeUint` outputs JSON numbers for uint256 values. +/// This deserializer accepts both `"100"` (string) and `100` (number) forms. +pub fn deserialize_uint_to_string<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + let value = serde_json::Value::deserialize(deserializer)?; + match value { + serde_json::Value::String(s) => Ok(s), + serde_json::Value::Number(n) => Ok(n.to_string()), + _ => Err(serde::de::Error::custom("expected a number or string for amount")), + } +} + +/// Deserializes a JSON array of values that may be either numbers or strings into `Vec`. +/// +/// Array-level counterpart of [`deserialize_uint_to_string`]. +pub fn deserialize_uint_vec_to_strings<'de, D>(deserializer: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + let values = Vec::::deserialize(deserializer)?; + values + .into_iter() + .map(|v| match v { + serde_json::Value::String(s) => Ok(s), + serde_json::Value::Number(n) => Ok(n.to_string()), + _ => Err(serde::de::Error::custom("expected a number or string for amount")), + }) + .collect() +} + +// TEST VECTOR TYPES +// ================================================================================================ + +/// Deserialized leaf value test vector from Solidity-generated JSON. +#[derive(Debug, Deserialize)] +pub struct LeafValueVector { + pub origin_network: u32, + pub origin_token_address: String, + pub destination_network: u32, + pub destination_address: String, + #[serde(deserialize_with = "deserialize_uint_to_string")] + pub amount: String, + pub metadata_hash: String, + #[allow(dead_code)] + #[serde(default)] + pub leaf_value: String, +} + +impl LeafValueVector { + /// Converts this test vector into a `LeafData` instance. + pub fn to_leaf_data(&self) -> LeafData { + LeafData { + origin_network: self.origin_network, + origin_token_address: EthAddress::from_hex(&self.origin_token_address) + .expect("valid origin token address hex"), + destination_network: self.destination_network, + destination_address: EthAddress::from_hex(&self.destination_address) + .expect("valid destination address hex"), + amount: EthAmount::from_uint_str(&self.amount).expect("valid amount uint string"), + metadata_hash: MetadataHash::new( + hex_to_bytes(&self.metadata_hash).expect("valid metadata hash hex"), + ), + } + } +} + +/// Deserialized proof value test vector from Solidity-generated JSON. +/// Contains SMT proofs, exit roots, global index, and expected global exit root. +#[derive(Debug, Deserialize)] +pub struct ProofValueVector { + pub smt_proof_local_exit_root: Vec, + pub smt_proof_rollup_exit_root: Vec, + pub global_index: String, + pub mainnet_exit_root: String, + pub rollup_exit_root: String, + /// Expected global exit root: keccak256(mainnetExitRoot || rollupExitRoot) + #[allow(dead_code)] + pub global_exit_root: String, + pub claimed_global_index_hash_chain: String, +} + +impl ProofValueVector { + /// Converts this test vector into a `ProofData` instance. + pub fn to_proof_data(&self) -> ProofData { + let smt_proof_local: [SmtNode; 32] = self + .smt_proof_local_exit_root + .iter() + .map(|s| SmtNode::new(hex_to_bytes(s).expect("valid smt proof hex"))) + .collect::>() + .try_into() + .expect("expected 32 SMT proof nodes for local exit root"); + + let smt_proof_rollup: [SmtNode; 32] = self + .smt_proof_rollup_exit_root + .iter() + .map(|s| SmtNode::new(hex_to_bytes(s).expect("valid smt proof hex"))) + .collect::>() + .try_into() + .expect("expected 32 SMT proof nodes for rollup exit root"); + + ProofData { + smt_proof_local_exit_root: smt_proof_local, + smt_proof_rollup_exit_root: smt_proof_rollup, + global_index: GlobalIndex::from_hex(&self.global_index) + .expect("valid global index hex"), + mainnet_exit_root: ExitRoot::new( + hex_to_bytes(&self.mainnet_exit_root).expect("valid mainnet exit root hex"), + ), + rollup_exit_root: ExitRoot::new( + hex_to_bytes(&self.rollup_exit_root).expect("valid rollup exit root hex"), + ), + } + } +} + +/// Deserialized claim asset test vector from Solidity-generated JSON. +/// Contains both LeafData and ProofData from a real claimAsset transaction. +#[derive(Debug, Deserialize)] +pub struct ClaimAssetVector { + #[serde(flatten)] + pub proof: ProofValueVector, + + #[serde(flatten)] + pub leaf: LeafValueVector, +} + +/// Deserialized Merkle proof vectors from Solidity DepositContractBase.sol. +/// Uses parallel arrays for leaves and roots. For each element from leaves/roots there are 32 +/// elements from merkle_paths, which represent the merkle path for that leaf + root. +#[derive(Debug, Deserialize)] +pub struct MerkleProofVerificationFile { + pub leaves: Vec, + pub roots: Vec, + pub merkle_paths: Vec, +} + +/// Deserialized canonical zeros from Solidity DepositContractBase.sol. +#[derive(Debug, Deserialize)] +pub struct CanonicalZerosFile { + pub canonical_zeros: Vec, +} + +/// Deserialized Merkle Tree Frontier (MTF) vectors from Solidity DepositContractV2. +/// +/// Each leaf is produced by `getLeafValue` using the same hardcoded fields as `bridge_out.masm` +/// (leafType=0, originNetwork=64, metadataHash=0), parametrised by +/// a shared `origin_token_address`, `amounts[i]`, and per-index +/// `destination_networks[i]` / `destination_addresses[i]`. +/// +/// Amounts are serialized as uint256 values (JSON numbers). +#[derive(Debug, Deserialize)] +pub struct MtfVectorsFile { + pub leaves: Vec, + pub roots: Vec, + pub counts: Vec, + #[serde(deserialize_with = "deserialize_uint_vec_to_strings")] + pub amounts: Vec, + pub origin_token_address: String, + pub destination_networks: Vec, + pub destination_addresses: Vec, + pub token_name: String, + pub token_symbol: String, + pub token_decimals: u8, +} + +// LAZY-PARSED TEST VECTORS +// ================================================================================================ + +/// Lazily parsed claim asset test vector from the JSON file. +pub static CLAIM_ASSET_VECTOR: LazyLock = LazyLock::new(|| { + serde_json::from_str(CLAIM_ASSET_VECTORS_JSON) + .expect("failed to parse claim asset vectors JSON") +}); + +/// Lazily parsed bridge asset test vector from the JSON file (locally simulated L1 transaction). +pub static CLAIM_ASSET_VECTOR_LOCAL: LazyLock = LazyLock::new(|| { + serde_json::from_str(BRIDGE_ASSET_VECTORS_JSON) + .expect("failed to parse bridge asset vectors JSON") +}); + +/// Lazily parsed rollup deposit test vector from the JSON file. +pub static CLAIM_ASSET_VECTOR_ROLLUP: LazyLock = LazyLock::new(|| { + serde_json::from_str(ROLLUP_ASSET_VECTORS_JSON) + .expect("failed to parse rollup asset vectors JSON") +}); + +/// Lazily parsed Merkle proof vectors from the JSON file. +pub static SOLIDITY_MERKLE_PROOF_VECTORS: LazyLock = + LazyLock::new(|| { + serde_json::from_str(MERKLE_PROOF_VECTORS_JSON) + .expect("failed to parse Merkle proof vectors JSON") + }); + +/// Lazily parsed canonical zeros from the JSON file. +pub static SOLIDITY_CANONICAL_ZEROS: LazyLock = LazyLock::new(|| { + serde_json::from_str(CANONICAL_ZEROS_JSON).expect("failed to parse canonical zeros JSON") +}); + +/// Lazily parsed Merkle Tree Frontier (MTF) vectors from the JSON file. +pub static SOLIDITY_MTF_VECTORS: LazyLock = LazyLock::new(|| { + serde_json::from_str(MTF_VECTORS_JSON).expect("failed to parse MTF vectors JSON") +}); + +// CLAIM DATA SOURCE +// ================================================================================================ + +/// Identifies the source of claim data used in bridge-in tests and benchmarks. +#[derive(Debug, Clone, Copy)] +pub enum ClaimDataSource { + /// Real on-chain claimAsset data from claim_asset_vectors_real_tx.json (L1 to Miden). + RealL1ToMiden, + /// Locally simulated bridgeAsset data from claim_asset_vectors_local_tx.json (L1 to Miden). + SimulatedL1ToMiden, + /// Rollup deposit data from claim_asset_vectors_rollup_tx.json (L2 to Miden). + SimulatedL2ToMiden, +} + +impl ClaimDataSource { + /// Returns the `(ProofData, LeafData, ExitRoot, CgiChainHash)` tuple for this data source. + pub fn get_data(self) -> (ProofData, LeafData, ExitRoot, CgiChainHash) { + let vector = match self { + ClaimDataSource::RealL1ToMiden => &*CLAIM_ASSET_VECTOR, + ClaimDataSource::SimulatedL1ToMiden => &*CLAIM_ASSET_VECTOR_LOCAL, + ClaimDataSource::SimulatedL2ToMiden => &*CLAIM_ASSET_VECTOR_ROLLUP, + }; + let ger = ExitRoot::new( + hex_to_bytes(&vector.proof.global_exit_root).expect("valid global exit root hex"), + ); + let cgi_chain_hash = CgiChainHash::new( + hex_to_bytes(&vector.proof.claimed_global_index_hash_chain) + .expect("invalid CGI chain hash"), + ); + + (vector.proof.to_proof_data(), vector.leaf.to_leaf_data(), ger, cgi_chain_hash) + } +} diff --git a/crates/miden-agglayer/src/update_ger_note.rs b/crates/miden-agglayer/src/update_ger_note.rs index 07246db9f6..f20e5b2669 100644 --- a/crates/miden-agglayer/src/update_ger_note.rs +++ b/crates/miden-agglayer/src/update_ger_note.rs @@ -8,9 +8,9 @@ extern crate alloc; use alloc::string::ToString; use alloc::vec; +use miden_assembly::Library; use miden_assembly::serde::Deserializable; use miden_core::Word; -use miden_core::program::Program; use miden_protocol::account::AccountId; use miden_protocol::crypto::rand::FeltRng; use miden_protocol::errors::NoteError; @@ -34,10 +34,10 @@ use crate::ExitRoot; // Initialize the UPDATE_GER note script only once static UPDATE_GER_SCRIPT: LazyLock = LazyLock::new(|| { - let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/note_scripts/UPDATE_GER.masb")); - let program = - Program::read_from_bytes(bytes).expect("shipped UPDATE_GER script is well-formed"); - NoteScript::new(program) + let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/note_scripts/UPDATE_GER.masl")); + let library = + Library::read_from_bytes(bytes).expect("shipped UPDATE_GER script library is well-formed"); + NoteScript::from_library(&library).expect("shipped UPDATE_GER script is well-formed") }); // UPDATE_GER NOTE diff --git a/crates/miden-protocol/build.rs b/crates/miden-protocol/build.rs index 66bb714233..f116980634 100644 --- a/crates/miden-protocol/build.rs +++ b/crates/miden-protocol/build.rs @@ -515,11 +515,10 @@ fn generate_event_file_content( for (event_path, event_name) in events { let value = EventId::from_name(event_path).as_felt().as_canonical_u64(); debug_assert!(!event_name.is_empty()); - writeln!(&mut output, "const {}_ID: u64 = {};", event_name, value)?; + writeln!(&mut output, "const {event_name}_ID: u64 = {value};")?; writeln!( &mut output, - "static {}_NAME: ::miden_core::events::EventName = ::miden_core::events::EventName::new(\"{}\");", - event_name, event_path + "static {event_name}_NAME: ::miden_core::events::EventName = ::miden_core::events::EventName::new(\"{event_path}\");" )?; writeln!(&mut output)?; } diff --git a/crates/miden-protocol/src/note/script.rs b/crates/miden-protocol/src/note/script.rs index 2a72656ac4..8661d62954 100644 --- a/crates/miden-protocol/src/note/script.rs +++ b/crates/miden-protocol/src/note/script.rs @@ -42,6 +42,10 @@ impl NoteScript { // -------------------------------------------------------------------------------------------- /// Returns a new [NoteScript] instantiated from the provided program. + /// + /// TODO: since the note script now should be created from `Library`, not `Program`, this + /// constructor should be removed: + /// (). pub fn new(code: Program) -> Self { Self { entrypoint: code.entrypoint(), @@ -359,14 +363,14 @@ fn create_external_node_forest(digest: Word) -> (MastForest, MastNodeId) { mod tests { use super::{Felt, NoteScript, Vec}; use crate::assembly::Assembler; - use crate::testing::note::DEFAULT_NOTE_CODE; + use crate::testing::note::DEFAULT_NOTE_SCRIPT; #[test] fn test_note_script_to_from_felt() { let assembler = Assembler::default(); - let script_src = DEFAULT_NOTE_CODE; - let program = assembler.assemble_program(script_src).unwrap(); - let note_script = NoteScript::new(program); + let script_src = DEFAULT_NOTE_SCRIPT; + let library = assembler.assemble_library([script_src]).unwrap(); + let note_script = NoteScript::from_library(&library).unwrap(); let encoded: Vec = (¬e_script).into(); let decoded: NoteScript = encoded.try_into().unwrap(); @@ -381,8 +385,8 @@ mod tests { use crate::Word; let assembler = Assembler::default(); - let program = assembler.assemble_program("begin nop end").unwrap(); - let script = NoteScript::new(program); + let library = assembler.assemble_library([DEFAULT_NOTE_SCRIPT]).unwrap(); + let script = NoteScript::from_library(&library).unwrap(); assert!(script.mast().advice_map().is_empty()); diff --git a/crates/miden-protocol/src/testing/note.rs b/crates/miden-protocol/src/testing/note.rs index 913fd0f7ee..4d9ac5fba2 100644 --- a/crates/miden-protocol/src/testing/note.rs +++ b/crates/miden-protocol/src/testing/note.rs @@ -15,7 +15,11 @@ use crate::note::{ }; use crate::testing::account_id::ACCOUNT_ID_SENDER; -pub const DEFAULT_NOTE_CODE: &str = "begin nop end"; +pub const DEFAULT_NOTE_SCRIPT: &str = "\ +@note_script +pub proc main + nop +end"; impl Note { /// Returns a note with no-op code and one asset. @@ -39,7 +43,7 @@ impl Note { impl NoteScript { pub fn mock() -> Self { let assembler = Assembler::default(); - let code = assembler.assemble_program(DEFAULT_NOTE_CODE).unwrap(); - Self::new(code) + let library = assembler.assemble_library([DEFAULT_NOTE_SCRIPT]).unwrap(); + Self::from_library(&library).unwrap() } } diff --git a/crates/miden-standards/src/account/interface/test.rs b/crates/miden-standards/src/account/interface/test.rs index 5aef3f6203..c09aadbd2b 100644 --- a/crates/miden-standards/src/account/interface/test.rs +++ b/crates/miden-standards/src/account/interface/test.rs @@ -260,7 +260,8 @@ fn test_basic_wallet_custom_notes() { use miden::standards::wallets::basic->wallet use miden::standards::faucets::basic_fungible->fungible_faucet - begin + @note_script + pub proc main push.1 if.true # supported procs @@ -289,7 +290,8 @@ fn test_basic_wallet_custom_notes() { use miden::standards::wallets::basic->wallet use miden::standards::faucets::basic_fungible->fungible_faucet - begin + @note_script + pub proc main push.1 if.true # unsupported procs @@ -342,7 +344,8 @@ fn test_basic_fungible_faucet_custom_notes() { use miden::standards::wallets::basic->wallet use miden::standards::faucets::basic_fungible->fungible_faucet - begin + @note_script + pub proc main push.1 if.true # supported procs @@ -370,7 +373,8 @@ fn test_basic_fungible_faucet_custom_notes() { use miden::standards::wallets::basic->wallet use miden::standards::faucets::basic_fungible->fungible_faucet - begin + @note_script + pub proc main push.1 if.true # supported procs @@ -445,7 +449,8 @@ fn test_custom_account_custom_notes() { use miden::standards::wallets::basic->wallet use test::account::component_1->test_account - begin + @note_script + pub proc main push.1 if.true # supported proc @@ -476,7 +481,8 @@ fn test_custom_account_custom_notes() { use miden::standards::wallets::basic->wallet use test::account::component_1->test_account - begin + @note_script + pub proc main push.1 if.true call.wallet::receive_asset @@ -550,7 +556,8 @@ fn test_custom_account_multiple_components_custom_notes() { use test::account::component_1->test_account use miden::standards::faucets::basic_fungible->fungible_faucet - begin + @note_script + pub proc main push.1 if.true # supported procs @@ -587,7 +594,8 @@ fn test_custom_account_multiple_components_custom_notes() { use test::account::component_1->test_account use miden::standards::faucets::basic_fungible->fungible_faucet - begin + @note_script + pub proc main push.1 if.true # supported procs diff --git a/crates/miden-standards/src/code_builder/mod.rs b/crates/miden-standards/src/code_builder/mod.rs index edeed1a325..8d43dfd0e9 100644 --- a/crates/miden-standards/src/code_builder/mod.rs +++ b/crates/miden-standards/src/code_builder/mod.rs @@ -386,7 +386,8 @@ impl CodeBuilder { /// The parsed script will have access to all modules that have been added to this builder. /// /// # Arguments - /// * `program` - The note script source code + /// - `source` - the note script source code which is expected to have a single public procedure + /// marked with the @note_script attribute. /// /// # Errors /// Returns an error if: @@ -394,11 +395,20 @@ impl CodeBuilder { pub fn compile_note_script(self, source: impl Parse) -> Result { let CodeBuilder { assembler, advice_map, .. } = self; - let program = assembler.assemble_program(source).map_err(|err| { - CodeBuilderError::build_error_with_report("failed to parse note script", err) + let note_script_lib = assembler.assemble_library([source]).map_err(|err| { + CodeBuilderError::build_error_with_report("failed to parse note script library", err) })?; - Ok(NoteScript::new(Self::apply_advice_map(advice_map, program))) + NoteScript::from_library(&Self::apply_advice_map_to_library( + advice_map, + Arc::unwrap_or_clone(note_script_lib), + )) + .map_err(|err| { + CodeBuilderError::build_error_with_source( + "failed to create note script from library", + err, + ) + }) } // ACCESSORS @@ -501,6 +511,7 @@ impl From for Assembler { mod tests { use anyhow::Context; use miden_protocol::assembly::diagnostics::NamedSource; + use miden_protocol::testing::note::DEFAULT_NOTE_SCRIPT; use super::*; @@ -759,7 +770,7 @@ mod tests { let script = CodeBuilder::default() .with_advice_map_entry(key, value.clone()) - .compile_note_script("begin nop end") + .compile_note_script(DEFAULT_NOTE_SCRIPT) .context("failed to compile note script with advice map")?; let mast = script.mast(); diff --git a/crates/miden-standards/src/testing/note.rs b/crates/miden-standards/src/testing/note.rs index 6c8d4ef1a1..240fd61be1 100644 --- a/crates/miden-standards/src/testing/note.rs +++ b/crates/miden-standards/src/testing/note.rs @@ -18,7 +18,7 @@ use miden_protocol::note::{ NoteTag, NoteType, }; -use miden_protocol::testing::note::DEFAULT_NOTE_CODE; +use miden_protocol::testing::note::DEFAULT_NOTE_SCRIPT; use miden_protocol::vm::Package; use miden_protocol::{Felt, Word}; use rand::Rng; @@ -67,7 +67,7 @@ impl NoteBuilder { serial_num, // The note tag is not under test, so we choose a value that is always valid. tag: NoteTag::with_account_target(sender), - code: DEFAULT_NOTE_CODE.to_string(), + code: DEFAULT_NOTE_SCRIPT.to_string(), attachment: NoteAttachment::default(), source_code: SourceCodeOrigin::Masm { dyn_libraries: Vec::new(), diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs b/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs index 7ce54979cb..2d2496a47e 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs @@ -159,7 +159,7 @@ async fn check_note_consumability_partial_success() -> anyhow::Result<()> { sender, ChaCha20Rng::from_seed(ChaCha20Rng::from_seed([0_u8; 32]).random()), ) - .code("begin push.1 drop push.0 div end") + .code("@note_script pub proc main push.1 drop push.0 div end") .dynamically_linked_libraries([TransactionKernel::library()]) .build()?; @@ -167,7 +167,7 @@ async fn check_note_consumability_partial_success() -> anyhow::Result<()> { sender, ChaCha20Rng::from_seed(ChaCha20Rng::from_seed([0_u8; 32]).random()), ) - .code("begin push.2 drop push.0 div end") + .code("@note_script pub proc main push.2 drop push.0 div end") .dynamically_linked_libraries([TransactionKernel::library()]) .build()?; @@ -337,14 +337,14 @@ async fn check_note_consumability_epilogue_failure_with_new_combination() -> any sender, ChaCha20Rng::from_seed(ChaCha20Rng::from_seed([0_u8; 32]).random()), ) - .code("begin push.1 drop push.1 div end") + .code("@note_script pub proc main push.1 drop push.1 div end") .dynamically_linked_libraries([TransactionKernel::library()]) .build()?; let failing_note_1 = NoteBuilder::new( sender, ChaCha20Rng::from_seed(ChaCha20Rng::from_seed([0_u8; 32]).random()), ) - .code("begin push.1 drop push.0 div end") + .code("@note_script pub proc main push.1 drop push.0 div end") .dynamically_linked_libraries([TransactionKernel::library()]) .build()?; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs index de5171d810..b54fe1c9a9 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs @@ -20,6 +20,7 @@ use miden_protocol::testing::account_id::{ ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, ACCOUNT_ID_SENDER, }; +use miden_protocol::testing::note::DEFAULT_NOTE_SCRIPT; use miden_protocol::transaction::memory::{ASSET_SIZE, ASSET_VALUE_OFFSET}; use miden_protocol::{EMPTY_WORD, Felt, ONE, WORD_SIZE, Word}; use miden_standards::code_builder::CodeBuilder; @@ -425,7 +426,7 @@ async fn test_active_note_get_exactly_8_inputs() -> anyhow::Result<()> { let metadata = NoteMetadata::new(sender_id, NoteType::Public).with_tag(tag); let vault = NoteAssets::new(vec![]).context("failed to create input note assets")?; let note_script = CodeBuilder::default() - .compile_note_script("begin nop end") + .compile_note_script(DEFAULT_NOTE_SCRIPT) .context("failed to parse note script")?; // create a recipient with note storage, which number divides by 8. For simplicity create 8 diff --git a/crates/miden-testing/src/kernel_tests/tx/test_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_note.rs index 4e5f301c2f..a926869424 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_note.rs @@ -23,6 +23,7 @@ use miden_protocol::testing::account_id::{ ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE, ACCOUNT_ID_SENDER, }; +use miden_protocol::testing::note::DEFAULT_NOTE_SCRIPT; use miden_protocol::transaction::memory::ACTIVE_INPUT_NOTE_PTR; use miden_protocol::transaction::{RawOutputNote, TransactionArgs}; use miden_protocol::{Felt, Word}; @@ -187,7 +188,7 @@ async fn test_build_recipient() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; // Create test script and serial number - let note_script = CodeBuilder::default().compile_note_script("begin nop end")?; + let note_script = CodeBuilder::default().compile_note_script(DEFAULT_NOTE_SCRIPT)?; let serial_num = Word::default(); // Define test values as Words @@ -417,7 +418,8 @@ pub async fn test_timelock() -> anyhow::Result<()> { use miden::protocol::active_note use miden::protocol::tx - begin + @note_script + pub proc main # store the note storage to memory starting at address 0 push.0 exec.active_note::get_storage # => [num_storage_items, storage_ptr] @@ -518,7 +520,7 @@ async fn test_public_key_as_note_input() -> anyhow::Result<()> { let tag = NoteTag::with_account_target(target_account.id()); let metadata = NoteMetadata::new(sender_account.id(), NoteType::Public).with_tag(tag); let vault = NoteAssets::new(vec![])?; - let note_script = CodeBuilder::default().compile_note_script("begin nop end")?; + let note_script = CodeBuilder::default().compile_note_script(DEFAULT_NOTE_SCRIPT)?; let recipient = NoteRecipient::new(serial_num, note_script, NoteStorage::new(public_key_value.to_vec())?); let note_with_pub_key = Note::new(vault.clone(), metadata, recipient); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs index d0e3ab8ffd..30233755ee 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs @@ -43,7 +43,7 @@ use miden_protocol::testing::account_id::{ ACCOUNT_ID_SENDER, }; use miden_protocol::testing::constants::{FUNGIBLE_ASSET_AMOUNT, NON_FUNGIBLE_ASSET_DATA}; -use miden_protocol::testing::note::DEFAULT_NOTE_CODE; +use miden_protocol::testing::note::DEFAULT_NOTE_SCRIPT; use miden_protocol::transaction::{ InputNotes, RawOutputNote, @@ -235,7 +235,7 @@ async fn executed_transaction_output_notes() -> anyhow::Result<()> { // Create the expected output note for Note 2 which is public let serial_num_2 = Word::from([1, 2, 3, 4u32]); - let note_script_2 = CodeBuilder::default().compile_note_script(DEFAULT_NOTE_CODE)?; + let note_script_2 = CodeBuilder::default().compile_note_script(DEFAULT_NOTE_SCRIPT)?; let inputs_2 = NoteStorage::new(vec![ONE])?; let metadata_2 = NoteMetadata::new(account_id, note_type2) .with_tag(tag2) @@ -246,7 +246,7 @@ async fn executed_transaction_output_notes() -> anyhow::Result<()> { // Create the expected output note for Note 3 which is public let serial_num_3 = Word::from([Felt::new(5), Felt::new(6), Felt::new(7), Felt::new(8)]); - let note_script_3 = CodeBuilder::default().compile_note_script(DEFAULT_NOTE_CODE)?; + let note_script_3 = CodeBuilder::default().compile_note_script(DEFAULT_NOTE_SCRIPT)?; let inputs_3 = NoteStorage::new(vec![ONE, Felt::new(2)])?; let metadata_3 = NoteMetadata::new(account_id, note_type3) .with_tag(tag3) diff --git a/crates/miden-testing/src/mock_chain/chain.rs b/crates/miden-testing/src/mock_chain/chain.rs index 08d9d7a7cc..6ba40fba79 100644 --- a/crates/miden-testing/src/mock_chain/chain.rs +++ b/crates/miden-testing/src/mock_chain/chain.rs @@ -260,17 +260,26 @@ impl MockChain { .apply_block(genesis_block) .context("failed to build account from builder")?; - // Update committed_notes with full note details for genesis notes. - // This is needed because apply_block only stores headers for private notes, - // but tests need full note details to create input notes. - for note in genesis_notes { - if let Some(MockChainNote::Private(_, _, inclusion_proof)) = - chain.committed_notes.get(¬e.id()) - { - chain.committed_notes.insert( - note.id(), - MockChainNote::Public(note.clone(), inclusion_proof.clone()), - ); + // Add genesis notes to committed_notes with full note details. + // apply_block only stores Public output notes; private genesis notes must be added here + // since the test framework has the full note objects available. + let genesis_block = chain.blocks.first().expect("genesis block should exist after apply_block"); + let notes_tree = genesis_block.body().compute_block_note_tree(); + for (block_note_index, output_note) in genesis_block.body().output_notes() { + if let Some(genesis_note) = genesis_notes.iter().find(|n| n.id() == output_note.id()) { + if !chain.committed_notes.contains_key(&genesis_note.id()) { + let note_path = notes_tree.open(block_note_index); + let inclusion_proof = NoteInclusionProof::new( + genesis_block.header().block_num(), + block_note_index.leaf_index_value(), + note_path, + ) + .context("failed to create inclusion proof for genesis note")?; + chain.committed_notes.insert( + genesis_note.id(), + MockChainNote::new(genesis_note.clone(), inclusion_proof), + ); + } } } @@ -454,11 +463,9 @@ impl MockChain { &self.committed_notes } - /// Returns an [`InputNote`] for the given note ID. If the note does not exist or is not - /// public, `None` is returned. + /// Returns an [`InputNote`] for the given note ID, or `None` if not found. pub fn get_public_note(&self, note_id: &NoteId) -> Option { - let note = self.committed_notes.get(note_id)?; - note.clone().try_into().ok() + self.committed_notes.get(note_id).map(|n| n.clone().into()) } /// Returns a reference to the account identified by the given account ID. @@ -662,10 +669,7 @@ impl MockChain { .get(note) .with_context(|| format!("note with id {note} not found"))? .clone() - .try_into() - .with_context(|| { - format!("failed to convert mock chain note with id {note} into input note") - })?; + .into(); let note_block_num = input_note .location() @@ -947,16 +951,7 @@ impl MockChain { if let OutputNote::Public(public_note) = created_note { self.committed_notes.insert( public_note.id(), - MockChainNote::Public(public_note.as_note().clone(), note_inclusion_proof), - ); - } else { - self.committed_notes.insert( - created_note.id(), - MockChainNote::Private( - created_note.id(), - created_note.metadata().clone(), - note_inclusion_proof, - ), + MockChainNote::new(public_note.as_note().clone(), note_inclusion_proof), ); } } diff --git a/crates/miden-testing/src/mock_chain/note.rs b/crates/miden-testing/src/mock_chain/note.rs index 233c751f15..897c968ab4 100644 --- a/crates/miden-testing/src/mock_chain/note.rs +++ b/crates/miden-testing/src/mock_chain/note.rs @@ -7,60 +7,45 @@ use miden_tx::utils::serde::{ByteReader, ByteWriter, Deserializable, Serializabl // ================================================================================================ /// Represents a note that is stored in the mock chain. -#[allow(clippy::large_enum_variant)] +/// +/// Always holds the full [`Note`] object alongside its [`NoteInclusionProof`]. +/// The note's privacy is determined by the note's own metadata. #[derive(Debug, Clone, PartialEq, Eq)] -pub enum MockChainNote { - /// Details for a private note only include its [`NoteMetadata`] and [`NoteInclusionProof`]. - /// Other details needed to consume the note are expected to be stored locally, off-chain. - Private(NoteId, NoteMetadata, NoteInclusionProof), - /// Contains the full [`Note`] object alongside its [`NoteInclusionProof`]. - Public(Note, NoteInclusionProof), +pub struct MockChainNote { + note: Note, + inclusion_proof: NoteInclusionProof, } impl MockChainNote { - /// Returns the note's inclusion details. + /// Creates a new [`MockChainNote`] from a full note and its inclusion proof. + pub fn new(note: Note, inclusion_proof: NoteInclusionProof) -> Self { + Self { note, inclusion_proof } + } + + /// Returns the note's inclusion proof. pub fn inclusion_proof(&self) -> &NoteInclusionProof { - match self { - MockChainNote::Private(_, _, inclusion_proof) - | MockChainNote::Public(_, inclusion_proof) => inclusion_proof, - } + &self.inclusion_proof } /// Returns the note's metadata. pub fn metadata(&self) -> &NoteMetadata { - match self { - MockChainNote::Private(_, metadata, _) => metadata, - MockChainNote::Public(note, _) => note.metadata(), - } + self.note.metadata() } /// Returns the note's ID. pub fn id(&self) -> NoteId { - match self { - MockChainNote::Private(id, ..) => *id, - MockChainNote::Public(note, _) => note.id(), - } + self.note.id() } - /// Returns the underlying note if it is public. - pub fn note(&self) -> Option<&Note> { - match self { - MockChainNote::Private(..) => None, - MockChainNote::Public(note, _) => Some(note), - } + /// Returns a reference to the underlying note. + pub fn note(&self) -> &Note { + &self.note } } -impl TryFrom for InputNote { - type Error = anyhow::Error; - - fn try_from(value: MockChainNote) -> Result { - match value { - MockChainNote::Private(..) => Err(anyhow::anyhow!( - "private notes in the mock chain cannot be converted into input notes due to missing details" - )), - MockChainNote::Public(note, proof) => Ok(InputNote::Authenticated { note, proof }), - } +impl From for InputNote { + fn from(value: MockChainNote) -> Self { + InputNote::Authenticated { note: value.note, proof: value.inclusion_proof } } } @@ -69,38 +54,15 @@ impl TryFrom for InputNote { impl Serializable for MockChainNote { fn write_into(&self, target: &mut W) { - match self { - MockChainNote::Private(id, metadata, proof) => { - 0u8.write_into(target); - id.write_into(target); - metadata.write_into(target); - proof.write_into(target); - }, - MockChainNote::Public(note, proof) => { - 1u8.write_into(target); - note.write_into(target); - proof.write_into(target); - }, - } + self.note.write_into(target); + self.inclusion_proof.write_into(target); } } impl Deserializable for MockChainNote { fn read_from(source: &mut R) -> Result { - let note_type = u8::read_from(source)?; - match note_type { - 0 => { - let id = NoteId::read_from(source)?; - let metadata = NoteMetadata::read_from(source)?; - let proof = NoteInclusionProof::read_from(source)?; - Ok(MockChainNote::Private(id, metadata, proof)) - }, - 1 => { - let note = Note::read_from(source)?; - let proof = NoteInclusionProof::read_from(source)?; - Ok(MockChainNote::Public(note, proof)) - }, - _ => Err(DeserializationError::InvalidValue(format!("Unknown note type: {note_type}"))), - } + let note = Note::read_from(source)?; + let inclusion_proof = NoteInclusionProof::read_from(source)?; + Ok(Self { note, inclusion_proof }) } } diff --git a/crates/miden-testing/src/tx_context/context.rs b/crates/miden-testing/src/tx_context/context.rs index c2ee158a6b..9395c00dc0 100644 --- a/crates/miden-testing/src/tx_context/context.rs +++ b/crates/miden-testing/src/tx_context/context.rs @@ -405,8 +405,7 @@ impl MastForestStore for TransactionContext { #[cfg(test)] mod tests { use miden_protocol::Felt; - use miden_protocol::assembly::Assembler; - use miden_protocol::note::NoteScript; + use miden_standards::code_builder::CodeBuilder; use super::*; use crate::TransactionContextBuilder; @@ -414,20 +413,16 @@ mod tests { #[tokio::test] async fn test_get_note_scripts() { // Create two note scripts - let assembler1 = Assembler::default(); - let script1_code = "begin push.1 end"; - let program1 = assembler1 - .assemble_program(script1_code) + let script1_code = "@note_script\npub proc main\n push.1\nend"; + let note_script1 = CodeBuilder::default() + .compile_note_script(script1_code) .expect("failed to assemble note script 1"); - let note_script1 = NoteScript::new(program1); let script_root1 = note_script1.root(); - let assembler2 = Assembler::default(); - let script2_code = "begin push.2 push.3 add end"; - let program2 = assembler2 - .assemble_program(script2_code) + let script2_code = "@note_script\npub proc main\n push.2 push.3 add\nend"; + let note_script2 = CodeBuilder::default() + .compile_note_script(script2_code) .expect("failed to assemble note script 2"); - let note_script2 = NoteScript::new(program2); let script_root2 = note_script2.root(); // Build a transaction context with both note scripts diff --git a/crates/miden-testing/src/utils.rs b/crates/miden-testing/src/utils.rs index b16bc5a880..81a49d2c74 100644 --- a/crates/miden-testing/src/utils.rs +++ b/crates/miden-testing/src/utils.rs @@ -135,7 +135,8 @@ pub fn create_p2any_note( use ::miden::protocol::asset::ASSET_SIZE use miden::standards::wallets::basic->wallet - begin + @note_script + pub proc main # fetch pointer & number of assets push.0 exec.active_note::get_assets # [num_assets, dest_ptr] @@ -201,7 +202,7 @@ fn note_script_that_creates_notes<'note>( sender_id: AccountId, output_notes: impl Iterator, ) -> anyhow::Result { - let mut out = String::from("use miden::protocol::output_note\n\nbegin\n"); + let mut out = String::from("use miden::protocol::output_note\n\n@note_script\npub proc main\n"); for (idx, note) in output_notes.into_iter().enumerate() { anyhow::ensure!( diff --git a/crates/miden-testing/tests/agglayer/bridge_in.rs b/crates/miden-testing/tests/agglayer/bridge_in.rs index e1b7526844..9e45a965ab 100644 --- a/crates/miden-testing/tests/agglayer/bridge_in.rs +++ b/crates/miden-testing/tests/agglayer/bridge_in.rs @@ -43,6 +43,13 @@ use super::test_utils::{ SOLIDITY_MERKLE_PROOF_VECTORS, }; +// CONSTANTS +// ================================================================================================ + +/// Maximum allowed cycle count for CLAIM note processing. +/// Current observed values: ~25,662 (real/simulated), ~40,485 (rollup). +const MAX_CLAIM_NOTE_PROCESSING_CYCLES: usize = 64_000; + // HELPER FUNCTIONS // ================================================================================================ @@ -102,18 +109,20 @@ fn merkle_proof_verification_code( /// TX3: MINT → aggfaucet (mints asset, creates P2ID note) /// TX4: P2ID → destination (simulated case only) /// -/// Parameterized over two claim data sources: -/// - [`ClaimDataSource::Real`]: uses real [`ProofData`] and [`LeafData`] from +/// Parameterized over three claim data sources: +/// - [`ClaimDataSource::RealL1ToMiden`]: uses real [`ProofData`] and [`LeafData`] from /// `claim_asset_vectors_real_tx.json`, captured from an actual on-chain `claimAsset` transaction. -/// - [`ClaimDataSource::Simulated`]: uses locally generated [`ProofData`] and [`LeafData`] from -/// `claim_asset_vectors_local_tx.json`, produced by simulating a `bridgeAsset()` call. +/// - [`ClaimDataSource::SimulatedL1ToMiden`]: uses locally generated [`ProofData`] and [`LeafData`] +/// from `claim_asset_vectors_local_tx.json`, produced by simulating a `bridgeAsset()` call. +/// - [`ClaimDataSource::SimulatedL2ToMiden`]: uses rollup deposit data from +/// `claim_asset_vectors_rollup_tx.json`, produced by simulating a rollup deposit. /// /// Note: Modifying anything in the real test vectors would invalidate the Merkle proof, /// as the proof was computed for the original leaf data including the original destination. #[rstest::rstest] -#[case::real(ClaimDataSource::Real)] -#[case::simulated(ClaimDataSource::Simulated)] -#[case::rollup(ClaimDataSource::Rollup)] +#[case::real_l1_to_miden(ClaimDataSource::RealL1ToMiden)] +#[case::simulated_l1_to_miden(ClaimDataSource::SimulatedL1ToMiden)] +#[case::simulated_l2_to_miden(ClaimDataSource::SimulatedL2ToMiden)] #[tokio::test] async fn test_bridge_in_claim_to_p2id(#[case] data_source: ClaimDataSource) -> anyhow::Result<()> { use miden_agglayer::AggLayerBridge; @@ -179,7 +188,7 @@ async fn test_bridge_in_claim_to_p2id(#[case] data_source: ClaimDataSource) -> a // For the simulated/rollup case, create the destination account so we can consume the P2ID note let destination_account = if matches!( data_source, - ClaimDataSource::Simulated | ClaimDataSource::Rollup + ClaimDataSource::SimulatedL1ToMiden | ClaimDataSource::SimulatedL2ToMiden ) { let dest = Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE, IncrNonceAuthComponent); @@ -298,6 +307,12 @@ async fn test_bridge_in_claim_to_p2id(#[case] data_source: ClaimDataSource) -> a assert_eq!(cgi_chain_hash, actual_cgi_chain_hash); + let claim_cycles = claim_executed.measurements().notes_processing; + assert!( + claim_cycles <= MAX_CLAIM_NOTE_PROCESSING_CYCLES, + "CLAIM note processing exceeded cycle budget: {claim_cycles} > {MAX_CLAIM_NOTE_PROCESSING_CYCLES}" + ); + // VERIFY MINT NOTE WAS CREATED BY THE BRIDGE // -------------------------------------------------------------------------------------------- assert_eq!(claim_executed.output_notes().num_notes(), 1); @@ -414,7 +429,7 @@ async fn test_bridge_in_claim_to_p2id(#[case] data_source: ClaimDataSource) -> a /// been spent" #[tokio::test] async fn test_duplicate_claim_note_rejected() -> anyhow::Result<()> { - let data_source = ClaimDataSource::Simulated; + let data_source = ClaimDataSource::SimulatedL1ToMiden; let mut builder = MockChain::builder(); // CREATE BRIDGE ADMIN ACCOUNT diff --git a/crates/miden-testing/tests/agglayer/test_utils.rs b/crates/miden-testing/tests/agglayer/test_utils.rs index 69506f7a23..766cf4a1a5 100644 --- a/crates/miden-testing/tests/agglayer/test_utils.rs +++ b/crates/miden-testing/tests/agglayer/test_utils.rs @@ -1,19 +1,16 @@ extern crate alloc; -use alloc::string::String; use alloc::sync::Arc; -use alloc::vec::Vec; -use miden_agglayer::claim_note::{ProofData, SmtNode}; -use miden_agglayer::{ - CgiChainHash, - EthAddress, - EthAmount, - ExitRoot, - GlobalIndex, - LeafData, - MetadataHash, - agglayer_library, +use miden_agglayer::agglayer_library; +pub use miden_agglayer::testing::{ + ClaimDataSource, + LEAF_VALUE_VECTORS_JSON, + LeafValueVector, + MerkleProofVerificationFile, + MtfVectorsFile, + SOLIDITY_CANONICAL_ZEROS, + SOLIDITY_MERKLE_PROOF_VECTORS, }; use miden_assembly::{Assembler, DefaultSourceManager}; use miden_core_lib::CoreLibrary; @@ -28,291 +25,26 @@ use miden_processor::{ }; use miden_protocol::transaction::TransactionKernel; use miden_protocol::utils::sync::LazyLock; -use miden_tx::utils::hex_to_bytes; -use serde::Deserialize; // EMBEDDED TEST VECTOR JSON FILES // ================================================================================================ -/// Claim asset test vectors JSON — contains both LeafData and ProofData from a real claimAsset -/// transaction. -const CLAIM_ASSET_VECTORS_JSON: &str = include_str!( - "../../../miden-agglayer/solidity-compat/test-vectors/claim_asset_vectors_real_tx.json" -); - -/// Bridge asset test vectors JSON — contains test data for an L1 bridgeAsset transaction. -const BRIDGE_ASSET_VECTORS_JSON: &str = include_str!( - "../../../miden-agglayer/solidity-compat/test-vectors/claim_asset_vectors_local_tx.json" -); - -/// Rollup deposit test vectors JSON — contains test data for a rollup deposit with two-level -/// Merkle proofs. -const ROLLUP_ASSET_VECTORS_JSON: &str = include_str!( - "../../../miden-agglayer/solidity-compat/test-vectors/claim_asset_vectors_rollup_tx.json" -); - -/// Leaf data test vectors JSON from the Foundry-generated file. -pub const LEAF_VALUE_VECTORS_JSON: &str = - include_str!("../../../miden-agglayer/solidity-compat/test-vectors/leaf_value_vectors.json"); - -/// Merkle proof verification vectors JSON from the Foundry-generated file. -pub const MERKLE_PROOF_VECTORS_JSON: &str = - include_str!("../../../miden-agglayer/solidity-compat/test-vectors/merkle_proof_vectors.json"); - -/// Canonical zeros JSON from the Foundry-generated file. -pub const CANONICAL_ZEROS_JSON: &str = - include_str!("../../../miden-agglayer/solidity-compat/test-vectors/canonical_zeros.json"); - /// Merkle Tree Frontier (MTF) vectors JSON from the Foundry-generated file. pub const MTF_VECTORS_JSON: &str = include_str!( "../../../miden-agglayer/solidity-compat/test-vectors/merkle_tree_frontier_vectors.json" ); -// SERDE HELPERS -// ================================================================================================ - -/// Deserializes a JSON value that may be either a number or a string into a `String`. -/// -/// Foundry's `vm.serializeUint` outputs JSON numbers for uint256 values. -/// This deserializer accepts both `"100"` (string) and `100` (number) forms. -fn deserialize_uint_to_string<'de, D>(deserializer: D) -> Result -where - D: serde::Deserializer<'de>, -{ - let value = serde_json::Value::deserialize(deserializer)?; - match value { - serde_json::Value::String(s) => Ok(s), - serde_json::Value::Number(n) => Ok(n.to_string()), - _ => Err(serde::de::Error::custom("expected a number or string for amount")), - } -} - -/// Deserializes a JSON array of values that may be either numbers or strings into `Vec`. -/// -/// Array-level counterpart of [`deserialize_uint_to_string`]. -fn deserialize_uint_vec_to_strings<'de, D>(deserializer: D) -> Result, D::Error> -where - D: serde::Deserializer<'de>, -{ - let values = Vec::::deserialize(deserializer)?; - values - .into_iter() - .map(|v| match v { - serde_json::Value::String(s) => Ok(s), - serde_json::Value::Number(n) => Ok(n.to_string()), - _ => Err(serde::de::Error::custom("expected a number or string for amount")), - }) - .collect() -} - -// TEST VECTOR TYPES -// ================================================================================================ - -/// Deserialized leaf value test vector from Solidity-generated JSON. -#[derive(Debug, Deserialize)] -pub struct LeafValueVector { - pub origin_network: u32, - pub origin_token_address: String, - pub destination_network: u32, - pub destination_address: String, - #[serde(deserialize_with = "deserialize_uint_to_string")] - pub amount: String, - pub metadata_hash: String, - #[allow(dead_code)] - pub leaf_value: String, -} - -impl LeafValueVector { - /// Converts this test vector into a `LeafData` instance. - pub fn to_leaf_data(&self) -> LeafData { - LeafData { - origin_network: self.origin_network, - origin_token_address: EthAddress::from_hex(&self.origin_token_address) - .expect("valid origin token address hex"), - destination_network: self.destination_network, - destination_address: EthAddress::from_hex(&self.destination_address) - .expect("valid destination address hex"), - amount: EthAmount::from_uint_str(&self.amount).expect("valid amount uint string"), - metadata_hash: MetadataHash::new( - hex_to_bytes(&self.metadata_hash).expect("valid metadata hash hex"), - ), - } - } -} - -/// Deserialized proof value test vector from Solidity-generated JSON. -/// Contains SMT proofs, exit roots, global index, and expected global exit root. -#[derive(Debug, Deserialize)] -pub struct ProofValueVector { - pub smt_proof_local_exit_root: Vec, - pub smt_proof_rollup_exit_root: Vec, - pub global_index: String, - pub mainnet_exit_root: String, - pub rollup_exit_root: String, - /// Expected global exit root: keccak256(mainnetExitRoot || rollupExitRoot) - #[allow(dead_code)] - pub global_exit_root: String, - pub claimed_global_index_hash_chain: String, -} - -impl ProofValueVector { - /// Converts this test vector into a `ProofData` instance. - pub fn to_proof_data(&self) -> ProofData { - let smt_proof_local: [SmtNode; 32] = self - .smt_proof_local_exit_root - .iter() - .map(|s| SmtNode::new(hex_to_bytes(s).expect("valid smt proof hex"))) - .collect::>() - .try_into() - .expect("expected 32 SMT proof nodes for local exit root"); - - let smt_proof_rollup: [SmtNode; 32] = self - .smt_proof_rollup_exit_root - .iter() - .map(|s| SmtNode::new(hex_to_bytes(s).expect("valid smt proof hex"))) - .collect::>() - .try_into() - .expect("expected 32 SMT proof nodes for rollup exit root"); - - ProofData { - smt_proof_local_exit_root: smt_proof_local, - smt_proof_rollup_exit_root: smt_proof_rollup, - global_index: GlobalIndex::from_hex(&self.global_index) - .expect("valid global index hex"), - mainnet_exit_root: ExitRoot::new( - hex_to_bytes(&self.mainnet_exit_root).expect("valid mainnet exit root hex"), - ), - rollup_exit_root: ExitRoot::new( - hex_to_bytes(&self.rollup_exit_root).expect("valid rollup exit root hex"), - ), - } - } -} - -/// Deserialized claim asset test vector from Solidity-generated JSON. -/// Contains both LeafData and ProofData from a real claimAsset transaction. -#[derive(Debug, Deserialize)] -pub struct ClaimAssetVector { - #[serde(flatten)] - pub proof: ProofValueVector, - - #[serde(flatten)] - pub leaf: LeafValueVector, -} - -/// Deserialized Merkle proof vectors from Solidity DepositContractBase.sol. -/// Uses parallel arrays for leaves and roots. For each element from leaves/roots there are 32 -/// elements from merkle_paths, which represent the merkle path for that leaf + root. -#[derive(Debug, Deserialize)] -pub struct MerkleProofVerificationFile { - pub leaves: Vec, - pub roots: Vec, - pub merkle_paths: Vec, -} - -/// Deserialized canonical zeros from Solidity DepositContractBase.sol. -#[derive(Debug, Deserialize)] -pub struct CanonicalZerosFile { - pub canonical_zeros: Vec, -} - -/// Deserialized Merkle Tree Frontier vectors from Solidity DepositContractV2. -/// -/// Each leaf is produced by `getLeafValue` using the same hardcoded fields as `bridge_out.masm` -/// (leafType=0, originNetwork=64), parametrised by -/// a shared `origin_token_address`, `amounts[i]`, per-index -/// `destination_networks[i]` / `destination_addresses[i]`, and -/// `metadataHash = keccak256(abi.encode(token_name, token_symbol, token_decimals))`. -/// -/// Amounts are serialized as uint256 values (JSON numbers). -#[derive(Debug, Deserialize)] -pub struct MTFVectorsFile { - pub leaves: Vec, - pub roots: Vec, - pub counts: Vec, - #[serde(deserialize_with = "deserialize_uint_vec_to_strings")] - pub amounts: Vec, - pub origin_token_address: String, - pub destination_networks: Vec, - pub destination_addresses: Vec, - #[allow(dead_code)] - pub token_name: String, - pub token_symbol: String, - pub token_decimals: u8, -} - // LAZY-PARSED TEST VECTORS // ================================================================================================ -/// Lazily parsed claim asset test vector from the JSON file. -pub static CLAIM_ASSET_VECTOR: LazyLock = LazyLock::new(|| { - serde_json::from_str(CLAIM_ASSET_VECTORS_JSON) - .expect("failed to parse claim asset vectors JSON") -}); - -/// Lazily parsed bridge asset test vector from the JSON file (locally simulated L1 transaction). -pub static CLAIM_ASSET_VECTOR_LOCAL: LazyLock = LazyLock::new(|| { - serde_json::from_str(BRIDGE_ASSET_VECTORS_JSON) - .expect("failed to parse bridge asset vectors JSON") -}); - -/// Lazily parsed rollup deposit test vector from the JSON file. -pub static CLAIM_ASSET_VECTOR_ROLLUP: LazyLock = LazyLock::new(|| { - serde_json::from_str(ROLLUP_ASSET_VECTORS_JSON) - .expect("failed to parse rollup asset vectors JSON") -}); - -/// Lazily parsed Merkle proof vectors from the JSON file. -pub static SOLIDITY_MERKLE_PROOF_VECTORS: LazyLock = - LazyLock::new(|| { - serde_json::from_str(MERKLE_PROOF_VECTORS_JSON) - .expect("failed to parse Merkle proof vectors JSON") - }); - -/// Lazily parsed canonical zeros from the JSON file. -pub static SOLIDITY_CANONICAL_ZEROS: LazyLock = LazyLock::new(|| { - serde_json::from_str(CANONICAL_ZEROS_JSON).expect("failed to parse canonical zeros JSON") -}); - /// Lazily parsed Merkle Tree frontier (MTF) vectors from the JSON file. -pub static SOLIDITY_MTF_VECTORS: LazyLock = LazyLock::new(|| { +pub static SOLIDITY_MTF_VECTORS: LazyLock = LazyLock::new(|| { serde_json::from_str(MTF_VECTORS_JSON).expect("failed to parse MTF vectors JSON") }); // HELPER FUNCTIONS // ================================================================================================ -/// Identifies the source of claim data used in bridge-in tests. -#[derive(Debug, Clone, Copy)] -pub enum ClaimDataSource { - /// Real on-chain claimAsset data from claim_asset_vectors_real_tx.json. - Real, - /// Locally simulated bridgeAsset data from claim_asset_vectors_local_tx.json. - Simulated, - /// Rollup deposit data from claim_asset_vectors_rollup_tx.json. - Rollup, -} - -impl ClaimDataSource { - /// Returns the `(ProofData, LeafData, ExitRoot, CgiChainHash)` tuple for this data source. - pub fn get_data(self) -> (ProofData, LeafData, ExitRoot, CgiChainHash) { - let vector = match self { - ClaimDataSource::Real => &*CLAIM_ASSET_VECTOR, - ClaimDataSource::Simulated => &*CLAIM_ASSET_VECTOR_LOCAL, - ClaimDataSource::Rollup => &*CLAIM_ASSET_VECTOR_ROLLUP, - }; - let ger = ExitRoot::new( - hex_to_bytes(&vector.proof.global_exit_root).expect("valid global exit root hex"), - ); - let cgi_chain_hash = CgiChainHash::new( - hex_to_bytes(&vector.proof.claimed_global_index_hash_chain) - .expect("invalid CGI chain hash"), - ); - - (vector.proof.to_proof_data(), vector.leaf.to_leaf_data(), ger, cgi_chain_hash) - } -} - /// Execute a program with a default host and optional advice inputs. pub async fn execute_program_with_default_host( program: Program, diff --git a/crates/miden-testing/tests/auth/multisig_psm.rs b/crates/miden-testing/tests/auth/multisig_psm.rs index 31d090d460..39a3dfb2ba 100644 --- a/crates/miden-testing/tests/auth/multisig_psm.rs +++ b/crates/miden-testing/tests/auth/multisig_psm.rs @@ -12,6 +12,7 @@ use miden_protocol::testing::account_id::{ ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, }; +use miden_protocol::testing::note::DEFAULT_NOTE_SCRIPT; use miden_protocol::transaction::RawOutputNote; use miden_protocol::{Felt, Word}; use miden_standards::account::auth::{AuthMultisigPsm, AuthMultisigPsmConfig, PsmConfig}; @@ -461,7 +462,7 @@ async fn test_multisig_update_psm_public_key_must_be_called_alone( // Also reject rotation transactions that touch notes even when no other account procedure is // called. - let note_script = CodeBuilder::default().compile_note_script("begin nop end")?; + let note_script = CodeBuilder::default().compile_note_script(DEFAULT_NOTE_SCRIPT)?; let note_serial_num = Word::from([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]); let note_recipient = NoteRecipient::new(note_serial_num, note_script.clone(), NoteStorage::default()); diff --git a/crates/miden-testing/tests/scripts/faucet.rs b/crates/miden-testing/tests/scripts/faucet.rs index 675bdada2d..ccfc966861 100644 --- a/crates/miden-testing/tests/scripts/faucet.rs +++ b/crates/miden-testing/tests/scripts/faucet.rs @@ -293,7 +293,8 @@ async fn prove_burning_fungible_asset_on_existing_faucet_succeeds() -> anyhow::R // need to create a note with the fungible asset to be burned let burn_note_script_code = " # burn the asset - begin + @note_script + pub proc main dropw # => [] @@ -356,7 +357,8 @@ async fn faucet_burn_fungible_asset_fails_amount_exceeds_token_supply() -> anyho let burn_note_script_code = " # burn the asset - begin + @note_script + pub proc main dropw # => [] @@ -407,7 +409,7 @@ async fn test_public_note_creation_with_script_from_datastore() -> anyhow::Resul let note_type = NoteType::Public; // Create a simple output note script - let output_note_script_code = "begin push.1 drop end"; + let output_note_script_code = "@note_script pub proc main push.1 drop end"; let source_manager = Arc::new(DefaultSourceManager::default()); let output_note_script = CodeBuilder::with_source_manager(source_manager.clone()) .compile_note_script(output_note_script_code)?; @@ -441,7 +443,8 @@ async fn test_public_note_creation_with_script_from_datastore() -> anyhow::Resul " use miden::protocol::note - begin + @note_script + pub proc main # Build recipient hash from SERIAL_NUM, SCRIPT_ROOT, and STORAGE_COMMITMENT push.{script_root} # => [SCRIPT_ROOT] @@ -763,7 +766,8 @@ async fn test_network_faucet_set_policy_rejects_non_allowed_root() -> anyhow::Re r#" use miden::standards::mint_policies::policy_manager->policy_manager - begin + @note_script + pub proc main repeat.12 push.0 end push.{invalid_policy_root} call.policy_manager::set_mint_policy @@ -944,7 +948,8 @@ async fn test_network_faucet_transfer_ownership() -> anyhow::Result<()> { r#" use miden::standards::access::ownable2step - begin + @note_script + pub proc main repeat.14 push.0 end push.{new_owner_prefix} push.{new_owner_suffix} @@ -997,7 +1002,8 @@ async fn test_network_faucet_transfer_ownership() -> anyhow::Result<()> { let accept_note_script_code = r#" use miden::standards::access::ownable2step - begin + @note_script + pub proc main repeat.16 push.0 end call.ownable2step::accept_ownership dropw dropw dropw dropw @@ -1072,7 +1078,8 @@ async fn test_network_faucet_only_owner_can_transfer() -> anyhow::Result<()> { r#" use miden::standards::access::ownable2step - begin + @note_script + pub proc main repeat.14 push.0 end push.{new_owner_prefix} push.{new_owner_suffix} @@ -1142,7 +1149,8 @@ async fn test_network_faucet_renounce_ownership() -> anyhow::Result<()> { let renounce_note_script_code = r#" use miden::standards::access::ownable2step - begin + @note_script + pub proc main repeat.16 push.0 end call.ownable2step::renounce_ownership dropw dropw dropw dropw @@ -1156,7 +1164,8 @@ async fn test_network_faucet_renounce_ownership() -> anyhow::Result<()> { r#" use miden::standards::access::ownable2step - begin + @note_script + pub proc main repeat.14 push.0 end push.{new_owner_prefix} push.{new_owner_suffix} diff --git a/crates/miden-testing/tests/scripts/ownable2step.rs b/crates/miden-testing/tests/scripts/ownable2step.rs index c0441acaff..0eff56ad95 100644 --- a/crates/miden-testing/tests/scripts/ownable2step.rs +++ b/crates/miden-testing/tests/scripts/ownable2step.rs @@ -80,7 +80,8 @@ fn create_transfer_note( let script = format!( r#" use miden::standards::access::ownable2step->test_account - begin + @note_script + pub proc main repeat.14 push.0 end push.{new_owner_prefix} push.{new_owner_suffix} @@ -107,7 +108,8 @@ fn create_accept_note( ) -> anyhow::Result { let script = r#" use miden::standards::access::ownable2step->test_account - begin + @note_script + pub proc main repeat.16 push.0 end call.test_account::accept_ownership dropw dropw dropw dropw @@ -129,7 +131,8 @@ fn create_renounce_note( ) -> anyhow::Result { let script = r#" use miden::standards::access::ownable2step->test_account - begin + @note_script + pub proc main repeat.16 push.0 end call.test_account::renounce_ownership dropw dropw dropw dropw @@ -456,7 +459,8 @@ async fn test_transfer_ownership_fails_with_invalid_account_id() -> anyhow::Resu let script = format!( r#" use miden::standards::access::ownable2step->test_account - begin + @note_script + pub proc main repeat.14 push.0 end push.{invalid_suffix} push.{invalid_prefix} @@ -464,6 +468,8 @@ async fn test_transfer_ownership_fails_with_invalid_account_id() -> anyhow::Resu dropw dropw dropw dropw end "#, + invalid_prefix = invalid_prefix, + invalid_suffix = invalid_suffix, ); let source_manager: Arc = Arc::new(DefaultSourceManager::default()); diff --git a/crates/miden-testing/tests/scripts/send_note.rs b/crates/miden-testing/tests/scripts/send_note.rs index 9859302858..6ca7c04e43 100644 --- a/crates/miden-testing/tests/scripts/send_note.rs +++ b/crates/miden-testing/tests/scripts/send_note.rs @@ -16,6 +16,7 @@ use miden_protocol::note::{ NoteType, PartialNote, }; +use miden_protocol::testing::note::DEFAULT_NOTE_SCRIPT; use miden_protocol::transaction::RawOutputNote; use miden_protocol::{Felt, Word}; use miden_standards::account::interface::{AccountInterface, AccountInterfaceExt}; @@ -67,7 +68,7 @@ async fn test_send_note_script_basic_wallet() -> anyhow::Result<()> { .with_tag(tag) .with_attachment(attachment.clone()); let assets = NoteAssets::new(vec![sent_asset0, sent_asset1]).unwrap(); - let note_script = CodeBuilder::default().compile_note_script("begin nop end").unwrap(); + let note_script = CodeBuilder::default().compile_note_script(DEFAULT_NOTE_SCRIPT).unwrap(); let serial_num = RandomCoin::new(Word::from([1, 2, 3, 4u32])).draw_word(); let recipient = NoteRecipient::new(serial_num, note_script, NoteStorage::default()); @@ -142,7 +143,7 @@ async fn test_send_note_script_basic_fungible_faucet() -> anyhow::Result<()> { let assets = NoteAssets::new(vec![Asset::Fungible( FungibleAsset::new(sender_basic_fungible_faucet_account.id(), 10).unwrap(), )])?; - let note_script = CodeBuilder::default().compile_note_script("begin nop end").unwrap(); + let note_script = CodeBuilder::default().compile_note_script(DEFAULT_NOTE_SCRIPT).unwrap(); let serial_num = RandomCoin::new(Word::from([1, 2, 3, 4u32])).draw_word(); let recipient = NoteRecipient::new(serial_num, note_script, NoteStorage::default());