chore: port agglayer lock/unlock onto next#2955
Conversation
…2752) * perf(agglayer): selective frontier load/save in bridge-out Replace unconditional load/save of all 32 LET frontier entries with selective operations based on the bit pattern of num_leaves: - load_let_frontier_selective: only loads entries where bit h=1 (entries that will be READ by append_and_update_frontier) - save_let_frontier_selective: only saves entries where bit h=0 (entries that were WRITTEN by append_and_update_frontier) This halves the number of storage map syscalls from 128 to 64 per B2AGG note consumption. Cycle savings vary by frontier occupancy: - Empty tree (0 leaves): 145k -> 116k (-20%) - Populated tree (2^31-1 leaves): 140k -> 60k (-57%) Also adds benchmarks with pre-populated frontier states (2^31 and 2^31-1 leaves) to measure the impact across different access patterns. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address review nits - Add v0.15.0 CHANGELOG entry for selective frontier optimization - Add newline after stack comment to separate logic blocks - Move add.1 to same line as swap u32shr.1 swap - Use if.false instead of eq.0 if.true for bit check - Add stack comment after double_word_array::set https://claude.ai/code/session_01GjjUe9KUggLJMAdiuBw8vk * Apply suggestions from code review Co-authored-by: Marti <marcin.gorny.94@protonmail.com> * perf(agglayer): avoid num_leaves memory round-trip in load_let_frontier_selective Keep num_leaves on the stack across the storew of [num_leaves, 0, 0, 0] to LET_FRONTIER_MEM_PTR, instead of dropping the word and reloading from memory. * chore: clarify load and save docs * docs: read/write frontier with explanations * chore: test large `num_leaves` for selective frontier loading (#2853) * test(agglayer): exercise selective frontier load/save at high num_leaves Adds bridge_out_at_high_num_leaves with two parameterised cases that pre-populate the bridge LET storage and consume one B2AGG note each, verifying the resulting Local Exit Root against an in-process MerkleTreeFrontier32 reference (already Solidity-compatible via test_solidity_mtf_compatibility): - peak_read (num_leaves = 2^31 - 1, binary 0111...1): every bit 0-30 is 1, bit 31 is 0. The masm READs frontier[0..30] from storage and WRITEs frontier[31] back. - peak_write (num_leaves = 2^31, binary 1000...0): every bit 0-30 is 0, bit 31 is 1. The masm READs frontier[31] and WRITEs frontier[0..30]. Together these two single-leaf inserts exercise every height in both READ and WRITE roles. The existing bridge_out_consecutive only walks num_leaves through 0..32, exercising bits 0-4; this fills in bits 5-31. To support pre-populating the frontier, MerkleTreeFrontier32 gains a from_state constructor and is made pub(super). The populate_let_state helper sets storage map keys as Word [0, 0, 0, h] (lo) / [0, 0, 1, h] (hi) — matching what the masm builds via push.0.0.0 / push.1.0.0 with the index at the bottom of the 4-felt key window. * chore: simplify docs; remove silly assertion * test(agglayer): seed initial frontier with a random byte stream Replaces the hand-rolled byte pattern with a seeded StdRng-driven fill, matching the StdRng-from-u64 pattern already used in asset_conversion.rs. Keeps the test deterministic across runs. --------- Co-authored-by: Claude (Opus) <noreply@anthropic.com> --------- Co-authored-by: Claude (Opus) <noreply@anthropic.com> (cherry picked from commit 4e4a496)
…ddress) (#2860) * fix(AggLayer): key faucet registry by (origin_network, origin_token_address) * chore: add CHANGELOG entry for #2860 * test(agglayer): pass origin_network to ConfigAggBridgeNote::create in bridge_out_at_high_num_leaves The new test added on agglayer (#2752) used the old 5-arg ConfigAggBridgeNote::create signature; this PR's signature change (adding origin_network) needed to be threaded through after the merge. * fix(testing): align agglayer-imported tests with new ConfigAggBridgeNote::create signature The merged agglayer cherry-pick introduced tests that called the 5-argument ConfigAggBridgeNote::create and referenced ClaimDataSource::SimulatedL1ToMiden, neither of which exist on this branch. Pass the origin_network argument and use the renamed L1ToMiden variant so these tests compile. * fix: address review comments on bridge_config.masm - register_faucet: correct pad(8) to pad(10) after exec.hash_token_address; callable procs auto-pad the stack to 16, so the annotation must reflect the two zero felts that come up from overflow when hash_token_address consumes 6 felts and produces 4. - lookup_faucet_by_token_address: trim the docblock to drop the contextual "keying on the address alone..." sentence that only made sense in the context of this PR. - hash_token_address: collapse the per-store inline comments back to a single umbrella comment, matching the original's terser style. (cherry picked from commit 0003d0a)
…n assets (#2771) * feat: add scale data to bridge storage * feat: update config_agg_bridge note & fix register_faucet proc * feat: extend faucet registration with full metadata, is_native flag, and metadata hash * feat: replace FPI with bridge-local reads in bridge_out, add lock path for native tokens * feat: add unlock path for native tokens in bridge_in, plus lock/unlock tests * chore: add changelog entry & bump rand to fix cargo-deny Bumps rand 0.9.2 → 0.9.4 and rand 0.10.0 → 0.10.1 to resolve RUSTSEC-2026-0097 flagged by `cargo deny check` in CI. * refactor: slim faucet & update SPEC section * style: rustfmt import grouping * refactor: tighten MASM stack ops, normalize stack-comment style * refactor: cleanup masm & stack comments * refactor: pack ConfigAggBridgeNote::create args into ConversionMetadata Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor: split bridge_in output-note emission into bridge_in_output.masm Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor: fix stack-comment depths in CONFIG_AGG_BRIDGE.masm The inter-call dropw sequences were removed, so the input pad(12) now persists across both call boundaries. Update every # => [...] comment to reflect the true sdepth (verified with sdepth debug.stack drop): pad(16) through the setup, pad(32) after call 1, pad(48) after call 2, pad(16) after the final repeat.8 cleanup. * refactor: clarify docstrings and add native-path duplicate-claim test - bridge_config.masm: reword the register_faucet local-offset comment and move an implementation detail out of get_faucet_metadata_hash's docstring into an inline comment. - bridge_in_output.masm: document that unlock_and_send's replay safety comes from the nullifier check in bridge_in::claim, not serial-number uniqueness. - bridge_in.rs: add bridge_in_unlock_native_duplicate_rejected. Seeds the bridge vault with 2x the claim amount so the nullifier is the only thing stopping a second unlock, then asserts the replay fails with ERR_CLAIM_ALREADY_SPENT. * refactor: dedupe shared CLAIM_* consts and colocate UNLOCK_*_LOC Make CLAIM_OUTPUT_NOTE_FAUCET_AMOUNT and CLAIM_PROOF_DATA_KEY_MEM_ADDR public in bridge_in.masm and import them from bridge_in_output.masm, removing the duplicate definitions and the "keep in sync" comments. Move UNLOCK_*_LOC consts to sit immediately above unlock_and_send so the local-memory layout lives next to the procedure that uses it. * refactor: apply PR suggestions Co-authored-by: Marti <marcin.gorny.94@protonmail.com> * refactor(agglayer): drop redundant memory layout description in CONFIG_AGG_BRIDGE.masm The 18-felt note storage layout was documented in three places: the top-of-file comment block, the constant definitions immediately below, and the docblock above 'begin'. The constants are self-documenting and the docblock covers it for callers, so drop the top-of-file block. * refactor(agglayer): add mem_load_double_word_unaligned helper Mirror mem_store_double_word_unaligned in asm/agglayer/common/utils.masm and use it in CONFIG_AGG_BRIDGE.masm to load the metadata-hash double word from offset 10..17, replacing eight individual mem_load.METADATA_HASH_* ops with a single push.METADATA_HASH_LO_0 exec.utils::mem_load_double_word_unaligned. * refactor(agglayer): pre-pad CONFIG_AGG_BRIDGE call frames to drop the movdn dance Push the call's 6 trailing pad zeros first, then build the args on top, instead of pushing args first and then doing 'movdn.15 movdn.15 movdn.15 movdn.15 movdn.15 movdn.15' to move 6 pads to the bottom. The end-state stack is identical; this just removes a 6-instruction shuffle from each of the two call sites. * refactor(agglayer): introduce sub-key constants for faucet_metadata_map Replace literal push.0.0 / push.0.1 / push.0.2 / push.0.3 sub-key prefixes with named constants FAUCET_METADATA_SUBKEY_{ADDR_LO,ADDR_HI, HASH_LO,HASH_HI}. Only the metadata-map sites are touched; push.0.0 in faucet_registry / token_registry contexts is a different map and stays literal. * refactor(agglayer): cheaper register_faucet local stash via repeated movup.5 Replace movup.9/.8/.7/.6/.5 with five repeated movup.5 starting from the now-deepest non-address element. Each movup always lifts from depth 5 (instead of climbing 9→5), and the post-iteration stack is identical. * refactor(agglayer): use repeat.5 dup.4 to duplicate origin token address Replace 'dup.4 dup.4 dup.4 dup.4 dup.4' with 'repeat.5 dup.4 end'. The five-fold dup-from-depth-4 produces the same stack pattern (top word is the duplicate address with addr0 at the very top) which is what hash_token_address expects when it stores the 5-felt address into local memory before computing Poseidon2. * refactor(agglayer): unify get_faucet_metadata_hash key-prep with sibling proc Rewrite get_faucet_metadata_hash to use the same 'prep both keys first, then swapw between reads' pattern as get_faucet_conversion_info. The previous shape used 'dup.1 dup.1 ... movup.5 movup.5' to reshuffle faucet_id around the first read, which read awkwardly next to a sibling proc with the opposite mechanism. This subsumes the swapw / movup.5 movup.5 oddity Marti called out. * refactor(agglayer): correct stack-padding annotations in register_faucet Re-walk stack annotations from proc-entry through Steps 2-4. Pads grow on pops (via Miden's auto-padding to depth-16 floor) and stay constant on pushes; the previous comments treated them as if pad+named always equaled 16, which understated the depth after each new push. * refactor(agglayer): clarify is_faucet_native trailing-zeros comment 'sink ... below the trailing zeros' was misleading because the stack direction it implied is the opposite of what movdn.2 does. Reword to 'move is_native past the two trailing zeros'. * refactor(agglayer): clarify unlock_and_send local-stash comment The previous comment referenced 'claim's CLAIM_DEST_ID_*_LOCAL' and the 'exec invocations get their own local frame' detail, both of which are distracting. State the actual reason for the stash: we'll need the destination ID again after native_account::remove_asset clears the stack. * docs(agglayer): document native faucet registration and updated bridge flows - Section 2.1 (bridge-out): drop FPI references, replace with bridge-local faucet_metadata_map reads. Add the is_native dispatch (BURN vs lock_asset). - Section 2.2 (bridge-in): drop the unconditional MINT description, replace with the is_native dispatch (MINT vs remove_asset + direct P2ID). Update the token-registry lookup to mention the (address, network) pair. - Section 2.4 (faucet registration): describe the CONFIG_AGG_BRIDGE 18-felt payload and the two-call register_faucet / store_faucet_metadata_hash flow. - Section 7.1: rename to 'Registering faucets on Miden' (covers both wrapped and Miden-native faucets), and add a new sub-section explaining the wrapped-vs-native distinction at the dispatch level. * Apply suggestions from code review Co-authored-by: Marti <marcin.gorny.94@protonmail.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Marti <marcin.gorny.94@protonmail.com> Co-authored-by: Marti <marti@miden.team> (cherry picked from commit 9c9f847)
…o-next-sync # Conflicts: # crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm
…o-next-sync # Conflicts: # crates/miden-agglayer/src/faucet.rs # crates/miden-testing/tests/agglayer/bridge_out.rs
| /// URI + external link). Conversion metadata is no longer stored on the faucet; the bridge holds | ||
| /// it in `faucet_metadata_map`. |
There was a problem hiding this comment.
this comment should be adjusted to avoid documenting the changes specific to this PR.
I believe it would have been avoided had we already had e047a3a merged
| // mutability + description + logo URI + external link). Conversion metadata lives on | ||
| // the bridge, so the faucet adds no bridge-specific slots. |
| /// | ||
| /// The builder includes: | ||
| /// - The `AggLayerFaucet` component (conversion metadata + token metadata). | ||
| /// - The `AggLayerFaucet` component (token metadata only; conversion metadata lives on the bridge). |
There was a problem hiding this comment.
same here (this comment was cherry-picked verbatim, so it looks like we missed this comment on the original PR)
| // For mainnet and rollup fixtures, create the destination account so we can consume the P2ID | ||
| // note. The mock account is built from the destination ID encoded in the JSON test vector, | ||
| // since the claim note targets this account ID. | ||
| let destination_account = | ||
| if matches!(data_source, ClaimDataSource::L1ToMiden | ClaimDataSource::L2ToMiden) { | ||
| let dest = Account::mock(u128::from(destination_account_id), IncrNonceAuthComponent); | ||
| builder.add_account(dest.clone())?; | ||
| Some(dest) | ||
| } else { | ||
| None |
There was a problem hiding this comment.
the way we construct the destination account changed in the original PR, and it's not reflected here. Specifically, we no longer match on the data source, and instead always create a deterministic account
…o-next-sync rustfmt
fb2341b to
a8732d1
Compare
|
Since these changes were supposed to be merged w/o squashing, I rebased the commits (squash the fmt commit into the last merge commit) and force pushed, to end up with a clean git history on |
Brings the agglayer lock/unlock port (#2955) and other recent next changes (Account renames, AssetAmount, AccountComponentName) into the MINT-faucet-bind branch. Conflict resolution: MINT note construction has moved out of bridge_in.masm into bridge_in_output.masm on next. The PR's 22-felt MINT layout (ASSET_KEY + ASSET_VALUE for faucet binding) and the associated write_mint_note_storage / build_mint_recipient procedures have been transplanted onto bridge_in_output.masm. The cross-faucet regression test (test_mint_cannot_be_consumed_by_unrelated_faucet) and the AuthNetworkAccount regression tests have been updated to the new ConfigAggBridgeNote::create + create_existing_agglayer_faucet signatures (ConversionMetadata struct, no per-faucet origin token / network / scale / metadata_hash args). make lint and the full make test suite (1084 tests) pass locally.
Brings the agglayer lock/unlock port (#2955) and other recent next changes (Account renames, AssetAmount, AccountComponentName) into the MINT-faucet-bind branch. Conflict resolution: MINT note construction has moved out of bridge_in.masm into bridge_in_output.masm on next. The PR's 22-felt MINT layout (ASSET_KEY + ASSET_VALUE for faucet binding) and the associated write_mint_note_storage / build_mint_recipient procedures have been transplanted onto bridge_in_output.masm. The cross-faucet regression test (test_mint_cannot_be_consumed_by_unrelated_faucet) and the AuthNetworkAccount regression tests have been updated to the new ConfigAggBridgeNote::create + create_existing_agglayer_faucet signatures (ConversionMetadata struct, no per-faucet origin token / network / scale / metadata_hash args). make lint and the full make test suite (1084 tests) pass locally. fixup: include MINT-bind merge fixes that were not staged These three files were edited during the merge but never re-staged before the merge commit (d843c4e), so the pushed commit contained the auto-merged state without the manual fixups. CI failed because: - bridge_in_output.masm was missing the 22-felt MINT layout (ASSET_KEY/ASSET_VALUE for faucet binding); kept the old 14-felt shape. - bridge_in.rs's cross-faucet regression test still called the old 10-arg create_existing_agglayer_faucet and the old 5-arg ConfigAggBridgeNote::create. - network_account_regression.rs still called the old 10-arg create_existing_agglayer_faucet. cargo clippy --workspace --all-targets --all-features -- -D warnings and cargo check --workspace --all-targets --all-features are clean locally after this commit; targeted runs of agglayer::bridge_in::* and agglayer::network_account_regression::* tests pass.
* docs(agglayer): drop port-narrative phrases from faucet docstrings The "conversion metadata lives on the bridge" clause was useful for the PR 0xMiden#2771 / 0xMiden#2955 port narrative but is not load-bearing for readers of the merged code. Trim it from the AggLayerFaucet docstring in lib.rs and the inline comment in faucet.rs. * test(agglayer): always construct deterministic destination account The conditional matches! arm in test_bridge_in_claim_to_p2id enumerates both ClaimDataSource variants (L1ToMiden | L2ToMiden) — the only two — so the branch is always taken and the Option<Account> wrapping forces a needless if let Some(...) around the post-mint consume+assert block. Construct destination_account unconditionally and flatten the consume block to match the pattern used by the other bridge_in tests. --------- Co-authored-by: Claude (Opus) <noreply@anthropic.com> Co-authored-by: Alexander John Lee <77119221+partylikeits1983@users.noreply.github.com>
Purpose
The
agglayerandnextbranches diverged on 2026-03-30 (merge-base954a37bc). With the release approaching, this PR brings theagglayer-only commits ontonextso the release branch is complete. It is the inverse direction of #2951 (which backportednext→agglayer).I reviewed every PR merged to
agglayerin the past ~4 weeks and cross-referenced each againstnext's history by PR number. Most had already flowed tonextunder different hashes (git cherryover-reported them due to patch-id drift). Only three commits were genuinely missing — and they are exactly the lock/unlock cluster.Commits
Cherry-picked (with
-x), oldest first:#2752— perf(agglayer): selective frontier load/save inB2AGGprocessing#2860— fix(AggLayer): key faucet registry by(origin_network, origin_token_address)#2771— feat: lock/unlock for native Miden assets (the main feature, ~20 files)#2752and#2860cherry-picked cleanly modulonext'sFelt::new→Felt::new_uncheckedchange and theAssetAmount/AccountStorageMode/ faucet-flag renames.Note on
#2771: this is an adapted port, not a verbatim cherry-picknextandagglayermade incompatible faucet design choices after they forked, so#2771could not be cherry-picked verbatim — it was re-implemented onnext's architecture:agglayer,#2771moved conversion metadata (origin address, network, scale, metadata hash) off the faucet and onto the bridge (faucet_metadata_map), and was written against the old faucet API (TokenMetadata,OwnerControlled, pre-AuthNetworkAccount).nexthad independently rebuilt the AggLayer faucet on the newFungibleFaucet+TokenPolicyManager+Authority+AuthNetworkAccountstack (fix(agglayer): replaceNoAuthwithAuthNetworkAccounton bridge and faucet #2818, refactor: MergeBasicFungibleFaucetandNetworkFungibleFaucet#2890, refactor: add bon::builder toFungibleFaucet#2916, refactor: Extract Mint and Burn PolicyManager into their own component #2821), and the note layer changed too (NoteScriptRoot,PartialNoteMetadata, renamed recipient/attachment procedures, miden-vm v0.23).The port keeps
#2771's design (faucet holds only token metadata; the bridge holds conversion data and locks native assets in its own vault) but expresses it onnext's current API:AggLayerFaucetis now a thin wrapper overnext'sFungibleFaucetwith no bridge-specific storage slots.bridge_in_output.masmkeeps#2771's genuinely new native-tokenunlock_and_sendprocedure, but its wrapped-token MINT path usesnext's existing, proven 14-felt MINT-note machinery (note::compute_and_store_recipient,output_note::add_word_attachment) rather thanagglayer's 18-felt attachment-in-storage layout —next's MINT note script expects the 14-felt layout.pubasnext's assembler (miden-vm v0.23) requires.CONFIG_AGG_BRIDGEnote creation usesnext'sPartialNoteMetadata+NoteAttachmentsAPI.next's signatures (ConfigAggBridgeNote::create(ConversionMetadata, …), 6-argcreate_existing_agglayer_faucet,ClaimNote::create,AccountIdVersion::Version1,AccountStorageMode::Public).Intentionally excluded (already on
next)Checked by PR number and confirmed present on
next, so not cherry-picked:#2730(87dcd642),#2745(5b871b7c),#2746(f2c66258),#2759(588ca081), and#2848(redundant — it is itself a cherry-pick of#2759).Verification
cargo build --workspace— clean.cargo clippy --workspace --all-targets— clean.cargo test -p miden-testing --test lib -- agglayer— 56 passed, 0 failed, including the lock/unlock testsbridge_in_unlock_native_token,bridge_in_unlock_native_duplicate_rejected, andbridge_out_lock_native_token, plus#2752'sbridge_out_at_high_num_leavesand#2860'stest_config_agg_bridge_distinguishes_origin_network/test_claim_fails_when_origin_network_unregisteredregression tests.🤖 Generated with Claude Code