From eb532dab1867a1e14519978a21f534dbb25658cf Mon Sep 17 00:00:00 2001 From: Wiktor Starczewski Date: Wed, 2 Sep 2026 12:15:50 +0200 Subject: [PATCH 01/12] refactor(standards): split fee estimation out of pay_fee Port of #3784 from release/v0.16.0-rc to next. `fee::estimate_fee` owns everything up to the single `tx::compute_fee`, pricing the sponsorship notes through the new `fees::estimate_network_note_sponsorships`; `pay_fee` composes it with `fees::create_network_note_sponsorships` and the unchanged payment tail. Adapted to next: `tx::get_fee_asset_id` replaces `get_fee_faucet_id`, which made the `pay_network_note_sponsorships` wrapper a single call, so it was dropped as suggested in the rc review; `to_amount_unchecked` follows the next rename. Unlike rc, `pay_fee` reads the fee asset ID once for both passes and the margin math reuses the output-note count the walk already read: the rc shape added two kernel calls per fee payment, which pushed the "consume two P2ID notes with ECDSA signing" bench scenario past the 16384-row padding bracket pinned by the trace-contract guard. Cost tables regenerated on next. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01KkQzFtRjyDwQ7iVtRbsHsn --- CHANGELOG.md | 1 + bin/bench-transaction/bench-tx.json | 962 +++++++++--------- crates/miden-agglayer/src/costs/table.rs | 16 +- .../asm/standards/fee/mod.masm | 181 +++- .../asm/standards/fees/mod.masm | 238 +++-- .../miden-standards/src/note/costs/table.rs | 46 +- .../tests/auth/fee_payment/sponsorship.rs | 88 +- .../tests/scripts/fee_collection.rs | 20 +- 8 files changed, 888 insertions(+), 664 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ab88b5679..3a6527adb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ ### Changes +- [BREAKING] Split `fee::estimate_fee` out of `fee::pay_fee`, pricing the FEE_SPONSORSHIP notes via the new `fees::estimate_network_note_sponsorships` without creating them; the MAST roots of every fee-paying component change ([#3784](https://github.com/0xMiden/protocol/pull/3784)). - Added a check that the guardian public key is not one of the approver public keys ([#3764](https://github.com/0xMiden/protocol/pull/3764)). - [BREAKING] Incremented the MSRV to 1.98. - [BREAKING] Updated the Miden VM and crypto crate family to v0.30.0 and `midenc-hir-type` to v0.12.0. `LocalTransactionProver::new` now takes `miden_prover::Prover`, `CoreLibrary` exposes one merged package, and `TransactionVerifier::verify` now returns `VerificationOutcome` so callers can handle outstanding precompile work ([#3782](https://github.com/0xMiden/protocol/pull/3782)). diff --git a/bin/bench-transaction/bench-tx.json b/bin/bench-transaction/bench-tx.json index 00e3277bcf..ca895bf5d9 100644 --- a/bin/bench-transaction/bench-tx.json +++ b/bin/bench-transaction/bench-tx.json @@ -1,1073 +1,1073 @@ { "consume single P2ID note with Falcon signing": { - "prologue": 4702, + "prologue": 5329, "notes_processing": 2173, "note_execution": { - "0xd3d5bda9d758f4cef5e25bfe4c1e4855fbc43854e3843029fc82418b27c46111": 2131 + "0xac68d3992d557007868148823bb2aebcb3623c6aede5880a01f816d93e872a86": 2131 }, "tx_script_processing": 41, "epilogue": { - "total": 73750, - "auth_procedure": 72613 + "total": 73822, + "auth_procedure": 72685 }, "trace": { - "core_rows": 80710, - "chiplets_rows": 11555, - "poseidon2_permutation_rows": 54864, - "range_rows": 20649, + "core_rows": 81409, + "chiplets_rows": 11491, + "poseidon2_permutation_rows": 55328, + "range_rows": 20631, "chiplets_shape": { - "hasher_rows": 8496, - "bitwise_rows": 648, - "memory_rows": 2409, + "hasher_rows": 8648, + "bitwise_rows": 656, + "memory_rows": 2185, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume single P2ID note with ECDSA signing": { - "prologue": 4702, + "prologue": 5329, "notes_processing": 2173, "note_execution": { - "0x3d8e909872ba6287f8bcabd40df2f2f2e1cfb72187fce1095f7937950c749d02": 2131 + "0x10fda265c86244d95a08747945d8457fd180fd02760ad0f7f26df0c447658bbc": 2131 }, "tx_script_processing": 41, "epilogue": { - "total": 6112, - "auth_procedure": 4975 + "total": 6059, + "auth_procedure": 4922 }, "trace": { - "core_rows": 13072, - "chiplets_rows": 5812, - "poseidon2_permutation_rows": 19760, - "range_rows": 1899, + "core_rows": 13646, + "chiplets_rows": 5859, + "poseidon2_permutation_rows": 19824, + "range_rows": 2011, "chiplets_shape": { - "hasher_rows": 4144, - "bitwise_rows": 904, - "memory_rows": 762, + "hasher_rows": 4216, + "bitwise_rows": 848, + "memory_rows": 793, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume two P2ID notes with Falcon signing": { - "prologue": 5561, + "prologue": 5646, "notes_processing": 4528, "note_execution": { - "0x1334ee47271cac281b9ac957515d6d035aeaa0b773c0915f7f0645e2f04ef2e9": 2346, - "0x3f404f40d40f4aa97d9b67011bf67cbfce33d051a37ac61ed01e06bdf57a8331": 2131 + "0x7810979ea9cba8903202d8593afe04f967963d53c5b9c1c43a926b8be0572e80": 2131, + "0x7b934ca16878579b7f254b3c2fa8d27047b2d4e27d676f0b1b594501e15ab157": 2346 }, "tx_script_processing": 41, "epilogue": { - "total": 73678, - "auth_procedure": 72577 + "total": 73750, + "auth_procedure": 72649 }, "trace": { - "core_rows": 83852, - "chiplets_rows": 13588, - "poseidon2_permutation_rows": 55472, - "range_rows": 20387, + "core_rows": 84009, + "chiplets_rows": 13390, + "poseidon2_permutation_rows": 55584, + "range_rows": 20585, "chiplets_shape": { - "hasher_rows": 10024, - "bitwise_rows": 1016, - "memory_rows": 2546, + "hasher_rows": 10064, + "bitwise_rows": 1032, + "memory_rows": 2292, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume two P2ID notes with ECDSA signing": { - "prologue": 5561, + "prologue": 5646, "notes_processing": 4528, "note_execution": { - "0x1334ee47271cac281b9ac957515d6d035aeaa0b773c0915f7f0645e2f04ef2e9": 2346, - "0x3f404f40d40f4aa97d9b67011bf67cbfce33d051a37ac61ed01e06bdf57a8331": 2131 + "0x7810979ea9cba8903202d8593afe04f967963d53c5b9c1c43a926b8be0572e80": 2131, + "0x7b934ca16878579b7f254b3c2fa8d27047b2d4e27d676f0b1b594501e15ab157": 2346 }, "tx_script_processing": 41, "epilogue": { - "total": 6040, - "auth_procedure": 4939 + "total": 5987, + "auth_procedure": 4886 }, "trace": { - "core_rows": 16214, - "chiplets_rows": 7845, - "poseidon2_permutation_rows": 20384, - "range_rows": 1487, + "core_rows": 16246, + "chiplets_rows": 7758, + "poseidon2_permutation_rows": 20080, + "range_rows": 1397, "chiplets_shape": { - "hasher_rows": 5672, - "bitwise_rows": 1272, - "memory_rows": 899, + "hasher_rows": 5632, + "bitwise_rows": 1224, + "memory_rows": 900, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "create single P2ID note with Falcon signing": { - "prologue": 2065, + "prologue": 2080, "notes_processing": 35, "note_execution": {}, "tx_script_processing": 1883, "epilogue": { - "total": 75283, - "auth_procedure": 73197 + "total": 75557, + "auth_procedure": 73471 }, "trace": { - "core_rows": 79310, - "chiplets_rows": 11049, - "poseidon2_permutation_rows": 53152, - "range_rows": 20411, + "core_rows": 79599, + "chiplets_rows": 10903, + "poseidon2_permutation_rows": 53216, + "range_rows": 20307, "chiplets_shape": { - "hasher_rows": 8144, - "bitwise_rows": 600, - "memory_rows": 2303, + "hasher_rows": 8232, + "bitwise_rows": 616, + "memory_rows": 2053, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "create single P2ID note with ECDSA signing": { - "prologue": 2065, + "prologue": 2080, "notes_processing": 35, "note_execution": {}, "tx_script_processing": 1883, "epilogue": { - "total": 7645, - "auth_procedure": 5559 + "total": 7794, + "auth_procedure": 5708 }, "trace": { - "core_rows": 11672, - "chiplets_rows": 5314, - "poseidon2_permutation_rows": 18048, - "range_rows": 1273, + "core_rows": 11836, + "chiplets_rows": 5271, + "poseidon2_permutation_rows": 17664, + "range_rows": 1225, "chiplets_shape": { "hasher_rows": 3800, - "bitwise_rows": 856, - "memory_rows": 656, + "bitwise_rows": 808, + "memory_rows": 661, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume CLAIM note (L1 to Miden)": { - "prologue": 5010, - "notes_processing": 28303, + "prologue": 5060, + "notes_processing": 28215, "note_execution": { - "0xb92e6fd5ec3cb3d21fd8c207f67b385e1b06393b0476aff1dd17ad9bad1b8fbc": 28261 + "0x49cdb9cd15380c349a8fc22400ed05d402962d4eda56e9e3a2671030b8d60125": 28173 }, "tx_script_processing": 41, "epilogue": { - "total": 16614, - "auth_procedure": 11345 + "total": 21297, + "auth_procedure": 16028 }, "trace": { - "core_rows": 50012, - "chiplets_rows": 20291, - "poseidon2_permutation_rows": 41392, - "range_rows": 3469, + "core_rows": 54657, + "chiplets_rows": 21842, + "poseidon2_permutation_rows": 41808, + "range_rows": 3639, "chiplets_shape": { - "hasher_rows": 13248, - "bitwise_rows": 2816, - "memory_rows": 4225, + "hasher_rows": 14424, + "bitwise_rows": 2904, + "memory_rows": 4512, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume CLAIM note (L2 to Miden)": { - "prologue": 5010, - "notes_processing": 38465, + "prologue": 5060, + "notes_processing": 38185, "note_execution": { - "0x5ab9bf3e5894314d51d1224cbe74b44dde6ac77220c3686ac17faef2a25169ba": 38423 + "0x8f113a459b0a87ac90f3c184d1ebfd81cb09076fadb64bff39609eaa14209ce9": 38143 }, "tx_script_processing": 41, "epilogue": { - "total": 16614, - "auth_procedure": 11345 + "total": 21297, + "auth_procedure": 16028 }, "trace": { - "core_rows": 60174, - "chiplets_rows": 23041, - "poseidon2_permutation_rows": 43536, - "range_rows": 3693, + "core_rows": 64627, + "chiplets_rows": 24400, + "poseidon2_permutation_rows": 43952, + "range_rows": 3809, "chiplets_shape": { - "hasher_rows": 14800, - "bitwise_rows": 3072, - "memory_rows": 5167, + "hasher_rows": 15784, + "bitwise_rows": 3160, + "memory_rows": 5454, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume B2AGG note (bridge-out)": { - "prologue": 5317, - "notes_processing": 119280, + "prologue": 5367, + "notes_processing": 120008, "note_execution": { - "0x8045f362a182de06bb18d6a3d86fa86e2c379c5eb9cf379cf943f4d54d0eedae": 119238 + "0x71b6eb992761503187babb3bf70dcab321dc00465ecb077ab1a1b2289eedf4f6": 119966 }, "tx_script_processing": 41, "epilogue": { - "total": 25157, - "auth_procedure": 10184 + "total": 29753, + "auth_procedure": 14780 }, "trace": { - "core_rows": 149839, - "chiplets_rows": 71275, - "poseidon2_permutation_rows": 114000, - "range_rows": 4873, + "core_rows": 155213, + "chiplets_rows": 72879, + "poseidon2_permutation_rows": 114400, + "range_rows": 6037, "chiplets_shape": { - "hasher_rows": 57240, - "bitwise_rows": 3632, - "memory_rows": 10401, + "hasher_rows": 58464, + "bitwise_rows": 3720, + "memory_rows": 10693, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume B2AGG note (bridge-out, 2^31 leaves)": { - "prologue": 5317, - "notes_processing": 117589, + "prologue": 5367, + "notes_processing": 118317, "note_execution": { - "0x8045f362a182de06bb18d6a3d86fa86e2c379c5eb9cf379cf943f4d54d0eedae": 117547 + "0x71b6eb992761503187babb3bf70dcab321dc00465ecb077ab1a1b2289eedf4f6": 118275 }, "tx_script_processing": 41, "epilogue": { - "total": 24869, - "auth_procedure": 10184 + "total": 29465, + "auth_procedure": 14780 }, "trace": { - "core_rows": 147860, - "chiplets_rows": 70296, - "poseidon2_permutation_rows": 115392, - "range_rows": 4933, + "core_rows": 153234, + "chiplets_rows": 71900, + "poseidon2_permutation_rows": 115792, + "range_rows": 6009, "chiplets_shape": { - "hasher_rows": 56384, - "bitwise_rows": 3632, - "memory_rows": 10278, + "hasher_rows": 57608, + "bitwise_rows": 3720, + "memory_rows": 10570, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume B2AGG note (bridge-out, 2^31-1 leaves)": { - "prologue": 5317, - "notes_processing": 63420, + "prologue": 5367, + "notes_processing": 64148, "note_execution": { - "0x8045f362a182de06bb18d6a3d86fa86e2c379c5eb9cf379cf943f4d54d0eedae": 63378 + "0x71b6eb992761503187babb3bf70dcab321dc00465ecb077ab1a1b2289eedf4f6": 64106 }, "tx_script_processing": 41, "epilogue": { - "total": 16229, - "auth_procedure": 10184 + "total": 20825, + "auth_procedure": 14780 }, "trace": { - "core_rows": 85051, - "chiplets_rows": 40438, - "poseidon2_permutation_rows": 51824, - "range_rows": 3789, + "core_rows": 90425, + "chiplets_rows": 42042, + "poseidon2_permutation_rows": 52224, + "range_rows": 5025, "chiplets_shape": { - "hasher_rows": 30216, - "bitwise_rows": 3632, - "memory_rows": 6588, + "hasher_rows": 31440, + "bitwise_rows": 3720, + "memory_rows": 6880, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume P2ID note (network account)": { - "prologue": 4079, + "prologue": 4129, "notes_processing": 2173, "note_execution": { - "0x9c9eb8d97ddf61a38a3fdd6624d6dfe5db7ad6622862f0af7c912304e57b5a8a": 2131 + "0xa0711df1e3c6477a0edb404719c5b1215bcb15485541364be8e55d1ae6d3658d": 2131 }, "tx_script_processing": 41, "epilogue": { - "total": 12327, - "auth_procedure": 9064 + "total": 13013, + "auth_procedure": 9750 }, "trace": { - "core_rows": 18664, - "chiplets_rows": 8512, - "poseidon2_permutation_rows": 26080, - "range_rows": 1491, + "core_rows": 19400, + "chiplets_rows": 8784, + "poseidon2_permutation_rows": 26448, + "range_rows": 1619, "chiplets_shape": { - "hasher_rows": 6488, + "hasher_rows": 6720, "bitwise_rows": 920, - "memory_rows": 1102, + "memory_rows": 1142, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume P2ID note (16 assets, network account)": { - "prologue": 12524, + "prologue": 12574, "notes_processing": 29817, "note_execution": { - "0xcc4ef28945fffb42a774a2b037552c8fa5064b3f345ff8f6678ff8e4f64b4a97": 29775 + "0x4f3b199ab4df30bf543e1b4bc9db654b9f2fb552186c4122ceed28c6dfde8693": 29775 }, "tx_script_processing": 41, "epilogue": { - "total": 14806, - "auth_procedure": 9383 + "total": 15492, + "auth_procedure": 10069 }, "trace": { - "core_rows": 57232, - "chiplets_rows": 32968, - "poseidon2_permutation_rows": 42576, - "range_rows": 3571, + "core_rows": 57968, + "chiplets_rows": 33240, + "poseidon2_permutation_rows": 42944, + "range_rows": 3953, "chiplets_shape": { - "hasher_rows": 24944, + "hasher_rows": 25176, "bitwise_rows": 5120, - "memory_rows": 2902, + "memory_rows": 2942, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume P2IDE note (claim, network account)": { - "prologue": 4079, + "prologue": 4129, "notes_processing": 2281, "note_execution": { - "0xc8f84ad6166b2a1d26ed42023d715af78c2c04fcdd8b4713f87df85ba4fe580d": 2239 + "0x36fb7979b522f0f940eb1c56a26dea673042bb1305ed18114e1256985312d126": 2239 }, "tx_script_processing": 41, "epilogue": { - "total": 12327, - "auth_procedure": 9064 + "total": 13013, + "auth_procedure": 9750 }, "trace": { - "core_rows": 18772, - "chiplets_rows": 8548, - "poseidon2_permutation_rows": 26176, - "range_rows": 1509, + "core_rows": 19508, + "chiplets_rows": 8812, + "poseidon2_permutation_rows": 26544, + "range_rows": 1595, "chiplets_shape": { - "hasher_rows": 6520, + "hasher_rows": 6744, "bitwise_rows": 920, - "memory_rows": 1106, + "memory_rows": 1146, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume P2IDE note (claim, 16 assets, network account)": { - "prologue": 12524, + "prologue": 12574, "notes_processing": 29925, "note_execution": { - "0x796094cec656ed0e408b604733714f240f7b214079522c7e2fabc2fa72a9f635": 29883 + "0xbdb2ac27ff4e464791d0938f04f518681819ba3928a38f1cd72d994c43038f8d": 29883 }, "tx_script_processing": 41, "epilogue": { - "total": 14806, - "auth_procedure": 9383 + "total": 15492, + "auth_procedure": 10069 }, "trace": { - "core_rows": 57340, - "chiplets_rows": 33004, - "poseidon2_permutation_rows": 42672, - "range_rows": 3511, + "core_rows": 58076, + "chiplets_rows": 33268, + "poseidon2_permutation_rows": 43040, + "range_rows": 3965, "chiplets_shape": { - "hasher_rows": 24976, + "hasher_rows": 25200, "bitwise_rows": 5120, - "memory_rows": 2906, + "memory_rows": 2946, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume P2IDE note (reclaim, network account)": { - "prologue": 4182, + "prologue": 4232, "notes_processing": 2333, "note_execution": { - "0x4ef3f702e0697a657064ceddbd98308cc21bdc41ebb88383af34dbb1fe914097": 2291 + "0x3cd1c6d1cbff7db055d275341abcc500a42fd05d0adcee88d3653eaf10da3c62": 2291 }, "tx_script_processing": 41, "epilogue": { - "total": 12327, - "auth_procedure": 9064 + "total": 13013, + "auth_procedure": 9750 }, "trace": { - "core_rows": 18927, - "chiplets_rows": 8579, - "poseidon2_permutation_rows": 26368, - "range_rows": 1465, + "core_rows": 19663, + "chiplets_rows": 8843, + "poseidon2_permutation_rows": 26736, + "range_rows": 1585, "chiplets_shape": { - "hasher_rows": 6544, + "hasher_rows": 6768, "bitwise_rows": 920, - "memory_rows": 1113, + "memory_rows": 1153, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume SWAP note (public payback, network account)": { - "prologue": 4088, + "prologue": 4138, "notes_processing": 4537, "note_execution": { - "0xb3fdf5a2f247e8413f633bdafb05e1bdeb1171cb38b5e044b6c3a0de8303b468": 4495 + "0xe032d1b4e4b5b59927ca83c90a2d02389a3e4f0f14e4f9c5f4669b98acac5bfa": 4495 }, "tx_script_processing": 41, "epilogue": { - "total": 13989, - "auth_procedure": 9585 + "total": 14877, + "auth_procedure": 10473 }, "trace": { - "core_rows": 22699, - "chiplets_rows": 10586, - "poseidon2_permutation_rows": 28208, - "range_rows": 1779, + "core_rows": 23637, + "chiplets_rows": 10934, + "poseidon2_permutation_rows": 28576, + "range_rows": 1867, "chiplets_shape": { - "hasher_rows": 8072, - "bitwise_rows": 1256, - "memory_rows": 1256, + "hasher_rows": 8344, + "bitwise_rows": 1288, + "memory_rows": 1300, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume SWAP note (private payback, network account)": { - "prologue": 4088, + "prologue": 4138, "notes_processing": 4034, "note_execution": { - "0x1e47ad29927705f58e53bcec4c691a22d5f09909a28b3508a425a06a09d44d00": 3992 + "0xf81b87014953b8bfa7880c4ca404982c6acde8f1ab33241013a3eaacd2f7a015": 3992 }, "tx_script_processing": 41, "epilogue": { - "total": 13989, - "auth_procedure": 9585 + "total": 14877, + "auth_procedure": 10473 }, "trace": { - "core_rows": 22196, - "chiplets_rows": 10457, - "poseidon2_permutation_rows": 27888, - "range_rows": 1761, + "core_rows": 23134, + "chiplets_rows": 10813, + "poseidon2_permutation_rows": 28256, + "range_rows": 1873, "chiplets_shape": { - "hasher_rows": 7960, - "bitwise_rows": 1256, - "memory_rows": 1239, + "hasher_rows": 8240, + "bitwise_rows": 1288, + "memory_rows": 1283, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume PSWAP note (full fill, network account)": { - "prologue": 4079, - "notes_processing": 7336, + "prologue": 4129, + "notes_processing": 7341, "note_execution": { - "0x4f0fa61b2270c6cd3a193ed86d652c51335c0ab8271fb32aa2812ff540bd57cb": 7294 + "0xd002f96b26ae44a19bb6e2ef8c4bb0b18dfc13448df8e8bf7622c256bf5ab2cc": 7299 }, "tx_script_processing": 41, "epilogue": { - "total": 14029, - "auth_procedure": 9585 + "total": 14917, + "auth_procedure": 10473 }, "trace": { - "core_rows": 25529, - "chiplets_rows": 11423, - "poseidon2_permutation_rows": 30752, - "range_rows": 1861, + "core_rows": 26472, + "chiplets_rows": 11779, + "poseidon2_permutation_rows": 31104, + "range_rows": 1989, "chiplets_shape": { - "hasher_rows": 8592, - "bitwise_rows": 1384, - "memory_rows": 1445, + "hasher_rows": 8872, + "bitwise_rows": 1416, + "memory_rows": 1489, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume PSWAP note (partial fill, network account)": { - "prologue": 4079, - "notes_processing": 9836, + "prologue": 4129, + "notes_processing": 9846, "note_execution": { - "0x4f0fa61b2270c6cd3a193ed86d652c51335c0ab8271fb32aa2812ff540bd57cb": 9794 + "0xd002f96b26ae44a19bb6e2ef8c4bb0b18dfc13448df8e8bf7622c256bf5ab2cc": 9804 }, "tx_script_processing": 41, "epilogue": { - "total": 15446, - "auth_procedure": 9789 + "total": 16538, + "auth_procedure": 10881 }, "trace": { - "core_rows": 29446, - "chiplets_rows": 13195, - "poseidon2_permutation_rows": 32544, - "range_rows": 2011, + "core_rows": 30598, + "chiplets_rows": 13635, + "poseidon2_permutation_rows": 32896, + "range_rows": 2083, "chiplets_shape": { - "hasher_rows": 9808, - "bitwise_rows": 1776, - "memory_rows": 1609, + "hasher_rows": 10136, + "bitwise_rows": 1840, + "memory_rows": 1657, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume MINT note (fungible faucet, network account)": { - "prologue": 5803, - "notes_processing": 9183, + "prologue": 5853, + "notes_processing": 9406, "note_execution": { - "0xf89434d6652c33467586d94cb98ccae242e368d65778ac0c60212828a1eab15b": 9141 + "0xd0282fd8dff237bba5e5fe178497434fb02dfd64e9cb5e55427c848e998e22bc": 9364 }, "tx_script_processing": 41, "epilogue": { - "total": 19060, - "auth_procedure": 9340 + "total": 19948, + "auth_procedure": 10228 }, "trace": { - "core_rows": 34131, - "chiplets_rows": 13485, - "poseidon2_permutation_rows": 28704, - "range_rows": 2905, + "core_rows": 35292, + "chiplets_rows": 13868, + "poseidon2_permutation_rows": 29040, + "range_rows": 2981, "chiplets_shape": { - "hasher_rows": 10112, - "bitwise_rows": 1256, - "memory_rows": 2115, + "hasher_rows": 10416, + "bitwise_rows": 1288, + "memory_rows": 2162, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume MINT note (non-fungible faucet, network account)": { - "prologue": 5852, - "notes_processing": 12388, + "prologue": 5902, + "notes_processing": 12451, "note_execution": { - "0x5c71596a4bd644327b60fcdcd90d7bb4c7db41ecf64308169244af5f8ba5e11d": 12346 + "0x9d1c38fffad4aaf2042aca75bdd164f3571d6f391e39eb3325a408ed659ebe02": 12409 }, "tx_script_processing": 41, "epilogue": { - "total": 19330, - "auth_procedure": 9349 + "total": 20218, + "auth_procedure": 10237 }, "trace": { - "core_rows": 37655, - "chiplets_rows": 14662, - "poseidon2_permutation_rows": 30544, - "range_rows": 2963, + "core_rows": 38656, + "chiplets_rows": 15018, + "poseidon2_permutation_rows": 30880, + "range_rows": 3157, "chiplets_shape": { - "hasher_rows": 11248, - "bitwise_rows": 1160, - "memory_rows": 2252, + "hasher_rows": 11528, + "bitwise_rows": 1192, + "memory_rows": 2296, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume BURN note (network account)": { - "prologue": 6406, - "notes_processing": 4527, + "prologue": 6456, + "notes_processing": 4613, "note_execution": { - "0x3f1a82301028b2d9c24d939c0644cfba10d0036e9b6374b9e9dca3f9eaeecd6c": 4485 + "0xf23c5dbf34e0f5b7a07bb55740fee27cad486d762243d9db32bf7424e2136807": 4571 }, "tx_script_processing": 41, "epilogue": { - "total": 17799, - "auth_procedure": 9138 + "total": 18485, + "auth_procedure": 9824 }, "trace": { - "core_rows": 28817, - "chiplets_rows": 11785, - "poseidon2_permutation_rows": 27856, - "range_rows": 2621, + "core_rows": 29639, + "chiplets_rows": 12066, + "poseidon2_permutation_rows": 28208, + "range_rows": 2805, "chiplets_shape": { - "hasher_rows": 8920, + "hasher_rows": 9160, "bitwise_rows": 976, - "memory_rows": 1887, + "memory_rows": 1928, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume FAUCET_POLICY_CONFIG note (network account)": { - "prologue": 5738, - "notes_processing": 3592, + "prologue": 5788, + "notes_processing": 3630, "note_execution": { - "0x1f2ce020e34fbcc888d18a7dd727829d1f1795fbb5c90927db0c51cf26bcab0b": 3550 + "0x7c8bce77a06f095ef8528d12d13d39f9b23b4cbfdc232351bda98867421dbd51": 3588 }, "tx_script_processing": 41, "epilogue": { - "total": 17646, - "auth_procedure": 9129 + "total": 18332, + "auth_procedure": 9815 }, "trace": { - "core_rows": 27061, - "chiplets_rows": 10379, - "poseidon2_permutation_rows": 26480, - "range_rows": 2487, + "core_rows": 27835, + "chiplets_rows": 10651, + "poseidon2_permutation_rows": 26816, + "range_rows": 2607, "chiplets_shape": { - "hasher_rows": 7976, + "hasher_rows": 8208, "bitwise_rows": 640, - "memory_rows": 1761, + "memory_rows": 1801, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume FAUCET_METADATA_CONFIG note (network account)": { - "prologue": 5189, - "notes_processing": 6023, + "prologue": 5239, + "notes_processing": 6122, "note_execution": { - "0xc5d07af771df01abcd0fb395b32d26f7ba9205f09d4360ecca7876101017ecf4": 5981 + "0x3d6e7c333189b6747362486e248915a9be8ffe043ea0f3780d67a90b0a0797a0": 6080 }, "tx_script_processing": 41, "epilogue": { - "total": 16235, - "auth_procedure": 9048 + "total": 16921, + "auth_procedure": 9734 }, "trace": { - "core_rows": 27532, - "chiplets_rows": 10472, - "poseidon2_permutation_rows": 25776, - "range_rows": 2395, + "core_rows": 28367, + "chiplets_rows": 10752, + "poseidon2_permutation_rows": 26112, + "range_rows": 2469, "chiplets_shape": { - "hasher_rows": 7992, + "hasher_rows": 8232, "bitwise_rows": 648, - "memory_rows": 1830, + "memory_rows": 1870, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume MIN_BURN_AMOUNT_CONFIG note (network account)": { - "prologue": 5795, - "notes_processing": 2418, + "prologue": 5845, + "notes_processing": 2449, "note_execution": { - "0x7aeb862ad5ae354ca222f42cb76f60de52b8cfc78a8c540bce55b8b6a2dc6e81": 2376 + "0x5dc62fd00814e17f051376fb49493484003bbc0c9e0c9b3396033aa6caf8cacd": 2407 }, "tx_script_processing": 41, "epilogue": { - "total": 17799, - "auth_procedure": 9138 + "total": 18485, + "auth_procedure": 9824 }, "trace": { - "core_rows": 26097, - "chiplets_rows": 9989, - "poseidon2_permutation_rows": 25072, - "range_rows": 2469, + "core_rows": 26864, + "chiplets_rows": 10269, + "poseidon2_permutation_rows": 25408, + "range_rows": 2611, "chiplets_shape": { - "hasher_rows": 7632, + "hasher_rows": 7872, "bitwise_rows": 640, - "memory_rows": 1715, + "memory_rows": 1755, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume ALLOWLIST_CONFIG note (network account)": { - "prologue": 5795, - "notes_processing": 2985, + "prologue": 5845, + "notes_processing": 3016, "note_execution": { - "0xc27f85d59a55783f8405b4aaa258e2bbc0789f9b5d600a9fc6b0b648fbb08854": 2943 + "0x82ae7cc36f7837b5fe5e0328e061bc8358e56be0075f73439ce4305358d3bf43": 2974 }, "tx_script_processing": 41, "epilogue": { - "total": 17978, - "auth_procedure": 9138 + "total": 18664, + "auth_procedure": 9824 }, "trace": { - "core_rows": 26843, - "chiplets_rows": 10615, - "poseidon2_permutation_rows": 27616, - "range_rows": 2477, + "core_rows": 27610, + "chiplets_rows": 10895, + "poseidon2_permutation_rows": 27952, + "range_rows": 2635, "chiplets_shape": { - "hasher_rows": 8184, + "hasher_rows": 8424, "bitwise_rows": 640, - "memory_rows": 1789, + "memory_rows": 1829, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume BLOCKLIST_CONFIG note (network account)": { - "prologue": 5795, - "notes_processing": 2985, + "prologue": 5845, + "notes_processing": 3016, "note_execution": { - "0x57e359b416c6054eaaee8ec6d101dfa7ccd1e8897014e6004cd8b87e1b82ba77": 2943 + "0x05c704c0fc0efca937aca121f76b3bd810c202d979e47f982fb128e6b2a6a58b": 2974 }, "tx_script_processing": 41, "epilogue": { - "total": 17978, - "auth_procedure": 9138 + "total": 18664, + "auth_procedure": 9824 }, "trace": { - "core_rows": 26843, - "chiplets_rows": 10615, - "poseidon2_permutation_rows": 27440, - "range_rows": 2493, + "core_rows": 27610, + "chiplets_rows": 10895, + "poseidon2_permutation_rows": 27776, + "range_rows": 2609, "chiplets_shape": { - "hasher_rows": 8184, + "hasher_rows": 8424, "bitwise_rows": 640, - "memory_rows": 1789, + "memory_rows": 1829, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume PAUSE_CONFIG note (network account)": { - "prologue": 3665, - "notes_processing": 2433, + "prologue": 3715, + "notes_processing": 2464, "note_execution": { - "0xebe32aa38938bfc16f277b8542e58ed4b6b7a4ea9be764972291b965299f59a6": 2391 + "0xa10de965c325fde1c84890ddca21fe4853c6f8e1320374923619708ac4ddc273": 2422 }, "tx_script_processing": 41, "epilogue": { - "total": 12392, - "auth_procedure": 8823 + "total": 13078, + "auth_procedure": 9509 }, "trace": { - "core_rows": 18575, - "chiplets_rows": 7665, - "poseidon2_permutation_rows": 23872, - "range_rows": 1581, + "core_rows": 19342, + "chiplets_rows": 7937, + "poseidon2_permutation_rows": 24208, + "range_rows": 1647, "chiplets_shape": { - "hasher_rows": 5872, + "hasher_rows": 6104, "bitwise_rows": 640, - "memory_rows": 1151, + "memory_rows": 1191, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume OWNER_CONFIG note (network account)": { - "prologue": 3524, - "notes_processing": 2461, + "prologue": 3574, + "notes_processing": 2492, "note_execution": { - "0x413694d4ebf2f4f4ad41eb749201f02c2d3e4c08da7f36eb957ccab987124fb7": 2419 + "0x770d225819e7af018842c17d8b038bd5517169f763b3220360292c528b12039f": 2450 }, "tx_script_processing": 41, "epilogue": { - "total": 12086, - "auth_procedure": 8805 + "total": 12772, + "auth_procedure": 9491 }, "trace": { - "core_rows": 18156, - "chiplets_rows": 7545, - "poseidon2_permutation_rows": 23728, - "range_rows": 1517, + "core_rows": 18923, + "chiplets_rows": 7825, + "poseidon2_permutation_rows": 24064, + "range_rows": 1607, "chiplets_shape": { - "hasher_rows": 5768, + "hasher_rows": 6008, "bitwise_rows": 656, - "memory_rows": 1119, + "memory_rows": 1159, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume RBAC_CONFIG note (network account)": { - "prologue": 3703, - "notes_processing": 5257, + "prologue": 3753, + "notes_processing": 5460, "note_execution": { - "0xe8b49ff8fb19d28da5c7e5266fe1a243b31cbb6e350636449d856e180797e81d": 5215 + "0xf7bee3cd990c2daa18fbb81ee73fc4a117f44bd77247c6de5e8d213268113524": 5418 }, "tx_script_processing": 41, "epilogue": { - "total": 12934, - "auth_procedure": 8832 + "total": 13620, + "auth_procedure": 9518 }, "trace": { - "core_rows": 21979, - "chiplets_rows": 10003, - "poseidon2_permutation_rows": 30160, - "range_rows": 1719, + "core_rows": 22918, + "chiplets_rows": 10307, + "poseidon2_permutation_rows": 30656, + "range_rows": 1923, "chiplets_shape": { - "hasher_rows": 7912, + "hasher_rows": 8176, "bitwise_rows": 656, - "memory_rows": 1433, + "memory_rows": 1473, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume NETWORK_ACCOUNT_CONFIG note (network account)": { - "prologue": 3608, - "notes_processing": 2958, + "prologue": 3658, + "notes_processing": 2989, "note_execution": { - "0x89ace2ea72ffd23f3252bf317ed8155bf1e79fc769d166f538487c5b74d646c6": 2916 + "0x0a46944038fb10b361d0b14f62f4f153f1df530eab3fcde2e01f7002c79a39bc": 2947 }, "tx_script_processing": 41, "epilogue": { - "total": 12405, - "auth_procedure": 8814 + "total": 13091, + "auth_procedure": 9500 }, "trace": { - "core_rows": 19056, - "chiplets_rows": 8201, - "poseidon2_permutation_rows": 26416, - "range_rows": 1543, + "core_rows": 19823, + "chiplets_rows": 8481, + "poseidon2_permutation_rows": 26768, + "range_rows": 1715, "chiplets_shape": { - "hasher_rows": 6360, + "hasher_rows": 6600, "bitwise_rows": 640, - "memory_rows": 1199, + "memory_rows": 1239, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume CONSTANT_FEE_POLICY_CONFIG note (network account)": { - "prologue": 3664, - "notes_processing": 3643, + "prologue": 3714, + "notes_processing": 3688, "note_execution": { - "0x6420abd200a8e86e40db716e1a98aaf6c389047ae16b799030bd40a778b47b01": 3601 + "0x2127d871673cd4e51c92f209e6182e2254ad3bdcb24f0a43a4bf21ad56ac01d2": 3646 }, "tx_script_processing": 41, "epilogue": { - "total": 12558, - "auth_procedure": 8823 + "total": 13244, + "auth_procedure": 9509 }, "trace": { - "core_rows": 19950, - "chiplets_rows": 8495, - "poseidon2_permutation_rows": 26528, - "range_rows": 1591, + "core_rows": 20731, + "chiplets_rows": 8767, + "poseidon2_permutation_rows": 26864, + "range_rows": 1735, "chiplets_shape": { - "hasher_rows": 6576, + "hasher_rows": 6808, "bitwise_rows": 640, - "memory_rows": 1277, + "memory_rows": 1317, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume FEE_SPONSORSHIP note with feature note (network account)": { - "prologue": 5282, + "prologue": 5367, "notes_processing": 1155, "note_execution": { - "0xaa511953232bfdd8bde965cf9b0cca4b57e4a9d9a83ba375ea8002caec40eff1": 456, - "0xcfad35a98d385c2cd4aa71e74125102cc4a475029d7d1c49f3a10bf430d0b37b": 648 + "0xa7995001442412a833ad5d36457a67963ddb41d71b07e2f44f5f2cdfef20bc84": 456, + "0xf7decae9953c23fcd430885e7de27b22cb8e406260d7683f190ed5a795a14b08": 648 }, "tx_script_processing": 41, "epilogue": { - "total": 14994, - "auth_procedure": 11875 + "total": 15687, + "auth_procedure": 12568 }, "trace": { - "core_rows": 21516, - "chiplets_rows": 9619, - "poseidon2_permutation_rows": 26320, - "range_rows": 1575, + "core_rows": 22294, + "chiplets_rows": 9907, + "poseidon2_permutation_rows": 26656, + "range_rows": 1677, "chiplets_shape": { - "hasher_rows": 7400, - "bitwise_rows": 992, - "memory_rows": 1225, + "hasher_rows": 7632, + "bitwise_rows": 1008, + "memory_rows": 1265, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume FEE_SPONSORSHIP note (reclaim)": { - "prologue": 3965, + "prologue": 4015, "notes_processing": 2865, "note_execution": { - "0xe32572b933d5de6c718bbc3c56e2b33574be853601d0577b2deb20a49be30539": 2823 + "0x12cb8a0d175a0a98c0b6ebc58ebb64cf529b0677be85bd7dd001601bb30b2b6b": 2823 }, "tx_script_processing": 41, "epilogue": { - "total": 10411, - "auth_procedure": 8149 + "total": 10358, + "auth_procedure": 8096 }, "trace": { - "core_rows": 17326, - "chiplets_rows": 7958, - "poseidon2_permutation_rows": 24016, - "range_rows": 1579, + "core_rows": 17323, + "chiplets_rows": 7855, + "poseidon2_permutation_rows": 23696, + "range_rows": 1597, "chiplets_shape": { - "hasher_rows": 5856, - "bitwise_rows": 1224, - "memory_rows": 876, + "hasher_rows": 5816, + "bitwise_rows": 1160, + "memory_rows": 877, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume CLAIM note (L1 to Miden, with fee payment)": { - "prologue": 5010, - "notes_processing": 28303, + "prologue": 5060, + "notes_processing": 28215, "note_execution": { - "0xb92e6fd5ec3cb3d21fd8c207f67b385e1b06393b0476aff1dd17ad9bad1b8fbc": 28261 + "0x49cdb9cd15380c349a8fc22400ed05d402962d4eda56e9e3a2671030b8d60125": 28173 }, "tx_script_processing": 41, "epilogue": { - "total": 20708, - "auth_procedure": 14034 + "total": 25391, + "auth_procedure": 18717 }, "trace": { - "core_rows": 54106, - "chiplets_rows": 22433, - "poseidon2_permutation_rows": 46672, - "range_rows": 3621, + "core_rows": 58751, + "chiplets_rows": 23984, + "poseidon2_permutation_rows": 47104, + "range_rows": 3801, "chiplets_shape": { - "hasher_rows": 14888, - "bitwise_rows": 3168, - "memory_rows": 4375, + "hasher_rows": 16064, + "bitwise_rows": 3256, + "memory_rows": 4662, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume CLAIM note (L2 to Miden, with fee payment)": { - "prologue": 5010, - "notes_processing": 38465, + "prologue": 5060, + "notes_processing": 38185, "note_execution": { - "0x5ab9bf3e5894314d51d1224cbe74b44dde6ac77220c3686ac17faef2a25169ba": 38423 + "0x8f113a459b0a87ac90f3c184d1ebfd81cb09076fadb64bff39609eaa14209ce9": 38143 }, "tx_script_processing": 41, "epilogue": { - "total": 20708, - "auth_procedure": 14034 + "total": 25391, + "auth_procedure": 18717 }, "trace": { - "core_rows": 64268, - "chiplets_rows": 25183, - "poseidon2_permutation_rows": 48816, - "range_rows": 3861, + "core_rows": 68721, + "chiplets_rows": 26542, + "poseidon2_permutation_rows": 49248, + "range_rows": 3995, "chiplets_shape": { - "hasher_rows": 16440, - "bitwise_rows": 3424, - "memory_rows": 5317, + "hasher_rows": 17424, + "bitwise_rows": 3512, + "memory_rows": 5604, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume B2AGG note (bridge-out, with fee payment)": { - "prologue": 5317, - "notes_processing": 119280, + "prologue": 5367, + "notes_processing": 120008, "note_execution": { - "0x8045f362a182de06bb18d6a3d86fa86e2c379c5eb9cf379cf943f4d54d0eedae": 119238 + "0x71b6eb992761503187babb3bf70dcab321dc00465ecb077ab1a1b2289eedf4f6": 119966 }, "tx_script_processing": 41, "epilogue": { - "total": 29251, - "auth_procedure": 12873 + "total": 33847, + "auth_procedure": 17469 }, "trace": { - "core_rows": 153933, - "chiplets_rows": 73417, - "poseidon2_permutation_rows": 118112, - "range_rows": 5047, + "core_rows": 159307, + "chiplets_rows": 75021, + "poseidon2_permutation_rows": 118528, + "range_rows": 6213, "chiplets_shape": { - "hasher_rows": 58880, - "bitwise_rows": 3984, - "memory_rows": 10551, + "hasher_rows": 60104, + "bitwise_rows": 4072, + "memory_rows": 10843, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume B2AGG note (bridge-out, 2^31-1 leaves, with fee payment)": { - "prologue": 5317, - "notes_processing": 63420, + "prologue": 5367, + "notes_processing": 64148, "note_execution": { - "0x8045f362a182de06bb18d6a3d86fa86e2c379c5eb9cf379cf943f4d54d0eedae": 63378 + "0x71b6eb992761503187babb3bf70dcab321dc00465ecb077ab1a1b2289eedf4f6": 64106 }, "tx_script_processing": 41, "epilogue": { - "total": 20323, - "auth_procedure": 12873 + "total": 24919, + "auth_procedure": 17469 }, "trace": { - "core_rows": 89145, - "chiplets_rows": 42580, - "poseidon2_permutation_rows": 55920, - "range_rows": 3969, + "core_rows": 94519, + "chiplets_rows": 44184, + "poseidon2_permutation_rows": 56336, + "range_rows": 5101, "chiplets_shape": { - "hasher_rows": 31856, - "bitwise_rows": 3984, - "memory_rows": 6738, + "hasher_rows": 33080, + "bitwise_rows": 4072, + "memory_rows": 7030, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume CONFIG_AGG_BRIDGE note (with fee payment)": { - "prologue": 4697, - "notes_processing": 13318, + "prologue": 4747, + "notes_processing": 13766, "note_execution": { - "0x4a95146177b6458432d5a1f60f8ade60686c30743fcc89271463bc1bf577ff97": 13276 + "0xd440b0cee85c6aef5c01291aa059ed14e6415fea85819c9ec7ee46f87acfbee8": 13724 }, "tx_script_processing": 41, "epilogue": { - "total": 16076, - "auth_procedure": 8976 + "total": 16762, + "auth_procedure": 9662 }, "trace": { - "core_rows": 34176, - "chiplets_rows": 15307, - "poseidon2_permutation_rows": 37536, - "range_rows": 2493, + "core_rows": 35360, + "chiplets_rows": 15643, + "poseidon2_permutation_rows": 38096, + "range_rows": 2725, "chiplets_shape": { - "hasher_rows": 12240, + "hasher_rows": 12536, "bitwise_rows": 696, - "memory_rows": 2369, + "memory_rows": 2409, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume DEREGISTER_AGG_FAUCET note (with fee payment)": { - "prologue": 4738, - "notes_processing": 12628, + "prologue": 4788, + "notes_processing": 12778, "note_execution": { - "0xe4cef1d192ed7353b3cdb747f5b6beec5419c36c49d96a6d954e3c694ecc9653": 12586 + "0x6b725938c4ac230b95043428503b438d09d1190117b878d60e98f7ba8313149d": 12736 }, "tx_script_processing": 41, "epilogue": { - "total": 16076, - "auth_procedure": 8976 + "total": 16762, + "auth_procedure": 9662 }, "trace": { - "core_rows": 33527, - "chiplets_rows": 14945, - "poseidon2_permutation_rows": 36320, - "range_rows": 2343, + "core_rows": 34413, + "chiplets_rows": 15298, + "poseidon2_permutation_rows": 36688, + "range_rows": 2611, "chiplets_shape": { - "hasher_rows": 12016, + "hasher_rows": 12352, "bitwise_rows": 688, - "memory_rows": 2239, + "memory_rows": 2256, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume UPDATE_GER note (with fee payment)": { - "prologue": 4679, - "notes_processing": 4347, + "prologue": 4729, + "notes_processing": 4546, "note_execution": { - "0x0f17499940516b9743f89ef52d6cb1d3bb002d1c196e033af53a792302987c9a": 4305 + "0x7d8f590e31efdb657004624314cd273c13ef1aec51e0c5f233e3fc2a8369ca99": 4504 }, "tx_script_processing": 41, "epilogue": { - "total": 15276, - "auth_procedure": 8976 + "total": 15962, + "auth_procedure": 9662 }, "trace": { - "core_rows": 24387, - "chiplets_rows": 10170, - "poseidon2_permutation_rows": 29760, - "range_rows": 2101, + "core_rows": 25322, + "chiplets_rows": 10474, + "poseidon2_permutation_rows": 30240, + "range_rows": 2297, "chiplets_shape": { - "hasher_rows": 7880, + "hasher_rows": 8144, "bitwise_rows": 640, - "memory_rows": 1648, + "memory_rows": 1688, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume REMOVE_GER note (with fee payment)": { - "prologue": 4738, - "notes_processing": 5371, + "prologue": 4788, + "notes_processing": 5578, "note_execution": { - "0x7305f2b4fc451fa2cdb9c1799357f8b43637ea09330da85535339a02fc427395": 5329 + "0xae6a81d6163dd72e5a0d8c400b28e29a7aaedc506ccdf8e5967473b75f0dffc6": 5536 }, "tx_script_processing": 41, "epilogue": { - "total": 15312, - "auth_procedure": 8976 + "total": 15998, + "auth_procedure": 9662 }, "trace": { - "core_rows": 25506, - "chiplets_rows": 10486, - "poseidon2_permutation_rows": 30256, - "range_rows": 2141, + "core_rows": 26449, + "chiplets_rows": 10782, + "poseidon2_permutation_rows": 30704, + "range_rows": 2293, "chiplets_shape": { - "hasher_rows": 8112, + "hasher_rows": 8368, "bitwise_rows": 640, - "memory_rows": 1732, + "memory_rows": 1772, "kernel_rom_rows": 1, "ace_rows": 0 } diff --git a/crates/miden-agglayer/src/costs/table.rs b/crates/miden-agglayer/src/costs/table.rs index 4bba940ff3..e01686e6b9 100644 --- a/crates/miden-agglayer/src/costs/table.rs +++ b/crates/miden-agglayer/src/costs/table.rs @@ -2,20 +2,20 @@ // Values are maxima across the benchmarked paths; see `miden_standards::note::costs` for the // caveats on what they do and do not cover. -/// Cycles of consuming a CLAIM note: L1 origin 54062, L2 origin 64224 (maximum). -pub const CLAIM_CONSUMPTION_CYCLES: u32 = 64224; +/// Cycles of consuming a CLAIM note: L1 origin 58707, L2 origin 68677 (maximum). +pub const CLAIM_CONSUMPTION_CYCLES: u32 = 68677; -/// Cycles of consuming a B2AGG note: empty frontier 153889 (maximum), 2^31-1 leaves 89101. -pub const B2AGG_CONSUMPTION_CYCLES: u32 = 153889; +/// Cycles of consuming a B2AGG note: empty frontier 159263 (maximum), 2^31-1 leaves 94475. +pub const B2AGG_CONSUMPTION_CYCLES: u32 = 159263; /// Cycles of consuming a CONFIG_AGG_BRIDGE note (single benchmarked path). -pub const CONFIG_AGG_BRIDGE_CONSUMPTION_CYCLES: u32 = 34132; +pub const CONFIG_AGG_BRIDGE_CONSUMPTION_CYCLES: u32 = 35316; /// Cycles of consuming a DEREGISTER_AGG_FAUCET note (single benchmarked path). -pub const DEREGISTER_AGG_FAUCET_CONSUMPTION_CYCLES: u32 = 33483; +pub const DEREGISTER_AGG_FAUCET_CONSUMPTION_CYCLES: u32 = 34369; /// Cycles of consuming an UPDATE_GER note (single benchmarked path). -pub const UPDATE_GER_CONSUMPTION_CYCLES: u32 = 24343; +pub const UPDATE_GER_CONSUMPTION_CYCLES: u32 = 25278; /// Cycles of consuming a REMOVE_GER note (single benchmarked path). -pub const REMOVE_GER_CONSUMPTION_CYCLES: u32 = 25462; +pub const REMOVE_GER_CONSUMPTION_CYCLES: u32 = 26405; diff --git a/crates/miden-standards/asm/standards/fee/mod.masm b/crates/miden-standards/asm/standards/fee/mod.masm index 83c76340da..fa5c354004 100644 --- a/crates/miden-standards/asm/standards/fee/mod.masm +++ b/crates/miden-standards/asm/standards/fee/mod.masm @@ -7,7 +7,7 @@ use miden::protocol::asset use miden::protocol::native_account use miden::protocol::output_note use miden::protocol::tx -use {Asset} from miden::protocol::types +use {Asset, AssetId} from miden::protocol::types use miden::standards::assets::fungible_asset use miden::standards::fees use miden::standards::notes::tx_fee @@ -27,19 +27,31 @@ pub type ConversionInfo = word # CONSTANTS # ================================================================================================= -# Estimated upper bound on the number of cycles a pay_fee flow spends after the kernel's +# Estimated upper bound on the number of cycles the fee note payment spends after the kernel's # compute_fee call returns: rate conversion, fee-asset construction, serial-number derivation, # recipient computation, note creation and funding. Guarded by the fee-payment regression tests. const PAY_FEE_CYCLES = 8192 +# Estimated upper bounds on the number of cycles the sponsorship payment spends after the +# compute_fee call returns: a walk over every output note looking for network notes, then pricing +# and funding a FEE_SPONSORSHIP note for each one found, which covers the foreign procedure call +# into the target's fee policy, the note creation and the vault withdrawal. +# +# NOT covered by a regression test: zeroing both leaves the suite green, because the sponsoring +# case lands exactly on an ilog2 boundary either way. They are margins over a measurement, so a +# test would have to assert cycle counts rather than behaviour. Re-measure if the payment changes. +const SPONSORSHIP_WALK_PER_OUTPUT_NOTE_CYCLES = 512 +const SPONSORSHIP_NOTE_CYCLES = 16384 + # Estimated upper bounds on the number of kernel epilogue cycles spent after the auth procedure # returns: output vault build, output notes commitment, account patch and final account # commitments. The cost scales with the number of output notes (measured ~330 cycles per # asset-less note; ~1k-2k base for note-less transactions), so the estimate is computed as -# BASE + PER_NOTE * (num_output_notes + 1), counting the fee note itself. Note that the per-note -# figure is measured on asset-less notes; notes carrying many assets cost more, so asset-heavy -# transactions may slightly under-estimate the fee (which fails closed: the batch builder may -# reject the underpaying note). Guarded by the fee-payment regression tests. +# BASE + PER_NOTE * (num_output_notes + 1 + num_sponsorship_notes), counting the fee note and the +# sponsorship notes the payment has yet to create. Note that the per-note figure is measured on +# asset-less notes; notes carrying many assets cost more, so asset-heavy transactions may slightly +# under-estimate the fee (which fails closed: the batch builder may reject the underpaying note). +# Guarded by the fee-payment regression tests. const POST_AUTH_EPILOGUE_BASE_CYCLES = 4096 const POST_AUTH_EPILOGUE_PER_NOTE_CYCLES = 512 @@ -233,28 +245,44 @@ pub proc convert_amount(amount: felt, rate: ConversionRate) -> felt # => [converted_amount] end -#! Adds the internal cycle margins of a pay_fee flow to the caller's estimate of remaining +#! Adds the internal cycle margins of a fee flow to the caller's estimate of remaining #! authentication cycles. #! -#! The margins cover the pay_fee tail (everything after the compute_fee call) and the post-auth -#! kernel epilogue, whose cost scales with the number of output notes. All output notes except -#! the fee note itself exist by the time a pay_fee flow runs, so the estimate is scaled by their -#! count plus one. +#! The margins cover the payment — everything the fee flow does after the compute_fee call, +#! sponsorship notes included — and the post-auth kernel epilogue, whose cost scales with the +#! number of output notes. The fee note and the sponsorship notes are created after the fee is +#! computed, so the epilogue estimate is scaled by the count of the notes that already exist plus +#! those. #! -#! Inputs: [num_extra_cycles] +#! Inputs: [num_extra_cycles, num_sponsorship_notes, num_output_notes] #! Outputs: [num_estimated_extra_cycles] #! #! Where: #! - num_extra_cycles is the estimated number of cycles the authentication procedure spends after -#! the pay_fee flow returns. +#! the fee flow returns. +#! - num_sponsorship_notes is the number of FEE_SPONSORSHIP notes the payment will create. +#! - num_output_notes is the number of output notes that exist before the payment creates any. #! - num_estimated_extra_cycles is the total estimate to pass to compute_fee. #! #! Invocation: exec -pub proc apply_cycle_margins(num_extra_cycles: felt) -> felt +pub proc apply_cycle_margins( + num_extra_cycles: felt, + num_sponsorship_notes: felt, + num_output_notes: felt, +) -> felt add.PAY_FEE_CYCLES add.POST_AUTH_EPILOGUE_BASE_CYCLES - # => [num_partial_extra_cycles] + # => [num_estimated_extra_cycles, num_sponsorship_notes, num_output_notes] + + # the sponsorship payment walks every output note that exists + dup.2 mul.SPONSORSHIP_WALK_PER_OUTPUT_NOTE_CYCLES add + # => [num_estimated_extra_cycles, num_sponsorship_notes, num_output_notes] + + # and creates one sponsorship note per network note it finds + dup.1 mul.SPONSORSHIP_NOTE_CYCLES add + # => [num_estimated_extra_cycles, num_sponsorship_notes, num_output_notes] - exec.tx::get_num_output_notes add.1 mul.POST_AUTH_EPILOGUE_PER_NOTE_CYCLES add + # the epilogue processes the notes that exist, the sponsorship notes and the fee note + movup.2 movup.2 add add.1 mul.POST_AUTH_EPILOGUE_PER_NOTE_CYCLES add # => [num_estimated_extra_cycles] end @@ -316,6 +344,37 @@ pub proc create_and_fund_fee_note(asset: Asset) # => [] end +#! Estimates the fee of the current transaction. +#! +#! The fee covers the notes the payment goes on to create: the TX_FEE note, and one +#! FEE_SPONSORSHIP note per network output note its target prices above zero (see +#! fees::create_network_note_sponsorships). +#! +#! num_extra_cycles only needs to cover the number of cycles the caller spends after the fee flow +#! returns (e.g. transaction summary creation, hashing and signature verification, see +#! signature::estimate_authentication_cycles); this procedure adds the payment cycles and the +#! post-authentication kernel epilogue cycles internally. +#! +#! Inputs: [num_extra_cycles] +#! Outputs: [fee_amount] +#! +#! Where: +#! - num_extra_cycles is the estimated number of cycles the caller spends after this call until +#! the end of the authentication procedure. +#! - fee_amount is the computed fee of the transaction, denominated in the native fee asset. +#! +#! Panics if: +#! - the sponsorship estimate fails (propagates the panics of +#! fees::estimate_network_note_sponsorships, e.g. a target's fee policy is unset or unreachable). +#! - num_extra_cycles (plus internal margins) is not a u32 or the total cycle count exceeds the +#! maximum cycle count. +#! +#! Invocation: exec +pub proc estimate_fee(num_extra_cycles: felt) -> felt + exec.tx::get_fee_asset_id exec.estimate_fee_for_asset + # => [fee_amount] +end + #! Computes the transaction fee and pays it by creating and funding a public TX_FEE note. #! #! This is the fee payment flow for all account types: the caller supplies the decoded @@ -327,23 +386,14 @@ end #! and obtain the conversion info via native_conversion_info, which needs no commitment since #! it is read from the reference block. #! -#! The fee amount is computed via the compute_fee kernel procedure. num_extra_cycles only needs -#! to cover the number of cycles the authentication procedure spends after this procedure -#! returns (e.g. transaction summary creation, hashing and signature verification, see -#! signature::estimate_authentication_cycles); this procedure adds its own tail cycles and the -#! post-authentication kernel epilogue cycles internally. -#! -#! The paid amount is ceil(fee_amount * rate_num / rate_den) of the asset issued by the faucet -#! in the conversion info. +#! The fee is computed by estimate_fee. The payment then settles both of the transaction's fee +#! obligations, in order: the sponsorship notes of its network output notes, then the TX_FEE note +#! carrying ceil(fee_amount * rate_num / rate_den) of the asset issued by the faucet in the +#! conversion info. #! #! If the computed fee is zero (e.g. the reference block's verification base fee is zero), no -#! fee note is created and no conversion info is required (the empty word is accepted). Note that -#! the sponsorship step above may still have created notes and withdrawn from the vault. -#! -#! Before computing the fee, this creates a FEE_SPONSORSHIP note for every network output note -#! of the transaction (see fees::create_network_note_sponsorships), so those notes are included -#! in the cycle estimate and the fee. This prices each network note through its target account's -#! fee policy via an FPI call, which requires that target to be provisioned as a foreign account. +#! fee note is created and no conversion info is required (the empty word is accepted). The +#! sponsorship notes are created regardless. #! #! Inputs: [num_extra_cycles, CONVERSION_INFO] #! Outputs: [total_sponsored_fee_amount] @@ -353,15 +403,10 @@ end #! the end of the authentication procedure. #! - CONVERSION_INFO is [faucet_id_suffix, faucet_id_prefix, rate_num, rate_den], or the empty #! word if no conversion info was committed (only accepted when the computed fee is zero). -#! - total_sponsored_fee_amount is the total amount the sponsorship step moved into sponsorship -#! notes. +#! - total_sponsored_fee_amount is the total amount moved into the sponsorship notes. #! #! Panics if: -#! - the sponsorship step fails (propagates the panics of -#! fees::create_network_note_sponsorships, e.g. a target's fee policy is unset/unreachable or -#! the vault underfunds a sponsored amount). -#! - num_extra_cycles (plus internal margins) is not a u32 or the total cycle count exceeds the -#! maximum cycle count. +#! - estimate_fee or fees::create_network_note_sponsorships panics. #! - the computed fee is non-zero and CONVERSION_INFO is the empty word. #! - the conversion rate is malformed or the converted amount overflows. #! - the account vault holds less of the payment asset than the amount to be paid. @@ -369,26 +414,20 @@ end #! #! Invocation: exec pub proc pay_fee(num_extra_cycles: felt, conversion_info: ConversionInfo) -> felt - # sponsor any network output notes before the fee is computed, so the sponsorship notes - # this creates are counted by both the cycle estimate and compute_fee. + # the native fee asset funds the sponsorship notes, and is read once for both passes exec.tx::get_fee_asset_id # => [FEE_ASSET_ID, num_extra_cycles, CONVERSION_INFO] - exec.fees::create_network_note_sponsorships - # => [total_sponsored_fee_amount, num_extra_cycles, CONVERSION_INFO] + dupw movup.8 movdn.4 + # => [FEE_ASSET_ID, num_extra_cycles, FEE_ASSET_ID, CONVERSION_INFO] - movdn.5 - # => [num_extra_cycles, CONVERSION_INFO, total_sponsored_fee_amount] + exec.estimate_fee_for_asset + # => [fee_amount, FEE_ASSET_ID, CONVERSION_INFO] - exec.apply_cycle_margins - # => [num_estimated_extra_cycles, CONVERSION_INFO, total_sponsored_fee_amount] + movdn.4 exec.fees::create_network_note_sponsorships + # => [total_sponsored_fee_amount, fee_amount, CONVERSION_INFO] - # no output notes are excluded from the fee computation - padw movup.4 - # => [num_estimated_extra_cycles, EXCLUDE_NOTES_COMMITMENT, CONVERSION_INFO, - # total_sponsored_fee_amount] - - exec.tx::compute_fee + movdn.5 # => [fee_amount, CONVERSION_INFO, total_sponsored_fee_amount] dup eq.0 @@ -427,3 +466,41 @@ pub proc pay_fee(num_extra_cycles: felt, conversion_info: ConversionInfo) -> fel # => [total_sponsored_fee_amount] end end + +# HELPER PROCEDURES +# ================================================================================================= + +#! Estimates the fee of the current transaction with the native fee asset ID already at hand. +#! +#! Backs `estimate_fee`, and lets `pay_fee` read the fee asset ID once for both the estimate and +#! the sponsorship payment. +#! +#! Inputs: [FEE_ASSET_ID, num_extra_cycles] +#! Outputs: [fee_amount] +#! +#! Where: +#! - FEE_ASSET_ID is the ID of the native fee asset, see `tx::get_fee_asset_id`. +#! - num_extra_cycles and fee_amount are as described in `estimate_fee`. +#! +#! Panics if: +#! - `estimate_fee` panics. +#! +#! Invocation: exec +proc estimate_fee_for_asset(fee_asset_id: AssetId, num_extra_cycles: felt) -> felt + # only the note counts are needed: the fee prices the notes, not the amounts they carry + exec.fees::estimate_network_note_sponsorships + # => [num_sponsorship_notes, num_output_notes, num_extra_cycles] + + movup.2 + # => [num_extra_cycles, num_sponsorship_notes, num_output_notes] + + exec.apply_cycle_margins + # => [num_estimated_extra_cycles] + + # no output notes are excluded from the fee computation + padw movup.4 + # => [num_estimated_extra_cycles, EXCLUDE_NOTES_COMMITMENT] + + exec.tx::compute_fee + # => [fee_amount] +end diff --git a/crates/miden-standards/asm/standards/fees/mod.masm b/crates/miden-standards/asm/standards/fees/mod.masm index df18852b3f..183cca6ac2 100644 --- a/crates/miden-standards/asm/standards/fees/mod.masm +++ b/crates/miden-standards/asm/standards/fees/mod.masm @@ -8,6 +8,9 @@ # # `create_network_note_sponsorships` walks the transaction's output notes and, for every note targeted at a # network account, creates a paired FEE_SPONSORSHIP note funded with the fee that account charges. +# +# `estimate_network_note_sponsorships` walks the same notes and prices them the same way, but creates +# nothing, so a caller can charge for those notes before it pays for them. use miden::core::word use miden::core::crypto::hashes::poseidon2 @@ -62,7 +65,7 @@ const WORD_ALIGNMENT_MASK = 0xFFFFFFFC const SPONSORSHIP_STORAGE_LOC = 0 const SPONSORSHIP_FEATURE_NOTE_ID_LOC = SPONSORSHIP_STORAGE_LOC + FEATURE_NOTE_ID_ITEM_OFFSET -# Local memory offset at which `create_network_note_sponsorship` caches the expected fee asset ID. +# Local memory offset at which `process_network_note_sponsorship` caches the expected fee asset ID. const EXPECTED_FEE_ASSET_ID_LOC = 0 # Local memory offsets used by `compute_sponsorship_fee` (in its own local frame). @@ -284,60 +287,40 @@ end #! #! Invocation: exec pub proc create_network_note_sponsorships(fee_asset_id: AssetId) -> felt - push.0 movdn.4 - # => [FEE_ASSET_ID, total_sponsored_fee_amount = 0] - - # get the output note count before any sponsorship notes were created, since creating them - # increments the count - exec.tx::get_num_output_notes - # => [num_output_notes, FEE_ASSET_ID, total_sponsored_fee_amount] - - push.0 - # => [note_idx = 0, num_output_notes, FEE_ASSET_ID, total_sponsored_fee_amount] - - dup.1 dup.1 neq - # => [should_loop, note_idx, num_output_notes, FEE_ASSET_ID, total_sponsored_fee_amount] - - while.true - # only network output notes are sponsored - dup exec.is_network_note - # => [is_network_note, attachment_idx, note_idx, num_output_notes, FEE_ASSET_ID, - # total_sponsored_fee_amount] - - if.true - dup.1 - # => [note_idx, attachment_idx, note_idx, num_output_notes, FEE_ASSET_ID, - # total_sponsored_fee_amount] - - # pass a copy of the expected fee asset ID along with the note parameters - dupw.1 movup.5 movup.5 - # => [note_idx, attachment_idx, FEE_ASSET_ID, note_idx, num_output_notes, FEE_ASSET_ID, - # total_sponsored_fee_amount] - - exec.create_network_note_sponsorship - # => [sponsored_fee_amount, note_idx, num_output_notes, FEE_ASSET_ID, - # total_sponsored_fee_amount] - - movup.7 add movdn.6 - # => [note_idx, num_output_notes, FEE_ASSET_ID, total_sponsored_fee_amount] - else - # do not sponsor non-network output notes - drop - # => [note_idx, num_output_notes, FEE_ASSET_ID, total_sponsored_fee_amount] - end - - add.1 - # => [note_idx+1, num_output_notes, FEE_ASSET_ID, total_sponsored_fee_amount] - - dup.1 dup.1 neq - # => [should_loop, note_idx, num_output_notes, FEE_ASSET_ID, total_sponsored_fee_amount] - end - # => [note_idx, num_output_notes, FEE_ASSET_ID, total_sponsored_fee_amount] - - drop drop dropw + push.1 movdn.4 exec.process_network_note_sponsorships movdn.2 drop drop # => [total_sponsored_fee_amount] end +#! Estimates the FEE_SPONSORSHIP notes that this transaction's network output notes require. +#! +#! Prices the network output notes exactly as `create_network_note_sponsorships` does, but creates +#! no note and moves nothing out of the vault, so a caller can charge for those notes before it +#! pays for them. A note the target prices to zero needs no sponsorship note and so is not +#! counted. +#! +#! Inputs: [FEE_ASSET_ID] +#! Outputs: [num_sponsorship_notes, num_output_notes] +#! +#! Where: +#! - FEE_ASSET_ID is the ID of the asset the sponsorship notes are funded with, typically read +#! from the fee manager's fee asset ID slot. +#! - num_sponsorship_notes is the number of notes `create_network_note_sponsorships` will create. +#! - num_output_notes is the number of output notes the transaction has at this point, i.e. the +#! notes `create_network_note_sponsorships` will walk. +#! +#! Panics if: +#! - a network output note's target ID attachment does not consist of exactly one word. +#! - the target account's fee policy requires a network output note's recipient preimage and it +#! is unavailable or does not hash to the recipient. +#! - the target account's active fee policy is not set. +#! - a target account charges a non-zero fee in an asset other than the expected fee asset. +#! +#! Invocation: exec +pub proc estimate_network_note_sponsorships(fee_asset_id: AssetId) -> (felt, felt) + push.0 movdn.4 exec.process_network_note_sponsorships drop + # => [num_sponsorship_notes, num_output_notes] +end + # HELPER PROCEDURES # ================================================================================================= @@ -602,20 +585,118 @@ proc is_network_note(note_idx: u16) -> (Bool, u8) # => [is_found, attachment_idx] end -#! Creates a FEE_SPONSORSHIP note for the network output note at the given index. +#! Walks the transaction's output notes and prices every network note through its target account's +#! fee policy, creating its FEE_SPONSORSHIP note along the way when asked to. #! -#! See [`create_network_note_sponsorships`] for the full behaviour; this procedure handles a single network -#! note. A network note the target account prices to zero produces no sponsorship note. +#! Backs `create_network_note_sponsorships` (create_notes = 1) and +#! `estimate_network_note_sponsorships` (create_notes = 0); see those for the full behaviour. +#! +#! Inputs: [FEE_ASSET_ID, create_notes] +#! Outputs: [total_sponsored_fee_amount, num_sponsorship_notes, num_output_notes] +#! +#! Where: +#! - FEE_ASSET_ID is the ID of the asset the sponsorship notes are funded with. +#! - create_notes is 1 to create the sponsorship notes, 0 to only price them. +#! - total_sponsored_fee_amount is the total amount the sponsorship notes carry. +#! - num_sponsorship_notes is the number of network notes priced above zero, i.e. of sponsorship +#! notes created or to be created. +#! - num_output_notes is the number of output notes walked, read before any note is created. #! -#! Inputs: [note_idx, attachment_idx, EXPECTED_FEE_ASSET_ID] +#! Panics if: +#! - process_network_note_sponsorship panics for a network output note. +#! +#! Invocation: exec +proc process_network_note_sponsorships(fee_asset_id: AssetId, create_notes: Bool) -> (felt, felt, felt) + push.0 movdn.4 + # => [FEE_ASSET_ID, num_sponsorship_notes = 0, create_notes] + + push.0 movdn.4 + # => [FEE_ASSET_ID, total_sponsored_fee_amount = 0, num_sponsorship_notes, create_notes] + + # get the output note count before any sponsorship notes are created, since creating them + # increments the count + exec.tx::get_num_output_notes + # => [num_output_notes, FEE_ASSET_ID, total_sponsored_fee_amount, num_sponsorship_notes, + # create_notes] + + push.0 + # => [note_idx = 0, num_output_notes, FEE_ASSET_ID, total_sponsored_fee_amount, + # num_sponsorship_notes, create_notes] + + dup.1 dup.1 neq + # => [should_loop, note_idx, num_output_notes, FEE_ASSET_ID, total_sponsored_fee_amount, + # num_sponsorship_notes, create_notes] + + while.true + # only network output notes are sponsored + dup exec.is_network_note + # => [is_network_note, attachment_idx, note_idx, num_output_notes, FEE_ASSET_ID, + # total_sponsored_fee_amount, num_sponsorship_notes, create_notes] + + if.true + dup.1 + # => [note_idx, attachment_idx, note_idx, num_output_notes, FEE_ASSET_ID, + # total_sponsored_fee_amount, num_sponsorship_notes, create_notes] + + # pass a copy of the expected fee asset ID and of the create flag along with the note + # parameters + dupw.1 movup.5 movup.5 dup.14 movdn.6 + # => [note_idx, attachment_idx, FEE_ASSET_ID, create_notes, note_idx, num_output_notes, + # FEE_ASSET_ID, total_sponsored_fee_amount, num_sponsorship_notes, create_notes] + + exec.process_network_note_sponsorship + # => [sponsored_fee_amount, note_idx, num_output_notes, FEE_ASSET_ID, + # total_sponsored_fee_amount, num_sponsorship_notes, create_notes] + + # a note priced to zero is sponsored by no note + dup neq.0 movup.9 add movdn.8 + # => [sponsored_fee_amount, note_idx, num_output_notes, FEE_ASSET_ID, + # total_sponsored_fee_amount, num_sponsorship_notes, create_notes] + + movup.7 add movdn.6 + # => [note_idx, num_output_notes, FEE_ASSET_ID, total_sponsored_fee_amount, + # num_sponsorship_notes, create_notes] + else + # do not sponsor non-network output notes + drop + # => [note_idx, num_output_notes, FEE_ASSET_ID, total_sponsored_fee_amount, + # num_sponsorship_notes, create_notes] + end + + add.1 + # => [note_idx+1, num_output_notes, FEE_ASSET_ID, total_sponsored_fee_amount, + # num_sponsorship_notes, create_notes] + + dup.1 dup.1 neq + # => [should_loop, note_idx, num_output_notes, FEE_ASSET_ID, total_sponsored_fee_amount, + # num_sponsorship_notes, create_notes] + end + # => [note_idx, num_output_notes, FEE_ASSET_ID, total_sponsored_fee_amount, + # num_sponsorship_notes, create_notes] + + drop movdn.6 dropw movup.3 drop + # => [total_sponsored_fee_amount, num_sponsorship_notes, num_output_notes] +end + +#! Prices the network output note at the given index and, when asked to, creates its +#! FEE_SPONSORSHIP note. +#! +#! Handles a single network note for `create_network_note_sponsorships` (create_notes = 1) and +#! `estimate_network_note_sponsorships` (create_notes = 0); see those for the full behaviour. A +#! network note the target account prices to zero needs no sponsorship note, so none is created for +#! it. When create_notes is 0 the note is only priced: the vault and the output notes are left +#! untouched. +#! +#! Inputs: [note_idx, attachment_idx, EXPECTED_FEE_ASSET_ID, create_notes] #! Outputs: [sponsored_fee_amount] #! #! Where: #! - note_idx is the index of the network output note to sponsor. #! - attachment_idx is the index of the note's network account target attachment. #! - EXPECTED_FEE_ASSET_ID is the ID of the asset the sponsorship note must be funded with. -#! - sponsored_fee_amount is the amount moved into the sponsorship note, or zero if none was -#! created. +#! - create_notes is 1 to create the sponsorship note, 0 to only price it. +#! - sponsored_fee_amount is the amount the sponsorship note carries, or zero if the target prices +#! the note to zero and so it needs none. #! #! Panics if: #! - the note's target ID attachment does not consist of exactly one word. @@ -623,43 +704,54 @@ end #! or does not hash to the recipient. #! - the target account's active fee policy is not set. #! - the target account charges a non-zero fee in an asset other than the expected fee asset. -#! - the native account's vault does not hold enough of the fee asset. +#! - create_notes is 1 and the native account's vault does not hold enough of the fee asset. #! #! Invocation: exec @locals(4) -proc create_network_note_sponsorship( +proc process_network_note_sponsorship( note_idx: u16, attachment_idx: u8, - expected_fee_asset_id: AssetId + expected_fee_asset_id: AssetId, + create_notes: Bool ) -> felt # cache the expected fee asset ID for the target fee asset check movdn.5 movdn.5 loc_storew_le.EXPECTED_FEE_ASSET_ID_LOC dropw - # => [note_idx, attachment_idx] + # => [note_idx, attachment_idx, create_notes] - dup movdn.2 - # => [note_idx, attachment_idx, note_idx] + dup movdn.3 + # => [note_idx, attachment_idx, create_notes, note_idx] exec.compute_sponsorship_fee - # => [FEE_ASSET_ID, FEE_ASSET_VALUE, target_id_prefix, note_idx] + # => [FEE_ASSET_ID, FEE_ASSET_VALUE, target_id_prefix, create_notes, note_idx] exec.fungible_asset::to_amount_unchecked - # => [fee_amount, FEE_ASSET_ID, FEE_ASSET_VALUE, target_id_prefix, note_idx] + # => [fee_amount, FEE_ASSET_ID, FEE_ASSET_VALUE, target_id_prefix, create_notes, note_idx] - movdn.10 dup.10 eq.0 - # => [is_zero_fee, FEE_ASSET_ID, FEE_ASSET_VALUE, target_id_prefix, note_idx, fee_amount] + movdn.11 dup.11 eq.0 + # => [is_zero_fee, FEE_ASSET_ID, FEE_ASSET_VALUE, target_id_prefix, create_notes, note_idx, + # fee_amount] if.true - # if the price of the note is zero, do not create a sponsorship note - dropw dropw drop drop + # if the price of the note is zero, it needs no sponsorship note + dropw dropw drop drop drop # => [fee_amount] else # reject a target whose fee asset differs from the expected fee asset dupw padw loc_loadw_le.EXPECTED_FEE_ASSET_ID_LOC exec.word::eq assert.err=ERR_FEE_MANAGER_TARGET_FEE_ASSET_MISMATCH - # => [FEE_ASSET_ID, FEE_ASSET_VALUE, target_id_prefix, note_idx, fee_amount] + # => [FEE_ASSET_ID, FEE_ASSET_VALUE, target_id_prefix, create_notes, note_idx, fee_amount] - exec.create_sponsorship_note - # => [fee_amount] + movup.9 + # => [create_notes, FEE_ASSET_ID, FEE_ASSET_VALUE, target_id_prefix, note_idx, fee_amount] + + if.true + exec.create_sponsorship_note + # => [fee_amount] + else + # only pricing the note: discard the inputs of the note creation + dropw dropw drop drop + # => [fee_amount] + end end end diff --git a/crates/miden-standards/src/note/costs/table.rs b/crates/miden-standards/src/note/costs/table.rs index 9f763ca5e5..d784b7d3b2 100644 --- a/crates/miden-standards/src/note/costs/table.rs +++ b/crates/miden-standards/src/note/costs/table.rs @@ -2,54 +2,54 @@ // Values are maxima across the benchmarked paths; see `miden_standards::note::costs` for the // caveats on what they do and do not cover. -/// Cycles of consuming a P2ID note: 1 asset 18620, 16 assets 57188 (maximum). -pub const P2ID_CONSUMPTION_CYCLES: u32 = 57188; +/// Cycles of consuming a P2ID note: 1 asset 19356, 16 assets 57924 (maximum). +pub const P2ID_CONSUMPTION_CYCLES: u32 = 57924; -/// Cycles of consuming a P2IDE note: claim 18728, claim with 16 assets 57296 (maximum), reclaim -/// 18883. -pub const P2IDE_CONSUMPTION_CYCLES: u32 = 57296; +/// Cycles of consuming a P2IDE note: claim 19464, claim with 16 assets 58032 (maximum), reclaim +/// 19619. +pub const P2IDE_CONSUMPTION_CYCLES: u32 = 58032; -/// Cycles of consuming a SWAP note: public payback 22655 (maximum), private payback 22152. -pub const SWAP_CONSUMPTION_CYCLES: u32 = 22655; +/// Cycles of consuming a SWAP note: public payback 23593 (maximum), private payback 23090. +pub const SWAP_CONSUMPTION_CYCLES: u32 = 23593; -/// Cycles of consuming a PSWAP note: full fill 25485, partial fill 29402 (maximum). -pub const PSWAP_CONSUMPTION_CYCLES: u32 = 29402; +/// Cycles of consuming a PSWAP note: full fill 26428, partial fill 30554 (maximum). +pub const PSWAP_CONSUMPTION_CYCLES: u32 = 30554; -/// Cycles of consuming a MINT note: fungible faucet 34087, non-fungible faucet 37611 (maximum). -pub const MINT_CONSUMPTION_CYCLES: u32 = 37611; +/// Cycles of consuming a MINT note: fungible faucet 35248, non-fungible faucet 38612 (maximum). +pub const MINT_CONSUMPTION_CYCLES: u32 = 38612; /// Cycles of consuming a BURN note (single benchmarked path). -pub const BURN_CONSUMPTION_CYCLES: u32 = 28773; +pub const BURN_CONSUMPTION_CYCLES: u32 = 29595; /// Cycles of consuming a CONSTANT_FEE_POLICY_CONFIG note (single benchmarked path). -pub const CONSTANT_FEE_POLICY_CONFIG_CONSUMPTION_CYCLES: u32 = 19906; +pub const CONSTANT_FEE_POLICY_CONFIG_CONSUMPTION_CYCLES: u32 = 20687; /// Cycles of consuming a FAUCET_POLICY_CONFIG note (single benchmarked path). -pub const FAUCET_POLICY_CONFIG_CONSUMPTION_CYCLES: u32 = 27017; +pub const FAUCET_POLICY_CONFIG_CONSUMPTION_CYCLES: u32 = 27791; /// Cycles of consuming a FAUCET_METADATA_CONFIG note (single benchmarked path). -pub const FAUCET_METADATA_CONFIG_CONSUMPTION_CYCLES: u32 = 27488; +pub const FAUCET_METADATA_CONFIG_CONSUMPTION_CYCLES: u32 = 28323; /// Cycles of consuming a MIN_BURN_AMOUNT_CONFIG note (single benchmarked path). -pub const MIN_BURN_AMOUNT_CONFIG_CONSUMPTION_CYCLES: u32 = 26053; +pub const MIN_BURN_AMOUNT_CONFIG_CONSUMPTION_CYCLES: u32 = 26820; /// Cycles of consuming an ALLOWLIST_CONFIG note (single benchmarked path). -pub const ALLOWLIST_CONFIG_CONSUMPTION_CYCLES: u32 = 26799; +pub const ALLOWLIST_CONFIG_CONSUMPTION_CYCLES: u32 = 27566; /// Cycles of consuming a BLOCKLIST_CONFIG note (single benchmarked path). -pub const BLOCKLIST_CONFIG_CONSUMPTION_CYCLES: u32 = 26799; +pub const BLOCKLIST_CONFIG_CONSUMPTION_CYCLES: u32 = 27566; /// Cycles of consuming a PAUSE_CONFIG note (single benchmarked path). -pub const PAUSE_CONFIG_CONSUMPTION_CYCLES: u32 = 18531; +pub const PAUSE_CONFIG_CONSUMPTION_CYCLES: u32 = 19298; /// Cycles of consuming an OWNER_CONFIG note (single benchmarked path). -pub const OWNER_CONFIG_CONSUMPTION_CYCLES: u32 = 18112; +pub const OWNER_CONFIG_CONSUMPTION_CYCLES: u32 = 18879; /// Cycles of consuming an RBAC_CONFIG note (single benchmarked path). -pub const RBAC_CONFIG_CONSUMPTION_CYCLES: u32 = 21935; +pub const RBAC_CONFIG_CONSUMPTION_CYCLES: u32 = 22874; /// Cycles of consuming a NETWORK_ACCOUNT_CONFIG note (single benchmarked path). -pub const NETWORK_ACCOUNT_CONFIG_CONSUMPTION_CYCLES: u32 = 19012; +pub const NETWORK_ACCOUNT_CONFIG_CONSUMPTION_CYCLES: u32 = 19779; /// Cycles of consuming a FEE_SPONSORSHIP note (single benchmarked path). -pub const FEE_SPONSORSHIP_CONSUMPTION_CYCLES: u32 = 21472; +pub const FEE_SPONSORSHIP_CONSUMPTION_CYCLES: u32 = 22250; diff --git a/crates/miden-testing/tests/auth/fee_payment/sponsorship.rs b/crates/miden-testing/tests/auth/fee_payment/sponsorship.rs index 0b402295fd..7affdd240c 100644 --- a/crates/miden-testing/tests/auth/fee_payment/sponsorship.rs +++ b/crates/miden-testing/tests/auth/fee_payment/sponsorship.rs @@ -8,7 +8,7 @@ use miden_protocol::asset::{Asset, AssetAmount, AssetId, FungibleAsset}; use miden_protocol::crypto::rand::FeltRng; use miden_protocol::note::{Note, NoteScriptRoot, NoteTag, NoteType, PartialNote}; use miden_protocol::testing::account_id::ACCOUNT_ID_FEE_FAUCET; -use miden_protocol::transaction::RawOutputNote; +use miden_protocol::transaction::{ExecutedTransaction, RawOutputNote}; use miden_standards::account::auth::{ AuthNetworkAccount, FeeConversionInfo, @@ -108,14 +108,18 @@ fn p2id_network_note( .into()) } -// TESTS -// ================================================================================================ +/// Returns whether the output note is a FEE_SPONSORSHIP note. +fn is_sponsorship_note(note: &RawOutputNote) -> bool { + note.recipient() + .is_some_and(|recipient| recipient.script().root() == FeeSponsorshipNote::script_root()) +} -/// When a fee-paying account creates a network output note, `pay_fee` sponsors it: a -/// FEE_SPONSORSHIP note funded from the creator's vault is emitted alongside the network note, -/// carrying exactly the fee the target account's policy prices the note at. -#[tokio::test] -async fn pay_fee_sponsors_network_output_note() -> anyhow::Result<()> { +/// Has a signing wallet send a P2ID network note to a network account that prices the P2ID script +/// root at `target_fee`, paying its fee through `pay_fee`. Returns the executed transaction, the +/// network note it created and the target's ID. +async fn send_network_note( + target_fee: u64, +) -> anyhow::Result<(ExecutedTransaction, Note, AccountId)> { let mut rng = RandomCoin::new(Word::from([1u32, 2, 3, 4])); // a payload asset issued by a faucet other than the fee faucet, carried by the network note let payload_asset: Asset = FungibleAsset::mock(50); @@ -129,11 +133,11 @@ async fn pay_fee_sponsors_network_output_note() -> anyhow::Result<()> { [fee_asset(1_000_000)?, payload_asset], )?; - // the target network account prices the P2ID script root at FEE_AMOUNT + // the target network account prices the P2ID script root at target_fee let target = network_account( [2; 32], [P2idNote::script_root(), FeeSponsorshipNote::script_root()], - &[(P2idNote::script_root(), FEE_AMOUNT)], + &[(P2idNote::script_root(), target_fee)], [], SponsorshipPolicy::default(), )?; @@ -163,6 +167,19 @@ async fn pay_fee_sponsors_network_output_note() -> anyhow::Result<()> { .execute() .await?; + Ok((executed, network_note, target.id())) +} + +// TESTS +// ================================================================================================ + +/// When a fee-paying account creates a network output note, `pay_fee` sponsors it: a +/// FEE_SPONSORSHIP note funded from the creator's vault is emitted alongside the network note, +/// carrying exactly the fee the target account's policy prices the note at. +#[tokio::test] +async fn pay_fee_sponsors_network_output_note() -> anyhow::Result<()> { + let (executed, network_note, target_id) = send_network_note(FEE_AMOUNT).await?; + // three output notes: the network note, its sponsorship note, and the sponsor's fee note let output_notes = executed.output_notes(); assert_eq!(output_notes.num_notes(), 3); @@ -178,14 +195,11 @@ async fn pay_fee_sponsors_network_output_note() -> anyhow::Result<()> { // target network account let sponsorship = output_notes .iter() - .find(|note| { - note.recipient().map(|recipient| recipient.script().root()) - == Some(FeeSponsorshipNote::script_root()) - }) + .find(|note| is_sponsorship_note(note)) .expect("a sponsorship note should be created for the network note"); let sponsorship_assets: Vec = sponsorship.assets().iter().copied().collect(); assert_eq!(sponsorship_assets, vec![fee_asset(FEE_AMOUNT)?]); - assert_eq!(sponsorship.metadata().tag(), NoteTag::with_account_target(target.id())); + assert_eq!(sponsorship.metadata().tag(), NoteTag::with_account_target(target_id)); // the sponsor still pays its own fee, and the paid amount covers the fee required for the // transaction including the sponsorship note it just created @@ -205,6 +219,40 @@ async fn pay_fee_sponsors_network_output_note() -> anyhow::Result<()> { Ok(()) } +/// A network note priced to zero by its target needs no sponsorship: `pay_fee` creates no +/// FEE_SPONSORSHIP note for it and still pays the sponsor's own fee. +#[tokio::test] +async fn pay_fee_does_not_sponsor_network_note_priced_to_zero() -> anyhow::Result<()> { + let (executed, network_note, _) = send_network_note(0).await?; + + // two output notes: the network note and the sponsor's fee note + let output_notes = executed.output_notes(); + assert_eq!(output_notes.num_notes(), 2); + assert_eq!(output_notes.get_note(0).id(), network_note.id()); + assert!( + output_notes.iter().all(|note| !is_sponsorship_note(note)), + "a network note priced to zero should not be sponsored", + ); + // the sponsor's fee note still covers the fee required for the transaction, including the + // pricing call the payment makes for the unsponsored note after the fee is computed + let fee_note = output_notes.get_note(1); + assert_eq!(fee_note.metadata().tag(), TxFeeNote::TAG); + let paid = fee_note + .assets() + .iter() + .next() + .expect("fee note carries one asset") + .unwrap_fungible(); + assert!( + paid.amount() >= executed.compute_fee(), + "paid fee {} should cover the required fee {}", + paid.amount(), + executed.compute_fee(), + ); + + Ok(()) +} + /// End-to-end single hop: a local wallet creates a P2ID network note (and, via `pay_fee`, its /// FEE_SPONSORSHIP note); the target network account then consumes both, and its /// `collect_sponsored_fees` credits the prepaid fee into its vault. @@ -254,10 +302,7 @@ async fn network_account_collects_sponsored_fee_single_hop() -> anyhow::Result<( let sponsorship_id = creation_tx .output_notes() .iter() - .find(|note| { - note.recipient().map(|recipient| recipient.script().root()) - == Some(FeeSponsorshipNote::script_root()) - }) + .find(|note| is_sponsorship_note(note)) .expect("a sponsorship note should be created") .id(); @@ -373,10 +418,7 @@ async fn spawned_network_note_sponsored_by_a_and_collected_by_b_multi_hop() -> a let sponsorship_id = spawn_tx .output_notes() .iter() - .find(|note| { - note.recipient().map(|recipient| recipient.script().root()) - == Some(FeeSponsorshipNote::script_root()) - }) + .find(|note| is_sponsorship_note(note)) .expect("A should sponsor the spawned note") .id(); diff --git a/crates/miden-testing/tests/scripts/fee_collection.rs b/crates/miden-testing/tests/scripts/fee_collection.rs index 693956838d..ca08ac628f 100644 --- a/crates/miden-testing/tests/scripts/fee_collection.rs +++ b/crates/miden-testing/tests/scripts/fee_collection.rs @@ -1259,11 +1259,23 @@ async fn sponsors_and_collects_multiple_notes() -> anyhow::Result<()> { // SPONSORSHIP POLICY // ================================================================================================ -/// Under [`SponsorshipPolicy::AtMostCollectedFees`], a sponsorship that no collected fee backs is -/// rejected. +/// Under [`SponsorshipPolicy::AtMostCollectedFees`], sponsorships the collected fees do not cover +/// are rejected: one that nothing backs, and two whose summed amount exceeds the one collected fee. +/// The latter pins that the cap compares the summed amount: a total that collapsed to the number +/// of sponsorship notes would pass it. +#[rstest] +// (network notes created, collected notes consumed) +#[case::nothing_collected(1, 0)] +#[case::one_of_two_covered(2, 1)] #[tokio::test] -async fn sponsoring_more_than_collected_is_rejected() -> anyhow::Result<()> { - let test = SponsorshipTest::builder().build()?; +async fn sponsoring_more_than_collected_is_rejected( + #[case] num_network_notes: u32, + #[case] num_collected_notes: u32, +) -> anyhow::Result<()> { + let test = SponsorshipTest::builder() + .num_network_notes(num_network_notes) + .num_collected_notes(num_collected_notes) + .build()?; let result = test.transaction()?.execute().await; From c6c61645973636c488e30c05d40c0f03d1ccdff7 Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Thu, 3 Sep 2026 09:34:02 +0000 Subject: [PATCH 02/12] docs(standards): fix the estimate_fee_for_asset panic list and name the apply_cycle_margins change Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01KkQzFtRjyDwQ7iVtRbsHsn --- CHANGELOG.md | 2 +- crates/miden-standards/asm/standards/fee/mod.masm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a6527adb0..1f69f0a9b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ ### Changes -- [BREAKING] Split `fee::estimate_fee` out of `fee::pay_fee`, pricing the FEE_SPONSORSHIP notes via the new `fees::estimate_network_note_sponsorships` without creating them; the MAST roots of every fee-paying component change ([#3784](https://github.com/0xMiden/protocol/pull/3784)). +- [BREAKING] Split `fee::estimate_fee` out of `fee::pay_fee`, pricing the FEE_SPONSORSHIP notes via the new `fees::estimate_network_note_sponsorships` without creating them, and `fee::apply_cycle_margins` now also takes the output-note count; the MAST roots of every fee-paying component change ([#3784](https://github.com/0xMiden/protocol/pull/3784)). - Added a check that the guardian public key is not one of the approver public keys ([#3764](https://github.com/0xMiden/protocol/pull/3764)). - [BREAKING] Incremented the MSRV to 1.98. - [BREAKING] Updated the Miden VM and crypto crate family to v0.30.0 and `midenc-hir-type` to v0.12.0. `LocalTransactionProver::new` now takes `miden_prover::Prover`, `CoreLibrary` exposes one merged package, and `TransactionVerifier::verify` now returns `VerificationOutcome` so callers can handle outstanding precompile work ([#3782](https://github.com/0xMiden/protocol/pull/3782)). diff --git a/crates/miden-standards/asm/standards/fee/mod.masm b/crates/miden-standards/asm/standards/fee/mod.masm index fa5c354004..3988ce8a8e 100644 --- a/crates/miden-standards/asm/standards/fee/mod.masm +++ b/crates/miden-standards/asm/standards/fee/mod.masm @@ -483,7 +483,7 @@ end #! - num_extra_cycles and fee_amount are as described in `estimate_fee`. #! #! Panics if: -#! - `estimate_fee` panics. +#! - `fees::estimate_network_note_sponsorships` or `tx::compute_fee` panics. #! #! Invocation: exec proc estimate_fee_for_asset(fee_asset_id: AssetId, num_extra_cycles: felt) -> felt From faea1e10459f7ae6163a0ccbae30bd6cd0c421a3 Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Thu, 3 Sep 2026 10:45:47 +0000 Subject: [PATCH 03/12] refactor(standards): let estimate_fee take the fee asset ID Drops the wrapper that only read the asset ID: `fee::estimate_fee` now takes FEE_ASSET_ID as its first input, and `pay_fee` reads it once for the estimate and the sponsorship payment. Cost tables regenerated. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01KkQzFtRjyDwQ7iVtRbsHsn --- bin/bench-transaction/bench-tx.json | 210 +++++++++--------- .../asm/standards/fee/mod.masm | 62 ++---- .../miden-standards/src/note/costs/table.rs | 24 +- 3 files changed, 137 insertions(+), 159 deletions(-) diff --git a/bin/bench-transaction/bench-tx.json b/bin/bench-transaction/bench-tx.json index ca895bf5d9..60331586e6 100644 --- a/bin/bench-transaction/bench-tx.json +++ b/bin/bench-transaction/bench-tx.json @@ -14,7 +14,7 @@ "core_rows": 81409, "chiplets_rows": 11491, "poseidon2_permutation_rows": 55328, - "range_rows": 20631, + "range_rows": 20703, "chiplets_shape": { "hasher_rows": 8648, "bitwise_rows": 656, @@ -39,7 +39,7 @@ "core_rows": 13646, "chiplets_rows": 5859, "poseidon2_permutation_rows": 19824, - "range_rows": 2011, + "range_rows": 1989, "chiplets_shape": { "hasher_rows": 4216, "bitwise_rows": 848, @@ -65,7 +65,7 @@ "core_rows": 84009, "chiplets_rows": 13390, "poseidon2_permutation_rows": 55584, - "range_rows": 20585, + "range_rows": 20515, "chiplets_shape": { "hasher_rows": 10064, "bitwise_rows": 1032, @@ -91,7 +91,7 @@ "core_rows": 16246, "chiplets_rows": 7758, "poseidon2_permutation_rows": 20080, - "range_rows": 1397, + "range_rows": 1411, "chiplets_shape": { "hasher_rows": 5632, "bitwise_rows": 1224, @@ -114,7 +114,7 @@ "core_rows": 79599, "chiplets_rows": 10903, "poseidon2_permutation_rows": 53216, - "range_rows": 20307, + "range_rows": 20443, "chiplets_shape": { "hasher_rows": 8232, "bitwise_rows": 616, @@ -137,7 +137,7 @@ "core_rows": 11836, "chiplets_rows": 5271, "poseidon2_permutation_rows": 17664, - "range_rows": 1225, + "range_rows": 1209, "chiplets_shape": { "hasher_rows": 3800, "bitwise_rows": 808, @@ -449,9 +449,9 @@ }, "consume PSWAP note (full fill, network account)": { "prologue": 4129, - "notes_processing": 7341, + "notes_processing": 7352, "note_execution": { - "0xd002f96b26ae44a19bb6e2ef8c4bb0b18dfc13448df8e8bf7622c256bf5ab2cc": 7299 + "0x0b120c8f0057de9c9e75f96fcd8a6b534e97ffea43516a8cbfd696a6c5e91420": 7310 }, "tx_script_processing": 41, "epilogue": { @@ -459,10 +459,10 @@ "auth_procedure": 10473 }, "trace": { - "core_rows": 26472, + "core_rows": 26483, "chiplets_rows": 11779, "poseidon2_permutation_rows": 31104, - "range_rows": 1989, + "range_rows": 2029, "chiplets_shape": { "hasher_rows": 8872, "bitwise_rows": 1416, @@ -474,9 +474,9 @@ }, "consume PSWAP note (partial fill, network account)": { "prologue": 4129, - "notes_processing": 9846, + "notes_processing": 9868, "note_execution": { - "0xd002f96b26ae44a19bb6e2ef8c4bb0b18dfc13448df8e8bf7622c256bf5ab2cc": 9804 + "0x0b120c8f0057de9c9e75f96fcd8a6b534e97ffea43516a8cbfd696a6c5e91420": 9826 }, "tx_script_processing": 41, "epilogue": { @@ -484,10 +484,10 @@ "auth_procedure": 10881 }, "trace": { - "core_rows": 30598, + "core_rows": 30620, "chiplets_rows": 13635, "poseidon2_permutation_rows": 32896, - "range_rows": 2083, + "range_rows": 2087, "chiplets_shape": { "hasher_rows": 10136, "bitwise_rows": 1840, @@ -574,9 +574,9 @@ }, "consume FAUCET_POLICY_CONFIG note (network account)": { "prologue": 5788, - "notes_processing": 3630, + "notes_processing": 3762, "note_execution": { - "0x7c8bce77a06f095ef8528d12d13d39f9b23b4cbfdc232351bda98867421dbd51": 3588 + "0xe01a79fc018f08426261e4a923b8f1f6c286ecd8e498128625f2a426da6f8820": 3720 }, "tx_script_processing": 41, "epilogue": { @@ -584,14 +584,14 @@ "auth_procedure": 9815 }, "trace": { - "core_rows": 27835, - "chiplets_rows": 10651, - "poseidon2_permutation_rows": 26816, - "range_rows": 2607, + "core_rows": 27967, + "chiplets_rows": 10704, + "poseidon2_permutation_rows": 26880, + "range_rows": 2651, "chiplets_shape": { - "hasher_rows": 8208, - "bitwise_rows": 640, - "memory_rows": 1801, + "hasher_rows": 8248, + "bitwise_rows": 648, + "memory_rows": 1806, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -599,9 +599,9 @@ }, "consume FAUCET_METADATA_CONFIG note (network account)": { "prologue": 5239, - "notes_processing": 6122, + "notes_processing": 6254, "note_execution": { - "0x3d6e7c333189b6747362486e248915a9be8ffe043ea0f3780d67a90b0a0797a0": 6080 + "0x8b054ac6467e35c53f3ffbf90e06ec27f8195b0129ba30679e1f012d7e3c0896": 6212 }, "tx_script_processing": 41, "epilogue": { @@ -609,14 +609,14 @@ "auth_procedure": 9734 }, "trace": { - "core_rows": 28367, - "chiplets_rows": 10752, - "poseidon2_permutation_rows": 26112, - "range_rows": 2469, + "core_rows": 28499, + "chiplets_rows": 10805, + "poseidon2_permutation_rows": 26176, + "range_rows": 2471, "chiplets_shape": { - "hasher_rows": 8232, - "bitwise_rows": 648, - "memory_rows": 1870, + "hasher_rows": 8272, + "bitwise_rows": 656, + "memory_rows": 1875, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -624,9 +624,9 @@ }, "consume MIN_BURN_AMOUNT_CONFIG note (network account)": { "prologue": 5845, - "notes_processing": 2449, + "notes_processing": 2581, "note_execution": { - "0x5dc62fd00814e17f051376fb49493484003bbc0c9e0c9b3396033aa6caf8cacd": 2407 + "0x6aee9cc83c5ba20b828cd1a3f63cee7c57dc3231d98d3a7b41aab1f2fd570463": 2539 }, "tx_script_processing": 41, "epilogue": { @@ -634,14 +634,14 @@ "auth_procedure": 9824 }, "trace": { - "core_rows": 26864, - "chiplets_rows": 10269, - "poseidon2_permutation_rows": 25408, - "range_rows": 2611, + "core_rows": 26996, + "chiplets_rows": 10314, + "poseidon2_permutation_rows": 25472, + "range_rows": 2635, "chiplets_shape": { - "hasher_rows": 7872, - "bitwise_rows": 640, - "memory_rows": 1755, + "hasher_rows": 7904, + "bitwise_rows": 648, + "memory_rows": 1760, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -649,9 +649,9 @@ }, "consume ALLOWLIST_CONFIG note (network account)": { "prologue": 5845, - "notes_processing": 3016, + "notes_processing": 3148, "note_execution": { - "0x82ae7cc36f7837b5fe5e0328e061bc8358e56be0075f73439ce4305358d3bf43": 2974 + "0xfa26c9aa1c21b5a79ea7c0726f17c1c0d64aa18f24ae4fa1e1a252f3dc2e0422": 3106 }, "tx_script_processing": 41, "epilogue": { @@ -659,14 +659,14 @@ "auth_procedure": 9824 }, "trace": { - "core_rows": 27610, - "chiplets_rows": 10895, - "poseidon2_permutation_rows": 27952, - "range_rows": 2635, + "core_rows": 27742, + "chiplets_rows": 10940, + "poseidon2_permutation_rows": 28016, + "range_rows": 2601, "chiplets_shape": { - "hasher_rows": 8424, - "bitwise_rows": 640, - "memory_rows": 1829, + "hasher_rows": 8456, + "bitwise_rows": 648, + "memory_rows": 1834, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -674,9 +674,9 @@ }, "consume BLOCKLIST_CONFIG note (network account)": { "prologue": 5845, - "notes_processing": 3016, + "notes_processing": 3148, "note_execution": { - "0x05c704c0fc0efca937aca121f76b3bd810c202d979e47f982fb128e6b2a6a58b": 2974 + "0xd0fe81015108191518f03d38bcd5d2b65ffc6263143bba5b032717104fe0f840": 3106 }, "tx_script_processing": 41, "epilogue": { @@ -684,14 +684,14 @@ "auth_procedure": 9824 }, "trace": { - "core_rows": 27610, - "chiplets_rows": 10895, - "poseidon2_permutation_rows": 27776, - "range_rows": 2609, + "core_rows": 27742, + "chiplets_rows": 10940, + "poseidon2_permutation_rows": 27840, + "range_rows": 2621, "chiplets_shape": { - "hasher_rows": 8424, - "bitwise_rows": 640, - "memory_rows": 1829, + "hasher_rows": 8456, + "bitwise_rows": 648, + "memory_rows": 1834, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -699,9 +699,9 @@ }, "consume PAUSE_CONFIG note (network account)": { "prologue": 3715, - "notes_processing": 2464, + "notes_processing": 2596, "note_execution": { - "0xa10de965c325fde1c84890ddca21fe4853c6f8e1320374923619708ac4ddc273": 2422 + "0x8319881fb2093f5e4202542b56e2e4f28db21a4261fdef6e0a1e9b6e503d57e3": 2554 }, "tx_script_processing": 41, "epilogue": { @@ -709,14 +709,14 @@ "auth_procedure": 9509 }, "trace": { - "core_rows": 19342, - "chiplets_rows": 7937, - "poseidon2_permutation_rows": 24208, - "range_rows": 1647, + "core_rows": 19474, + "chiplets_rows": 7990, + "poseidon2_permutation_rows": 24272, + "range_rows": 1675, "chiplets_shape": { - "hasher_rows": 6104, - "bitwise_rows": 640, - "memory_rows": 1191, + "hasher_rows": 6144, + "bitwise_rows": 648, + "memory_rows": 1196, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -724,9 +724,9 @@ }, "consume OWNER_CONFIG note (network account)": { "prologue": 3574, - "notes_processing": 2492, + "notes_processing": 2624, "note_execution": { - "0x770d225819e7af018842c17d8b038bd5517169f763b3220360292c528b12039f": 2450 + "0xea76aaf9623f2edea65fa01b923bd7817e2ee98e51cb885047a3134fb3e46829": 2582 }, "tx_script_processing": 41, "epilogue": { @@ -734,14 +734,14 @@ "auth_procedure": 9491 }, "trace": { - "core_rows": 18923, - "chiplets_rows": 7825, - "poseidon2_permutation_rows": 24064, - "range_rows": 1607, + "core_rows": 19055, + "chiplets_rows": 7870, + "poseidon2_permutation_rows": 24128, + "range_rows": 1613, "chiplets_shape": { - "hasher_rows": 6008, - "bitwise_rows": 656, - "memory_rows": 1159, + "hasher_rows": 6040, + "bitwise_rows": 664, + "memory_rows": 1164, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -749,9 +749,9 @@ }, "consume RBAC_CONFIG note (network account)": { "prologue": 3753, - "notes_processing": 5460, + "notes_processing": 5592, "note_execution": { - "0xf7bee3cd990c2daa18fbb81ee73fc4a117f44bd77247c6de5e8d213268113524": 5418 + "0x42f1d87c22e0b90f446f37474f19de44b4f24e6f1bb1552bb9390dddde1acd77": 5550 }, "tx_script_processing": 41, "epilogue": { @@ -759,14 +759,14 @@ "auth_procedure": 9518 }, "trace": { - "core_rows": 22918, - "chiplets_rows": 10307, - "poseidon2_permutation_rows": 30656, - "range_rows": 1923, + "core_rows": 23050, + "chiplets_rows": 10352, + "poseidon2_permutation_rows": 30720, + "range_rows": 1911, "chiplets_shape": { - "hasher_rows": 8176, - "bitwise_rows": 656, - "memory_rows": 1473, + "hasher_rows": 8208, + "bitwise_rows": 664, + "memory_rows": 1478, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -774,9 +774,9 @@ }, "consume NETWORK_ACCOUNT_CONFIG note (network account)": { "prologue": 3658, - "notes_processing": 2989, + "notes_processing": 3121, "note_execution": { - "0x0a46944038fb10b361d0b14f62f4f153f1df530eab3fcde2e01f7002c79a39bc": 2947 + "0x1f642466bd8f54d64c5b1670c001ccb7e01fd0d7fde2d0c3d46c7b0fa99e4763": 3079 }, "tx_script_processing": 41, "epilogue": { @@ -784,14 +784,14 @@ "auth_procedure": 9500 }, "trace": { - "core_rows": 19823, - "chiplets_rows": 8481, - "poseidon2_permutation_rows": 26768, - "range_rows": 1715, + "core_rows": 19955, + "chiplets_rows": 8526, + "poseidon2_permutation_rows": 26816, + "range_rows": 1651, "chiplets_shape": { - "hasher_rows": 6600, - "bitwise_rows": 640, - "memory_rows": 1239, + "hasher_rows": 6632, + "bitwise_rows": 648, + "memory_rows": 1244, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -799,9 +799,9 @@ }, "consume CONSTANT_FEE_POLICY_CONFIG note (network account)": { "prologue": 3714, - "notes_processing": 3688, + "notes_processing": 3820, "note_execution": { - "0x2127d871673cd4e51c92f209e6182e2254ad3bdcb24f0a43a4bf21ad56ac01d2": 3646 + "0x025c0149268d80d27e8b63ac04c78d5d301eb25fa010a0066209a1b53c299c16": 3778 }, "tx_script_processing": 41, "epilogue": { @@ -809,14 +809,14 @@ "auth_procedure": 9509 }, "trace": { - "core_rows": 20731, - "chiplets_rows": 8767, - "poseidon2_permutation_rows": 26864, - "range_rows": 1735, + "core_rows": 20863, + "chiplets_rows": 8820, + "poseidon2_permutation_rows": 26944, + "range_rows": 1733, "chiplets_shape": { - "hasher_rows": 6808, - "bitwise_rows": 640, - "memory_rows": 1317, + "hasher_rows": 6848, + "bitwise_rows": 648, + "memory_rows": 1322, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -863,7 +863,7 @@ "core_rows": 17323, "chiplets_rows": 7855, "poseidon2_permutation_rows": 23696, - "range_rows": 1597, + "range_rows": 1547, "chiplets_shape": { "hasher_rows": 5816, "bitwise_rows": 1160, diff --git a/crates/miden-standards/asm/standards/fee/mod.masm b/crates/miden-standards/asm/standards/fee/mod.masm index 3988ce8a8e..2075ad76cf 100644 --- a/crates/miden-standards/asm/standards/fee/mod.masm +++ b/crates/miden-standards/asm/standards/fee/mod.masm @@ -355,10 +355,12 @@ end #! signature::estimate_authentication_cycles); this procedure adds the payment cycles and the #! post-authentication kernel epilogue cycles internally. #! -#! Inputs: [num_extra_cycles] +#! Inputs: [FEE_ASSET_ID, num_extra_cycles] #! Outputs: [fee_amount] #! #! Where: +#! - FEE_ASSET_ID is the ID of the native fee asset (see tx::get_fee_asset_id), which funds the +#! sponsorship notes. #! - num_extra_cycles is the estimated number of cycles the caller spends after this call until #! the end of the authentication procedure. #! - fee_amount is the computed fee of the transaction, denominated in the native fee asset. @@ -370,8 +372,22 @@ end #! maximum cycle count. #! #! Invocation: exec -pub proc estimate_fee(num_extra_cycles: felt) -> felt - exec.tx::get_fee_asset_id exec.estimate_fee_for_asset +pub proc estimate_fee(fee_asset_id: AssetId, num_extra_cycles: felt) -> felt + # only the note counts are needed: the fee prices the notes, not the amounts they carry + exec.fees::estimate_network_note_sponsorships + # => [num_sponsorship_notes, num_output_notes, num_extra_cycles] + + movup.2 + # => [num_extra_cycles, num_sponsorship_notes, num_output_notes] + + exec.apply_cycle_margins + # => [num_estimated_extra_cycles] + + # no output notes are excluded from the fee computation + padw movup.4 + # => [num_estimated_extra_cycles, EXCLUDE_NOTES_COMMITMENT] + + exec.tx::compute_fee # => [fee_amount] end @@ -421,7 +437,7 @@ pub proc pay_fee(num_extra_cycles: felt, conversion_info: ConversionInfo) -> fel dupw movup.8 movdn.4 # => [FEE_ASSET_ID, num_extra_cycles, FEE_ASSET_ID, CONVERSION_INFO] - exec.estimate_fee_for_asset + exec.estimate_fee # => [fee_amount, FEE_ASSET_ID, CONVERSION_INFO] movdn.4 exec.fees::create_network_note_sponsorships @@ -466,41 +482,3 @@ pub proc pay_fee(num_extra_cycles: felt, conversion_info: ConversionInfo) -> fel # => [total_sponsored_fee_amount] end end - -# HELPER PROCEDURES -# ================================================================================================= - -#! Estimates the fee of the current transaction with the native fee asset ID already at hand. -#! -#! Backs `estimate_fee`, and lets `pay_fee` read the fee asset ID once for both the estimate and -#! the sponsorship payment. -#! -#! Inputs: [FEE_ASSET_ID, num_extra_cycles] -#! Outputs: [fee_amount] -#! -#! Where: -#! - FEE_ASSET_ID is the ID of the native fee asset, see `tx::get_fee_asset_id`. -#! - num_extra_cycles and fee_amount are as described in `estimate_fee`. -#! -#! Panics if: -#! - `fees::estimate_network_note_sponsorships` or `tx::compute_fee` panics. -#! -#! Invocation: exec -proc estimate_fee_for_asset(fee_asset_id: AssetId, num_extra_cycles: felt) -> felt - # only the note counts are needed: the fee prices the notes, not the amounts they carry - exec.fees::estimate_network_note_sponsorships - # => [num_sponsorship_notes, num_output_notes, num_extra_cycles] - - movup.2 - # => [num_extra_cycles, num_sponsorship_notes, num_output_notes] - - exec.apply_cycle_margins - # => [num_estimated_extra_cycles] - - # no output notes are excluded from the fee computation - padw movup.4 - # => [num_estimated_extra_cycles, EXCLUDE_NOTES_COMMITMENT] - - exec.tx::compute_fee - # => [fee_amount] -end diff --git a/crates/miden-standards/src/note/costs/table.rs b/crates/miden-standards/src/note/costs/table.rs index d784b7d3b2..f68bbcb4dc 100644 --- a/crates/miden-standards/src/note/costs/table.rs +++ b/crates/miden-standards/src/note/costs/table.rs @@ -12,8 +12,8 @@ pub const P2IDE_CONSUMPTION_CYCLES: u32 = 58032; /// Cycles of consuming a SWAP note: public payback 23593 (maximum), private payback 23090. pub const SWAP_CONSUMPTION_CYCLES: u32 = 23593; -/// Cycles of consuming a PSWAP note: full fill 26428, partial fill 30554 (maximum). -pub const PSWAP_CONSUMPTION_CYCLES: u32 = 30554; +/// Cycles of consuming a PSWAP note: full fill 26439, partial fill 30576 (maximum). +pub const PSWAP_CONSUMPTION_CYCLES: u32 = 30576; /// Cycles of consuming a MINT note: fungible faucet 35248, non-fungible faucet 38612 (maximum). pub const MINT_CONSUMPTION_CYCLES: u32 = 38612; @@ -22,34 +22,34 @@ pub const MINT_CONSUMPTION_CYCLES: u32 = 38612; pub const BURN_CONSUMPTION_CYCLES: u32 = 29595; /// Cycles of consuming a CONSTANT_FEE_POLICY_CONFIG note (single benchmarked path). -pub const CONSTANT_FEE_POLICY_CONFIG_CONSUMPTION_CYCLES: u32 = 20687; +pub const CONSTANT_FEE_POLICY_CONFIG_CONSUMPTION_CYCLES: u32 = 20819; /// Cycles of consuming a FAUCET_POLICY_CONFIG note (single benchmarked path). -pub const FAUCET_POLICY_CONFIG_CONSUMPTION_CYCLES: u32 = 27791; +pub const FAUCET_POLICY_CONFIG_CONSUMPTION_CYCLES: u32 = 27923; /// Cycles of consuming a FAUCET_METADATA_CONFIG note (single benchmarked path). -pub const FAUCET_METADATA_CONFIG_CONSUMPTION_CYCLES: u32 = 28323; +pub const FAUCET_METADATA_CONFIG_CONSUMPTION_CYCLES: u32 = 28455; /// Cycles of consuming a MIN_BURN_AMOUNT_CONFIG note (single benchmarked path). -pub const MIN_BURN_AMOUNT_CONFIG_CONSUMPTION_CYCLES: u32 = 26820; +pub const MIN_BURN_AMOUNT_CONFIG_CONSUMPTION_CYCLES: u32 = 26952; /// Cycles of consuming an ALLOWLIST_CONFIG note (single benchmarked path). -pub const ALLOWLIST_CONFIG_CONSUMPTION_CYCLES: u32 = 27566; +pub const ALLOWLIST_CONFIG_CONSUMPTION_CYCLES: u32 = 27698; /// Cycles of consuming a BLOCKLIST_CONFIG note (single benchmarked path). -pub const BLOCKLIST_CONFIG_CONSUMPTION_CYCLES: u32 = 27566; +pub const BLOCKLIST_CONFIG_CONSUMPTION_CYCLES: u32 = 27698; /// Cycles of consuming a PAUSE_CONFIG note (single benchmarked path). -pub const PAUSE_CONFIG_CONSUMPTION_CYCLES: u32 = 19298; +pub const PAUSE_CONFIG_CONSUMPTION_CYCLES: u32 = 19430; /// Cycles of consuming an OWNER_CONFIG note (single benchmarked path). -pub const OWNER_CONFIG_CONSUMPTION_CYCLES: u32 = 18879; +pub const OWNER_CONFIG_CONSUMPTION_CYCLES: u32 = 19011; /// Cycles of consuming an RBAC_CONFIG note (single benchmarked path). -pub const RBAC_CONFIG_CONSUMPTION_CYCLES: u32 = 22874; +pub const RBAC_CONFIG_CONSUMPTION_CYCLES: u32 = 23006; /// Cycles of consuming a NETWORK_ACCOUNT_CONFIG note (single benchmarked path). -pub const NETWORK_ACCOUNT_CONFIG_CONSUMPTION_CYCLES: u32 = 19779; +pub const NETWORK_ACCOUNT_CONFIG_CONSUMPTION_CYCLES: u32 = 19911; /// Cycles of consuming a FEE_SPONSORSHIP note (single benchmarked path). pub const FEE_SPONSORSHIP_CONSUMPTION_CYCLES: u32 = 22250; From 5e1037836148e4fc6c6cfb7f6843c6b613687283 Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Thu, 3 Sep 2026 10:45:47 +0000 Subject: [PATCH 04/12] docs(standards): simplify the estimate_fee note coverage doc Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01KkQzFtRjyDwQ7iVtRbsHsn --- crates/miden-standards/asm/standards/fee/mod.masm | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/miden-standards/asm/standards/fee/mod.masm b/crates/miden-standards/asm/standards/fee/mod.masm index 2075ad76cf..a286fbd827 100644 --- a/crates/miden-standards/asm/standards/fee/mod.masm +++ b/crates/miden-standards/asm/standards/fee/mod.masm @@ -346,9 +346,8 @@ end #! Estimates the fee of the current transaction. #! -#! The fee covers the notes the payment goes on to create: the TX_FEE note, and one -#! FEE_SPONSORSHIP note per network output note its target prices above zero (see -#! fees::create_network_note_sponsorships). +#! The fee covers the notes that will be created: the TX_FEE note, and one +#! FEE_SPONSORSHIP note per network output note. #! #! num_extra_cycles only needs to cover the number of cycles the caller spends after the fee flow #! returns (e.g. transaction summary creation, hashing and signature verification, see From 4e70a03575a25cdf7d441192cbe7efafecfac4d9 Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Thu, 3 Sep 2026 10:45:47 +0000 Subject: [PATCH 05/12] docs: drop the MAST-root clause from the fee split changelog entry Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01KkQzFtRjyDwQ7iVtRbsHsn --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f69f0a9b0..cc95881b55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ ### Changes -- [BREAKING] Split `fee::estimate_fee` out of `fee::pay_fee`, pricing the FEE_SPONSORSHIP notes via the new `fees::estimate_network_note_sponsorships` without creating them, and `fee::apply_cycle_margins` now also takes the output-note count; the MAST roots of every fee-paying component change ([#3784](https://github.com/0xMiden/protocol/pull/3784)). +- [BREAKING] Split `fee::estimate_fee` out of `fee::pay_fee`, pricing the FEE_SPONSORSHIP notes via the new `fees::estimate_network_note_sponsorships` without creating them, and `fee::apply_cycle_margins` now also takes the output-note count ([#3784](https://github.com/0xMiden/protocol/pull/3784)). - Added a check that the guardian public key is not one of the approver public keys ([#3764](https://github.com/0xMiden/protocol/pull/3764)). - [BREAKING] Incremented the MSRV to 1.98. - [BREAKING] Updated the Miden VM and crypto crate family to v0.30.0 and `midenc-hir-type` to v0.12.0. `LocalTransactionProver::new` now takes `miden_prover::Prover`, `CoreLibrary` exposes one merged package, and `TransactionVerifier::verify` now returns `VerificationOutcome` so callers can handle outstanding precompile work ([#3782](https://github.com/0xMiden/protocol/pull/3782)). From 62e225fcc2dc6cfc1edb9703faae7e0bb3443cec Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Thu, 3 Sep 2026 10:47:52 +0000 Subject: [PATCH 06/12] docs: name both new apply_cycle_margins inputs in the changelog The procedure gained the sponsorship-note count as well as the output-note count. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01KkQzFtRjyDwQ7iVtRbsHsn --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc95881b55..d2c9cb6cf3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ ### Changes -- [BREAKING] Split `fee::estimate_fee` out of `fee::pay_fee`, pricing the FEE_SPONSORSHIP notes via the new `fees::estimate_network_note_sponsorships` without creating them, and `fee::apply_cycle_margins` now also takes the output-note count ([#3784](https://github.com/0xMiden/protocol/pull/3784)). +- [BREAKING] Split `fee::estimate_fee` out of `fee::pay_fee`, pricing the FEE_SPONSORSHIP notes via the new `fees::estimate_network_note_sponsorships` without creating them, and `fee::apply_cycle_margins` now also takes the sponsorship-note and output-note counts ([#3784](https://github.com/0xMiden/protocol/pull/3784)). - Added a check that the guardian public key is not one of the approver public keys ([#3764](https://github.com/0xMiden/protocol/pull/3764)). - [BREAKING] Incremented the MSRV to 1.98. - [BREAKING] Updated the Miden VM and crypto crate family to v0.30.0 and `midenc-hir-type` to v0.12.0. `LocalTransactionProver::new` now takes `miden_prover::Prover`, `CoreLibrary` exposes one merged package, and `TransactionVerifier::verify` now returns `VerificationOutcome` so callers can handle outstanding precompile work ([#3782](https://github.com/0xMiden/protocol/pull/3782)). From a7d9c451c9c92f137cc03b90bccfb31570c91f75 Mon Sep 17 00:00:00 2001 From: Marti Date: Thu, 3 Sep 2026 14:15:19 +0200 Subject: [PATCH 07/12] Apply batched suggestions from code review Co-authored-by: Marti --- crates/miden-standards/asm/standards/fees/mod.masm | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/miden-standards/asm/standards/fees/mod.masm b/crates/miden-standards/asm/standards/fees/mod.masm index 183cca6ac2..f722f5954f 100644 --- a/crates/miden-standards/asm/standards/fees/mod.masm +++ b/crates/miden-standards/asm/standards/fees/mod.masm @@ -648,7 +648,7 @@ proc process_network_note_sponsorships(fee_asset_id: AssetId, create_notes: Bool # => [sponsored_fee_amount, note_idx, num_output_notes, FEE_ASSET_ID, # total_sponsored_fee_amount, num_sponsorship_notes, create_notes] - # a note priced to zero is sponsored by no note + # only increment `num_sponsorship_notes` if the amount is non-zero dup neq.0 movup.9 add movdn.8 # => [sponsored_fee_amount, note_idx, num_output_notes, FEE_ASSET_ID, # total_sponsored_fee_amount, num_sponsorship_notes, create_notes] @@ -678,14 +678,13 @@ proc process_network_note_sponsorships(fee_asset_id: AssetId, create_notes: Bool # => [total_sponsored_fee_amount, num_sponsorship_notes, num_output_notes] end -#! Prices the network output note at the given index and, when asked to, creates its +#! Prices the network output note at the given index and, optionally, creates its #! FEE_SPONSORSHIP note. #! #! Handles a single network note for `create_network_note_sponsorships` (create_notes = 1) and #! `estimate_network_note_sponsorships` (create_notes = 0); see those for the full behaviour. A #! network note the target account prices to zero needs no sponsorship note, so none is created for -#! it. When create_notes is 0 the note is only priced: the vault and the output notes are left -#! untouched. +#! it. #! #! Inputs: [note_idx, attachment_idx, EXPECTED_FEE_ASSET_ID, create_notes] #! Outputs: [sponsored_fee_amount] From faecf22e77d79bc1262091730551a0c1f953d513 Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Thu, 3 Sep 2026 12:44:09 +0000 Subject: [PATCH 08/12] refactor(standards): price each network note once in the fee flow The estimate pass records every network output note's price in a caller-provided table indexed by output note index, and the creation pass funds the sponsorship notes from that table instead of repeating the FPI call into each target's fee policy. `pay_fee` keeps the table in its locals; `estimate_fee` and both `fees` walks take its address. This removes the second pricing pass from the payment tail, so the cycle margins after `compute_fee` no longer stand in for foreign procedure calls, and network-note transactions shrink by about 3500 core rows. Cost tables regenerated. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01KkQzFtRjyDwQ7iVtRbsHsn --- bin/bench-transaction/bench-tx.json | 708 +++++++++--------- crates/miden-agglayer/src/costs/table.rs | 16 +- .../asm/standards/fee/mod.masm | 47 +- .../asm/standards/fees/mod.masm | 316 +++++--- .../miden-standards/src/note/costs/table.rs | 46 +- 5 files changed, 611 insertions(+), 522 deletions(-) diff --git a/bin/bench-transaction/bench-tx.json b/bin/bench-transaction/bench-tx.json index 60331586e6..36dbdf0201 100644 --- a/bin/bench-transaction/bench-tx.json +++ b/bin/bench-transaction/bench-tx.json @@ -3,22 +3,22 @@ "prologue": 5329, "notes_processing": 2173, "note_execution": { - "0xac68d3992d557007868148823bb2aebcb3623c6aede5880a01f816d93e872a86": 2131 + "0x37a0c87d66130e20a87710fb5be10a75ccc1ff1de4c6b14c77787ce7e0366aa3": 2131 }, "tx_script_processing": 41, "epilogue": { - "total": 73822, - "auth_procedure": 72685 + "total": 73858, + "auth_procedure": 72721 }, "trace": { - "core_rows": 81409, - "chiplets_rows": 11491, - "poseidon2_permutation_rows": 55328, - "range_rows": 20703, + "core_rows": 81445, + "chiplets_rows": 11504, + "poseidon2_permutation_rows": 55392, + "range_rows": 20683, "chiplets_shape": { - "hasher_rows": 8648, + "hasher_rows": 8656, "bitwise_rows": 656, - "memory_rows": 2185, + "memory_rows": 2190, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -28,22 +28,22 @@ "prologue": 5329, "notes_processing": 2173, "note_execution": { - "0x10fda265c86244d95a08747945d8457fd180fd02760ad0f7f26df0c447658bbc": 2131 + "0xa4506f26c4c717bd0a860f6955a596f4872a5f398e815de703b5d1e52aaa24dc": 2131 }, "tx_script_processing": 41, "epilogue": { - "total": 6059, - "auth_procedure": 4922 + "total": 6095, + "auth_procedure": 4958 }, "trace": { - "core_rows": 13646, - "chiplets_rows": 5859, - "poseidon2_permutation_rows": 19824, - "range_rows": 1989, + "core_rows": 13682, + "chiplets_rows": 5872, + "poseidon2_permutation_rows": 19888, + "range_rows": 1991, "chiplets_shape": { - "hasher_rows": 4216, + "hasher_rows": 4224, "bitwise_rows": 848, - "memory_rows": 793, + "memory_rows": 798, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -58,18 +58,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 73750, - "auth_procedure": 72649 + "total": 73786, + "auth_procedure": 72685 }, "trace": { - "core_rows": 84009, - "chiplets_rows": 13390, - "poseidon2_permutation_rows": 55584, - "range_rows": 20515, + "core_rows": 84045, + "chiplets_rows": 13403, + "poseidon2_permutation_rows": 55648, + "range_rows": 20541, "chiplets_shape": { - "hasher_rows": 10064, + "hasher_rows": 10072, "bitwise_rows": 1032, - "memory_rows": 2292, + "memory_rows": 2297, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -84,18 +84,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 5987, - "auth_procedure": 4886 + "total": 6023, + "auth_procedure": 4922 }, "trace": { - "core_rows": 16246, - "chiplets_rows": 7758, - "poseidon2_permutation_rows": 20080, - "range_rows": 1411, + "core_rows": 16282, + "chiplets_rows": 7771, + "poseidon2_permutation_rows": 20144, + "range_rows": 1397, "chiplets_shape": { - "hasher_rows": 5632, + "hasher_rows": 5640, "bitwise_rows": 1224, - "memory_rows": 900, + "memory_rows": 905, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -107,18 +107,18 @@ "note_execution": {}, "tx_script_processing": 1883, "epilogue": { - "total": 75557, - "auth_procedure": 73471 + "total": 75593, + "auth_procedure": 73507 }, "trace": { - "core_rows": 79599, - "chiplets_rows": 10903, - "poseidon2_permutation_rows": 53216, - "range_rows": 20443, + "core_rows": 79635, + "chiplets_rows": 10916, + "poseidon2_permutation_rows": 53280, + "range_rows": 20499, "chiplets_shape": { - "hasher_rows": 8232, + "hasher_rows": 8240, "bitwise_rows": 616, - "memory_rows": 2053, + "memory_rows": 2058, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -130,18 +130,18 @@ "note_execution": {}, "tx_script_processing": 1883, "epilogue": { - "total": 7794, - "auth_procedure": 5708 + "total": 7830, + "auth_procedure": 5744 }, "trace": { - "core_rows": 11836, - "chiplets_rows": 5271, - "poseidon2_permutation_rows": 17664, - "range_rows": 1209, + "core_rows": 11872, + "chiplets_rows": 5284, + "poseidon2_permutation_rows": 17728, + "range_rows": 1223, "chiplets_shape": { - "hasher_rows": 3800, + "hasher_rows": 3808, "bitwise_rows": 808, - "memory_rows": 661, + "memory_rows": 666, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -155,18 +155,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 21297, - "auth_procedure": 16028 + "total": 17828, + "auth_procedure": 12559 }, "trace": { - "core_rows": 54657, - "chiplets_rows": 21842, - "poseidon2_permutation_rows": 41808, - "range_rows": 3639, + "core_rows": 51188, + "chiplets_rows": 20591, + "poseidon2_permutation_rows": 42016, + "range_rows": 3553, "chiplets_shape": { - "hasher_rows": 14424, - "bitwise_rows": 2904, - "memory_rows": 4512, + "hasher_rows": 13432, + "bitwise_rows": 2848, + "memory_rows": 4309, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -180,18 +180,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 21297, - "auth_procedure": 16028 + "total": 17828, + "auth_procedure": 12559 }, "trace": { - "core_rows": 64627, - "chiplets_rows": 24400, - "poseidon2_permutation_rows": 43952, - "range_rows": 3809, + "core_rows": 61158, + "chiplets_rows": 23149, + "poseidon2_permutation_rows": 44160, + "range_rows": 3753, "chiplets_shape": { - "hasher_rows": 15784, - "bitwise_rows": 3160, - "memory_rows": 5454, + "hasher_rows": 14792, + "bitwise_rows": 3104, + "memory_rows": 5251, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -205,18 +205,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 29753, - "auth_procedure": 14780 + "total": 26284, + "auth_procedure": 11311 }, "trace": { - "core_rows": 155213, - "chiplets_rows": 72879, - "poseidon2_permutation_rows": 114400, - "range_rows": 6037, + "core_rows": 151744, + "chiplets_rows": 71628, + "poseidon2_permutation_rows": 114624, + "range_rows": 5943, "chiplets_shape": { - "hasher_rows": 58464, - "bitwise_rows": 3720, - "memory_rows": 10693, + "hasher_rows": 57472, + "bitwise_rows": 3664, + "memory_rows": 10490, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -230,18 +230,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 29465, - "auth_procedure": 14780 + "total": 25996, + "auth_procedure": 11311 }, "trace": { - "core_rows": 153234, - "chiplets_rows": 71900, - "poseidon2_permutation_rows": 115792, - "range_rows": 6009, + "core_rows": 149765, + "chiplets_rows": 70657, + "poseidon2_permutation_rows": 116016, + "range_rows": 5957, "chiplets_shape": { - "hasher_rows": 57608, - "bitwise_rows": 3720, - "memory_rows": 10570, + "hasher_rows": 56624, + "bitwise_rows": 3664, + "memory_rows": 10367, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -255,18 +255,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 20825, - "auth_procedure": 14780 + "total": 17356, + "auth_procedure": 11311 }, "trace": { - "core_rows": 90425, - "chiplets_rows": 42042, - "poseidon2_permutation_rows": 52224, - "range_rows": 5025, + "core_rows": 86956, + "chiplets_rows": 40799, + "poseidon2_permutation_rows": 52448, + "range_rows": 4959, "chiplets_shape": { - "hasher_rows": 31440, - "bitwise_rows": 3720, - "memory_rows": 6880, + "hasher_rows": 30456, + "bitwise_rows": 3664, + "memory_rows": 6677, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -280,18 +280,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 13013, - "auth_procedure": 9750 + "total": 13049, + "auth_procedure": 9786 }, "trace": { - "core_rows": 19400, - "chiplets_rows": 8784, - "poseidon2_permutation_rows": 26448, - "range_rows": 1619, + "core_rows": 19436, + "chiplets_rows": 8797, + "poseidon2_permutation_rows": 26528, + "range_rows": 1643, "chiplets_shape": { - "hasher_rows": 6720, + "hasher_rows": 6728, "bitwise_rows": 920, - "memory_rows": 1142, + "memory_rows": 1147, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -305,18 +305,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 15492, - "auth_procedure": 10069 + "total": 15528, + "auth_procedure": 10105 }, "trace": { - "core_rows": 57968, - "chiplets_rows": 33240, - "poseidon2_permutation_rows": 42944, - "range_rows": 3953, + "core_rows": 58004, + "chiplets_rows": 33253, + "poseidon2_permutation_rows": 43024, + "range_rows": 3961, "chiplets_shape": { - "hasher_rows": 25176, + "hasher_rows": 25184, "bitwise_rows": 5120, - "memory_rows": 2942, + "memory_rows": 2947, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -330,18 +330,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 13013, - "auth_procedure": 9750 + "total": 13049, + "auth_procedure": 9786 }, "trace": { - "core_rows": 19508, - "chiplets_rows": 8812, - "poseidon2_permutation_rows": 26544, - "range_rows": 1595, + "core_rows": 19544, + "chiplets_rows": 8825, + "poseidon2_permutation_rows": 26624, + "range_rows": 1591, "chiplets_shape": { - "hasher_rows": 6744, + "hasher_rows": 6752, "bitwise_rows": 920, - "memory_rows": 1146, + "memory_rows": 1151, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -355,18 +355,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 15492, - "auth_procedure": 10069 + "total": 15528, + "auth_procedure": 10105 }, "trace": { - "core_rows": 58076, - "chiplets_rows": 33268, - "poseidon2_permutation_rows": 43040, - "range_rows": 3965, + "core_rows": 58112, + "chiplets_rows": 33281, + "poseidon2_permutation_rows": 43120, + "range_rows": 3979, "chiplets_shape": { - "hasher_rows": 25200, + "hasher_rows": 25208, "bitwise_rows": 5120, - "memory_rows": 2946, + "memory_rows": 2951, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -380,18 +380,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 13013, - "auth_procedure": 9750 + "total": 13049, + "auth_procedure": 9786 }, "trace": { - "core_rows": 19663, - "chiplets_rows": 8843, - "poseidon2_permutation_rows": 26736, - "range_rows": 1585, + "core_rows": 19699, + "chiplets_rows": 8856, + "poseidon2_permutation_rows": 26816, + "range_rows": 1601, "chiplets_shape": { - "hasher_rows": 6768, + "hasher_rows": 6776, "bitwise_rows": 920, - "memory_rows": 1153, + "memory_rows": 1158, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -405,18 +405,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 14877, - "auth_procedure": 10473 + "total": 14913, + "auth_procedure": 10509 }, "trace": { - "core_rows": 23637, - "chiplets_rows": 10934, - "poseidon2_permutation_rows": 28576, - "range_rows": 1867, + "core_rows": 23673, + "chiplets_rows": 10947, + "poseidon2_permutation_rows": 28656, + "range_rows": 1913, "chiplets_shape": { - "hasher_rows": 8344, + "hasher_rows": 8352, "bitwise_rows": 1288, - "memory_rows": 1300, + "memory_rows": 1305, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -430,18 +430,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 14877, - "auth_procedure": 10473 + "total": 14913, + "auth_procedure": 10509 }, "trace": { - "core_rows": 23134, - "chiplets_rows": 10813, - "poseidon2_permutation_rows": 28256, - "range_rows": 1873, + "core_rows": 23170, + "chiplets_rows": 10826, + "poseidon2_permutation_rows": 28336, + "range_rows": 1897, "chiplets_shape": { - "hasher_rows": 8240, + "hasher_rows": 8248, "bitwise_rows": 1288, - "memory_rows": 1283, + "memory_rows": 1288, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -455,18 +455,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 14917, - "auth_procedure": 10473 + "total": 14953, + "auth_procedure": 10509 }, "trace": { - "core_rows": 26483, - "chiplets_rows": 11779, - "poseidon2_permutation_rows": 31104, - "range_rows": 2029, + "core_rows": 26519, + "chiplets_rows": 11792, + "poseidon2_permutation_rows": 31184, + "range_rows": 2021, "chiplets_shape": { - "hasher_rows": 8872, + "hasher_rows": 8880, "bitwise_rows": 1416, - "memory_rows": 1489, + "memory_rows": 1494, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -480,18 +480,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 16538, - "auth_procedure": 10881 + "total": 16574, + "auth_procedure": 10917 }, "trace": { - "core_rows": 30620, - "chiplets_rows": 13635, - "poseidon2_permutation_rows": 32896, - "range_rows": 2087, + "core_rows": 30656, + "chiplets_rows": 13648, + "poseidon2_permutation_rows": 32976, + "range_rows": 2113, "chiplets_shape": { - "hasher_rows": 10136, + "hasher_rows": 10144, "bitwise_rows": 1840, - "memory_rows": 1657, + "memory_rows": 1662, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -505,18 +505,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 19948, - "auth_procedure": 10228 + "total": 19984, + "auth_procedure": 10264 }, "trace": { - "core_rows": 35292, - "chiplets_rows": 13868, - "poseidon2_permutation_rows": 29040, - "range_rows": 2981, + "core_rows": 35328, + "chiplets_rows": 13881, + "poseidon2_permutation_rows": 29120, + "range_rows": 3001, "chiplets_shape": { - "hasher_rows": 10416, + "hasher_rows": 10424, "bitwise_rows": 1288, - "memory_rows": 2162, + "memory_rows": 2167, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -530,18 +530,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 20218, - "auth_procedure": 10237 + "total": 20254, + "auth_procedure": 10273 }, "trace": { - "core_rows": 38656, - "chiplets_rows": 15018, - "poseidon2_permutation_rows": 30880, - "range_rows": 3157, + "core_rows": 38692, + "chiplets_rows": 15031, + "poseidon2_permutation_rows": 30960, + "range_rows": 3145, "chiplets_shape": { - "hasher_rows": 11528, + "hasher_rows": 11536, "bitwise_rows": 1192, - "memory_rows": 2296, + "memory_rows": 2301, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -555,18 +555,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 18485, - "auth_procedure": 9824 + "total": 18521, + "auth_procedure": 9860 }, "trace": { - "core_rows": 29639, - "chiplets_rows": 12066, - "poseidon2_permutation_rows": 28208, - "range_rows": 2805, + "core_rows": 29675, + "chiplets_rows": 12079, + "poseidon2_permutation_rows": 28288, + "range_rows": 2813, "chiplets_shape": { - "hasher_rows": 9160, + "hasher_rows": 9168, "bitwise_rows": 976, - "memory_rows": 1928, + "memory_rows": 1933, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -580,18 +580,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 18332, - "auth_procedure": 9815 + "total": 18368, + "auth_procedure": 9851 }, "trace": { - "core_rows": 27967, - "chiplets_rows": 10704, - "poseidon2_permutation_rows": 26880, - "range_rows": 2651, + "core_rows": 28003, + "chiplets_rows": 10717, + "poseidon2_permutation_rows": 26960, + "range_rows": 2619, "chiplets_shape": { - "hasher_rows": 8248, + "hasher_rows": 8256, "bitwise_rows": 648, - "memory_rows": 1806, + "memory_rows": 1811, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -605,18 +605,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 16921, - "auth_procedure": 9734 + "total": 16957, + "auth_procedure": 9770 }, "trace": { - "core_rows": 28499, - "chiplets_rows": 10805, - "poseidon2_permutation_rows": 26176, - "range_rows": 2471, + "core_rows": 28535, + "chiplets_rows": 10818, + "poseidon2_permutation_rows": 26256, + "range_rows": 2497, "chiplets_shape": { - "hasher_rows": 8272, + "hasher_rows": 8280, "bitwise_rows": 656, - "memory_rows": 1875, + "memory_rows": 1880, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -630,18 +630,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 18485, - "auth_procedure": 9824 + "total": 18521, + "auth_procedure": 9860 }, "trace": { - "core_rows": 26996, - "chiplets_rows": 10314, - "poseidon2_permutation_rows": 25472, - "range_rows": 2635, + "core_rows": 27032, + "chiplets_rows": 10327, + "poseidon2_permutation_rows": 25552, + "range_rows": 2591, "chiplets_shape": { - "hasher_rows": 7904, + "hasher_rows": 7912, "bitwise_rows": 648, - "memory_rows": 1760, + "memory_rows": 1765, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -655,18 +655,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 18664, - "auth_procedure": 9824 + "total": 18700, + "auth_procedure": 9860 }, "trace": { - "core_rows": 27742, - "chiplets_rows": 10940, - "poseidon2_permutation_rows": 28016, - "range_rows": 2601, + "core_rows": 27778, + "chiplets_rows": 10953, + "poseidon2_permutation_rows": 28096, + "range_rows": 2589, "chiplets_shape": { - "hasher_rows": 8456, + "hasher_rows": 8464, "bitwise_rows": 648, - "memory_rows": 1834, + "memory_rows": 1839, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -680,18 +680,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 18664, - "auth_procedure": 9824 + "total": 18700, + "auth_procedure": 9860 }, "trace": { - "core_rows": 27742, - "chiplets_rows": 10940, - "poseidon2_permutation_rows": 27840, + "core_rows": 27778, + "chiplets_rows": 10953, + "poseidon2_permutation_rows": 27920, "range_rows": 2621, "chiplets_shape": { - "hasher_rows": 8456, + "hasher_rows": 8464, "bitwise_rows": 648, - "memory_rows": 1834, + "memory_rows": 1839, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -705,18 +705,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 13078, - "auth_procedure": 9509 + "total": 13114, + "auth_procedure": 9545 }, "trace": { - "core_rows": 19474, - "chiplets_rows": 7990, - "poseidon2_permutation_rows": 24272, - "range_rows": 1675, + "core_rows": 19510, + "chiplets_rows": 8003, + "poseidon2_permutation_rows": 24352, + "range_rows": 1681, "chiplets_shape": { - "hasher_rows": 6144, + "hasher_rows": 6152, "bitwise_rows": 648, - "memory_rows": 1196, + "memory_rows": 1201, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -730,18 +730,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 12772, - "auth_procedure": 9491 + "total": 12808, + "auth_procedure": 9527 }, "trace": { - "core_rows": 19055, - "chiplets_rows": 7870, - "poseidon2_permutation_rows": 24128, - "range_rows": 1613, + "core_rows": 19091, + "chiplets_rows": 7883, + "poseidon2_permutation_rows": 24208, + "range_rows": 1641, "chiplets_shape": { - "hasher_rows": 6040, + "hasher_rows": 6048, "bitwise_rows": 664, - "memory_rows": 1164, + "memory_rows": 1169, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -755,18 +755,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 13620, - "auth_procedure": 9518 + "total": 13656, + "auth_procedure": 9554 }, "trace": { - "core_rows": 23050, - "chiplets_rows": 10352, - "poseidon2_permutation_rows": 30720, - "range_rows": 1911, + "core_rows": 23086, + "chiplets_rows": 10365, + "poseidon2_permutation_rows": 30800, + "range_rows": 1949, "chiplets_shape": { - "hasher_rows": 8208, + "hasher_rows": 8216, "bitwise_rows": 664, - "memory_rows": 1478, + "memory_rows": 1483, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -780,18 +780,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 13091, - "auth_procedure": 9500 + "total": 13127, + "auth_procedure": 9536 }, "trace": { - "core_rows": 19955, - "chiplets_rows": 8526, - "poseidon2_permutation_rows": 26816, - "range_rows": 1651, + "core_rows": 19991, + "chiplets_rows": 8539, + "poseidon2_permutation_rows": 26896, + "range_rows": 1687, "chiplets_shape": { - "hasher_rows": 6632, + "hasher_rows": 6640, "bitwise_rows": 648, - "memory_rows": 1244, + "memory_rows": 1249, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -805,18 +805,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 13244, - "auth_procedure": 9509 + "total": 13280, + "auth_procedure": 9545 }, "trace": { - "core_rows": 20863, - "chiplets_rows": 8820, - "poseidon2_permutation_rows": 26944, - "range_rows": 1733, + "core_rows": 20899, + "chiplets_rows": 8833, + "poseidon2_permutation_rows": 27024, + "range_rows": 1767, "chiplets_shape": { - "hasher_rows": 6848, + "hasher_rows": 6856, "bitwise_rows": 648, - "memory_rows": 1322, + "memory_rows": 1327, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -831,18 +831,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 15687, - "auth_procedure": 12568 + "total": 15723, + "auth_procedure": 12604 }, "trace": { - "core_rows": 22294, - "chiplets_rows": 9907, - "poseidon2_permutation_rows": 26656, - "range_rows": 1677, + "core_rows": 22330, + "chiplets_rows": 9920, + "poseidon2_permutation_rows": 26736, + "range_rows": 1693, "chiplets_shape": { - "hasher_rows": 7632, + "hasher_rows": 7640, "bitwise_rows": 1008, - "memory_rows": 1265, + "memory_rows": 1270, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -856,18 +856,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 10358, - "auth_procedure": 8096 + "total": 10394, + "auth_procedure": 8132 }, "trace": { - "core_rows": 17323, - "chiplets_rows": 7855, - "poseidon2_permutation_rows": 23696, - "range_rows": 1547, + "core_rows": 17359, + "chiplets_rows": 7868, + "poseidon2_permutation_rows": 23776, + "range_rows": 1535, "chiplets_shape": { - "hasher_rows": 5816, + "hasher_rows": 5824, "bitwise_rows": 1160, - "memory_rows": 877, + "memory_rows": 882, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -881,18 +881,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 25391, - "auth_procedure": 18717 + "total": 21922, + "auth_procedure": 15248 }, "trace": { - "core_rows": 58751, - "chiplets_rows": 23984, - "poseidon2_permutation_rows": 47104, - "range_rows": 3801, + "core_rows": 55282, + "chiplets_rows": 22733, + "poseidon2_permutation_rows": 47312, + "range_rows": 3741, "chiplets_shape": { - "hasher_rows": 16064, - "bitwise_rows": 3256, - "memory_rows": 4662, + "hasher_rows": 15072, + "bitwise_rows": 3200, + "memory_rows": 4459, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -906,18 +906,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 25391, - "auth_procedure": 18717 + "total": 21922, + "auth_procedure": 15248 }, "trace": { - "core_rows": 68721, - "chiplets_rows": 26542, - "poseidon2_permutation_rows": 49248, - "range_rows": 3995, + "core_rows": 65252, + "chiplets_rows": 25291, + "poseidon2_permutation_rows": 49456, + "range_rows": 3957, "chiplets_shape": { - "hasher_rows": 17424, - "bitwise_rows": 3512, - "memory_rows": 5604, + "hasher_rows": 16432, + "bitwise_rows": 3456, + "memory_rows": 5401, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -931,18 +931,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 33847, - "auth_procedure": 17469 + "total": 30378, + "auth_procedure": 14000 }, "trace": { - "core_rows": 159307, - "chiplets_rows": 75021, - "poseidon2_permutation_rows": 118528, - "range_rows": 6213, + "core_rows": 155838, + "chiplets_rows": 73770, + "poseidon2_permutation_rows": 118752, + "range_rows": 6097, "chiplets_shape": { - "hasher_rows": 60104, - "bitwise_rows": 4072, - "memory_rows": 10843, + "hasher_rows": 59112, + "bitwise_rows": 4016, + "memory_rows": 10640, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -956,18 +956,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 24919, - "auth_procedure": 17469 + "total": 21450, + "auth_procedure": 14000 }, "trace": { - "core_rows": 94519, - "chiplets_rows": 44184, - "poseidon2_permutation_rows": 56336, - "range_rows": 5101, + "core_rows": 91050, + "chiplets_rows": 42941, + "poseidon2_permutation_rows": 56560, + "range_rows": 5049, "chiplets_shape": { - "hasher_rows": 33080, - "bitwise_rows": 4072, - "memory_rows": 7030, + "hasher_rows": 32096, + "bitwise_rows": 4016, + "memory_rows": 6827, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -981,18 +981,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 16762, - "auth_procedure": 9662 + "total": 16798, + "auth_procedure": 9698 }, "trace": { - "core_rows": 35360, - "chiplets_rows": 15643, - "poseidon2_permutation_rows": 38096, - "range_rows": 2725, + "core_rows": 35396, + "chiplets_rows": 15656, + "poseidon2_permutation_rows": 38160, + "range_rows": 2739, "chiplets_shape": { - "hasher_rows": 12536, + "hasher_rows": 12544, "bitwise_rows": 696, - "memory_rows": 2409, + "memory_rows": 2414, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -1006,18 +1006,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 16762, - "auth_procedure": 9662 + "total": 16798, + "auth_procedure": 9698 }, "trace": { - "core_rows": 34413, - "chiplets_rows": 15298, - "poseidon2_permutation_rows": 36688, - "range_rows": 2611, + "core_rows": 34449, + "chiplets_rows": 15311, + "poseidon2_permutation_rows": 36752, + "range_rows": 2643, "chiplets_shape": { - "hasher_rows": 12352, + "hasher_rows": 12360, "bitwise_rows": 688, - "memory_rows": 2256, + "memory_rows": 2261, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -1031,18 +1031,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 15962, - "auth_procedure": 9662 + "total": 15998, + "auth_procedure": 9698 }, "trace": { - "core_rows": 25322, - "chiplets_rows": 10474, - "poseidon2_permutation_rows": 30240, - "range_rows": 2297, + "core_rows": 25358, + "chiplets_rows": 10487, + "poseidon2_permutation_rows": 30320, + "range_rows": 2281, "chiplets_shape": { - "hasher_rows": 8144, + "hasher_rows": 8152, "bitwise_rows": 640, - "memory_rows": 1688, + "memory_rows": 1693, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -1056,18 +1056,18 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 15998, - "auth_procedure": 9662 + "total": 16034, + "auth_procedure": 9698 }, "trace": { - "core_rows": 26449, - "chiplets_rows": 10782, - "poseidon2_permutation_rows": 30704, - "range_rows": 2293, + "core_rows": 26485, + "chiplets_rows": 10795, + "poseidon2_permutation_rows": 30784, + "range_rows": 2323, "chiplets_shape": { - "hasher_rows": 8368, + "hasher_rows": 8376, "bitwise_rows": 640, - "memory_rows": 1772, + "memory_rows": 1777, "kernel_rom_rows": 1, "ace_rows": 0 } diff --git a/crates/miden-agglayer/src/costs/table.rs b/crates/miden-agglayer/src/costs/table.rs index e01686e6b9..62f12a6b30 100644 --- a/crates/miden-agglayer/src/costs/table.rs +++ b/crates/miden-agglayer/src/costs/table.rs @@ -2,20 +2,20 @@ // Values are maxima across the benchmarked paths; see `miden_standards::note::costs` for the // caveats on what they do and do not cover. -/// Cycles of consuming a CLAIM note: L1 origin 58707, L2 origin 68677 (maximum). -pub const CLAIM_CONSUMPTION_CYCLES: u32 = 68677; +/// Cycles of consuming a CLAIM note: L1 origin 55238, L2 origin 65208 (maximum). +pub const CLAIM_CONSUMPTION_CYCLES: u32 = 65208; -/// Cycles of consuming a B2AGG note: empty frontier 159263 (maximum), 2^31-1 leaves 94475. -pub const B2AGG_CONSUMPTION_CYCLES: u32 = 159263; +/// Cycles of consuming a B2AGG note: empty frontier 155794 (maximum), 2^31-1 leaves 91006. +pub const B2AGG_CONSUMPTION_CYCLES: u32 = 155794; /// Cycles of consuming a CONFIG_AGG_BRIDGE note (single benchmarked path). -pub const CONFIG_AGG_BRIDGE_CONSUMPTION_CYCLES: u32 = 35316; +pub const CONFIG_AGG_BRIDGE_CONSUMPTION_CYCLES: u32 = 35352; /// Cycles of consuming a DEREGISTER_AGG_FAUCET note (single benchmarked path). -pub const DEREGISTER_AGG_FAUCET_CONSUMPTION_CYCLES: u32 = 34369; +pub const DEREGISTER_AGG_FAUCET_CONSUMPTION_CYCLES: u32 = 34405; /// Cycles of consuming an UPDATE_GER note (single benchmarked path). -pub const UPDATE_GER_CONSUMPTION_CYCLES: u32 = 25278; +pub const UPDATE_GER_CONSUMPTION_CYCLES: u32 = 25314; /// Cycles of consuming a REMOVE_GER note (single benchmarked path). -pub const REMOVE_GER_CONSUMPTION_CYCLES: u32 = 26405; +pub const REMOVE_GER_CONSUMPTION_CYCLES: u32 = 26441; diff --git a/crates/miden-standards/asm/standards/fee/mod.masm b/crates/miden-standards/asm/standards/fee/mod.masm index a286fbd827..57bea0d6cc 100644 --- a/crates/miden-standards/asm/standards/fee/mod.masm +++ b/crates/miden-standards/asm/standards/fee/mod.masm @@ -33,16 +33,22 @@ pub type ConversionInfo = word const PAY_FEE_CYCLES = 8192 # Estimated upper bounds on the number of cycles the sponsorship payment spends after the -# compute_fee call returns: a walk over every output note looking for network notes, then pricing -# and funding a FEE_SPONSORSHIP note for each one found, which covers the foreign procedure call -# into the target's fee policy, the note creation and the vault withdrawal. +# compute_fee call returns: a walk over every output note looking for network notes, then funding +# a FEE_SPONSORSHIP note for each one priced above zero, which covers reading the target +# attachment, the note creation and the vault withdrawal. The pricing itself runs during the +# estimate, before compute_fee, and is measured by the kernel. # # NOT covered by a regression test: zeroing both leaves the suite green, because the sponsoring # case lands exactly on an ilog2 boundary either way. They are margins over a measurement, so a -# test would have to assert cycle counts rather than behaviour. Re-measure if the payment changes. +# test would have to assert cycle counts rather than behaviour. SPONSORSHIP_NOTE_CYCLES was sized +# while the payment still repeated the pricing call, so it is generous. const SPONSORSHIP_WALK_PER_OUTPUT_NOTE_CYCLES = 512 const SPONSORSHIP_NOTE_CYCLES = 16384 +# Local memory offset at which `pay_fee` keeps the sponsorship price table, sized for +# fees::SPONSORSHIP_PRICE_TABLE_NUM_ENTRIES elements. +const SPONSORSHIP_PRICE_TABLE_LOC = 0 + # Estimated upper bounds on the number of kernel epilogue cycles spent after the auth procedure # returns: output vault build, output notes commitment, account patch and final account # commitments. The cost scales with the number of output notes (measured ~330 cycles per @@ -354,12 +360,14 @@ end #! signature::estimate_authentication_cycles); this procedure adds the payment cycles and the #! post-authentication kernel epilogue cycles internally. #! -#! Inputs: [FEE_ASSET_ID, num_extra_cycles] +#! Inputs: [FEE_ASSET_ID, price_table_ptr, num_extra_cycles] #! Outputs: [fee_amount] #! #! Where: #! - FEE_ASSET_ID is the ID of the native fee asset (see tx::get_fee_asset_id), which funds the #! sponsorship notes. +#! - price_table_ptr is the address of the sponsorship price table the estimate fills and the +#! payment reads back (see fees::estimate_network_note_sponsorships). #! - num_extra_cycles is the estimated number of cycles the caller spends after this call until #! the end of the authentication procedure. #! - fee_amount is the computed fee of the transaction, denominated in the native fee asset. @@ -371,7 +379,7 @@ end #! maximum cycle count. #! #! Invocation: exec -pub proc estimate_fee(fee_asset_id: AssetId, num_extra_cycles: felt) -> felt +pub proc estimate_fee(fee_asset_id: AssetId, price_table_ptr: u32, num_extra_cycles: felt) -> felt # only the note counts are needed: the fee prices the notes, not the amounts they carry exec.fees::estimate_network_note_sponsorships # => [num_sponsorship_notes, num_output_notes, num_extra_cycles] @@ -401,10 +409,11 @@ end #! and obtain the conversion info via native_conversion_info, which needs no commitment since #! it is read from the reference block. #! -#! The fee is computed by estimate_fee. The payment then settles both of the transaction's fee -#! obligations, in order: the sponsorship notes of its network output notes, then the TX_FEE note -#! carrying ceil(fee_amount * rate_num / rate_den) of the asset issued by the faucet in the -#! conversion info. +#! The fee is computed by estimate_fee, which prices the network output notes once and records the +#! prices in this procedure's local memory. The payment then settles both of the transaction's fee +#! obligations, in order: the sponsorship notes of its network output notes, funded from those +#! prices, then the TX_FEE note carrying ceil(fee_amount * rate_num / rate_den) of the asset issued +#! by the faucet in the conversion info. #! #! If the computed fee is zero (e.g. the reference block's verification base fee is zero), no #! fee note is created and no conversion info is required (the empty word is accepted). The @@ -428,18 +437,28 @@ end #! - the maximum number of output notes is exceeded. #! #! Invocation: exec +@locals(1024) pub proc pay_fee(num_extra_cycles: felt, conversion_info: ConversionInfo) -> felt + # the estimate fills the price table and the payment reads it back + locaddr.SPONSORSHIP_PRICE_TABLE_LOC dup + # => [price_table_ptr, price_table_ptr, num_extra_cycles, CONVERSION_INFO] + # the native fee asset funds the sponsorship notes, and is read once for both passes exec.tx::get_fee_asset_id - # => [FEE_ASSET_ID, num_extra_cycles, CONVERSION_INFO] + # => [FEE_ASSET_ID, price_table_ptr, price_table_ptr, num_extra_cycles, CONVERSION_INFO] dupw movup.8 movdn.4 - # => [FEE_ASSET_ID, num_extra_cycles, FEE_ASSET_ID, CONVERSION_INFO] + # => [FEE_ASSET_ID, price_table_ptr, FEE_ASSET_ID, price_table_ptr, num_extra_cycles, + # CONVERSION_INFO] + + movup.10 movdn.5 + # => [FEE_ASSET_ID, price_table_ptr, num_extra_cycles, FEE_ASSET_ID, price_table_ptr, + # CONVERSION_INFO] exec.estimate_fee - # => [fee_amount, FEE_ASSET_ID, CONVERSION_INFO] + # => [fee_amount, FEE_ASSET_ID, price_table_ptr, CONVERSION_INFO] - movdn.4 exec.fees::create_network_note_sponsorships + movdn.5 exec.fees::create_network_note_sponsorships # => [total_sponsored_fee_amount, fee_amount, CONVERSION_INFO] movdn.5 diff --git a/crates/miden-standards/asm/standards/fees/mod.masm b/crates/miden-standards/asm/standards/fees/mod.masm index f722f5954f..d754f04bb8 100644 --- a/crates/miden-standards/asm/standards/fees/mod.masm +++ b/crates/miden-standards/asm/standards/fees/mod.masm @@ -6,11 +6,13 @@ # FEE_SPONSORSHIP notes to the account vault, pricing each feature note through the active fee policy # owned by the `fee_manager` module. # -# `create_network_note_sponsorships` walks the transaction's output notes and, for every note targeted at a -# network account, creates a paired FEE_SPONSORSHIP note funded with the fee that account charges. +# `estimate_network_note_sponsorships` walks the transaction's output notes and, for every note +# targeted at a network account, asks that account's fee policy what the note costs, recording the +# price in a caller-provided table so a caller can charge for the FEE_SPONSORSHIP notes before it +# pays for them. # -# `estimate_network_note_sponsorships` walks the same notes and prices them the same way, but creates -# nothing, so a caller can charge for those notes before it pays for them. +# `create_network_note_sponsorships` walks the same notes and creates the paired FEE_SPONSORSHIP note +# for every note the table prices above zero, funded from the native account's vault. use miden::core::word use miden::core::crypto::hashes::poseidon2 @@ -32,7 +34,7 @@ use {NUM_ASSETS as SPONSORSHIP_NUM_ASSETS, FEATURE_NOTE_ID_ITEM_OFFSET} from mid use {NETWORK_ACCOUNT_TARGET_ATTACHMENT_SCHEME, NETWORK_ACCOUNT_TARGET_ATTACHMENT_NUM_WORDS} from miden::standards::attachments::network_account_target -use {Asset, AssetId, Bool} from miden::protocol::types +use {AccountId, Asset, AssetId, Bool} from miden::protocol::types pub mod fee_manager pub mod policies @@ -65,14 +67,25 @@ const WORD_ALIGNMENT_MASK = 0xFFFFFFFC const SPONSORSHIP_STORAGE_LOC = 0 const SPONSORSHIP_FEATURE_NOTE_ID_LOC = SPONSORSHIP_STORAGE_LOC + FEATURE_NOTE_ID_ITEM_OFFSET -# Local memory offset at which `process_network_note_sponsorship` caches the expected fee asset ID. +# Number of entries in the sponsorship price table that `estimate_network_note_sponsorships` fills +# and `create_network_note_sponsorships` reads, indexed by output note index. Sized for the +# transaction kernel's MAX_OUTPUT_NOTES_PER_TX limit; callers reserve this many elements of local +# memory for it. +pub const SPONSORSHIP_PRICE_TABLE_NUM_ENTRIES = 1024 + +# Local memory offsets at which `process_network_note_sponsorship` caches the expected fee asset ID +# and the address of the note's price table entry. const EXPECTED_FEE_ASSET_ID_LOC = 0 +const PRICE_ENTRY_PTR_LOC = 4 + +# Local memory offset at which `read_network_target_id` stages the network account target +# attachment. +const READ_TARGET_ATTACHMENT_WORD_LOC = 0 # Local memory offsets used by `compute_sponsorship_fee` (in its own local frame). -const COMPUTE_ATTACHMENT_WORD_LOC = 0 -const COMPUTE_NOTE_IDX_LOC = 4 -const COMPUTE_TARGET_ID_SUFFIX_LOC = 5 -const COMPUTE_TARGET_ID_PREFIX_LOC = 6 +const COMPUTE_NOTE_IDX_LOC = 0 +const COMPUTE_TARGET_ID_SUFFIX_LOC = 1 +const COMPUTE_TARGET_ID_PREFIX_LOC = 2 # Local memory offsets used by `create_sponsorship_note` (in its own local frame). const CREATE_FEE_ASSET_ID_LOC = 0 @@ -254,12 +267,12 @@ end #! #! Walks the transaction's output notes as they stood when this procedure was called. For each note #! that carries a `NetworkAccountTarget` attachment (i.e. is targeted at a network account), it reads -#! the target account ID, asks that account's fee policy what the note costs via an FPI call to its -#! `estimate_note_fee` procedure, and, if the fee is non-zero, creates a FEE_SPONSORSHIP output note -#! bound to the network note (as its companion) carrying exactly the estimated fee asset. The fee -#! asset is removed from the native account's vault to fund the note. Until fee asset conversion -#! exists, the target's fee must use the expected fee asset supplied by the caller, which typically -#! reads it from this account's fee manager. +#! the price `estimate_network_note_sponsorships` recorded for it in the price table and, if the +#! price is non-zero, creates a FEE_SPONSORSHIP output note bound to the network note (as its +#! companion) carrying exactly that amount of the expected fee asset. The fee asset is removed from +#! the native account's vault to fund the note. The table must have been filled for the same output +#! notes by `estimate_network_note_sponsorships`, which is where the target's fee policy is queried +#! and its fee asset checked. #! #! The created sponsorship note is public, tagged for the target network account, names the native #! account as its reclaimer, and has reclaim disabled. Its serial number is derived from the @@ -269,41 +282,41 @@ end #! Must be invoked while the native (fee-paying) account is the active account, since it removes the #! fee asset from that account's vault and creates notes from it. #! -#! Inputs: [FEE_ASSET_ID] +#! Inputs: [FEE_ASSET_ID, price_table_ptr] #! Outputs: [total_sponsored_fee_amount] #! #! Where: #! - FEE_ASSET_ID is the ID of the asset sponsorship notes are funded with, typically read from #! the fee manager's fee asset ID slot. +#! - price_table_ptr is the address of the price table `estimate_network_note_sponsorships` filled. #! - total_sponsored_fee_amount is the total amount moved into the created sponsorship notes. #! #! Panics if: #! - a network output note's target ID attachment does not consist of exactly one word. -#! - the target account's fee policy requires a network output note's recipient preimage and it -#! is unavailable or does not hash to the recipient. -#! - the target account's active fee policy is not set. -#! - a target account charges a non-zero fee in an asset other than the expected fee asset. #! - the native account's vault does not hold enough of the fee asset. #! #! Invocation: exec -pub proc create_network_note_sponsorships(fee_asset_id: AssetId) -> felt - push.1 movdn.4 exec.process_network_note_sponsorships movdn.2 drop drop +pub proc create_network_note_sponsorships(fee_asset_id: AssetId, price_table_ptr: u32) -> felt + push.1 movdn.5 exec.process_network_note_sponsorships movdn.2 drop drop # => [total_sponsored_fee_amount] end #! Estimates the FEE_SPONSORSHIP notes that this transaction's network output notes require. #! -#! Prices the network output notes exactly as `create_network_note_sponsorships` does, but creates -#! no note and moves nothing out of the vault, so a caller can charge for those notes before it -#! pays for them. A note the target prices to zero needs no sponsorship note and so is not -#! counted. +#! Asks each network note's target account what the note costs via an FPI call to its fee policy +#! and records the price in the table at price_table_ptr, indexed by output note index, for +#! `create_network_note_sponsorships` to fund the notes from. Creates no note and moves nothing out +#! of the vault, so a caller can charge for those notes before it pays for them. A note the target +#! prices to zero needs no sponsorship note and so is not counted. #! -#! Inputs: [FEE_ASSET_ID] +#! Inputs: [FEE_ASSET_ID, price_table_ptr] #! Outputs: [num_sponsorship_notes, num_output_notes] #! #! Where: #! - FEE_ASSET_ID is the ID of the asset the sponsorship notes are funded with, typically read #! from the fee manager's fee asset ID slot. +#! - price_table_ptr is the address of a table of SPONSORSHIP_PRICE_TABLE_NUM_ENTRIES elements in +#! the caller's local memory; only the entries of network output notes are written. #! - num_sponsorship_notes is the number of notes `create_network_note_sponsorships` will create. #! - num_output_notes is the number of output notes the transaction has at this point, i.e. the #! notes `create_network_note_sponsorships` will walk. @@ -316,8 +329,8 @@ end #! - a target account charges a non-zero fee in an asset other than the expected fee asset. #! #! Invocation: exec -pub proc estimate_network_note_sponsorships(fee_asset_id: AssetId) -> (felt, felt) - push.0 movdn.4 exec.process_network_note_sponsorships drop +pub proc estimate_network_note_sponsorships(fee_asset_id: AssetId, price_table_ptr: u32) -> (felt, felt) + push.0 movdn.5 exec.process_network_note_sponsorships drop # => [num_sponsorship_notes, num_output_notes] end @@ -585,18 +598,19 @@ proc is_network_note(note_idx: u16) -> (Bool, u8) # => [is_found, attachment_idx] end -#! Walks the transaction's output notes and prices every network note through its target account's -#! fee policy, creating its FEE_SPONSORSHIP note along the way when asked to. +#! Walks the transaction's output notes and, for every network note, either prices it through its +#! target account's fee policy into the price table or funds its FEE_SPONSORSHIP note from the table. #! -#! Backs `create_network_note_sponsorships` (create_notes = 1) and -#! `estimate_network_note_sponsorships` (create_notes = 0); see those for the full behaviour. +#! Backs `estimate_network_note_sponsorships` (create_notes = 0) and +#! `create_network_note_sponsorships` (create_notes = 1); see those for the full behaviour. #! -#! Inputs: [FEE_ASSET_ID, create_notes] +#! Inputs: [FEE_ASSET_ID, price_table_ptr, create_notes] #! Outputs: [total_sponsored_fee_amount, num_sponsorship_notes, num_output_notes] #! #! Where: #! - FEE_ASSET_ID is the ID of the asset the sponsorship notes are funded with. -#! - create_notes is 1 to create the sponsorship notes, 0 to only price them. +#! - price_table_ptr is the address of the price table, indexed by output note index. +#! - create_notes is 1 to create the sponsorship notes from the table, 0 to fill the table. #! - total_sponsored_fee_amount is the total amount the sponsorship notes carry. #! - num_sponsorship_notes is the number of network notes priced above zero, i.e. of sponsorship #! notes created or to be created. @@ -606,154 +620,224 @@ end #! - process_network_note_sponsorship panics for a network output note. #! #! Invocation: exec -proc process_network_note_sponsorships(fee_asset_id: AssetId, create_notes: Bool) -> (felt, felt, felt) - push.0 movdn.4 - # => [FEE_ASSET_ID, num_sponsorship_notes = 0, create_notes] - - push.0 movdn.4 - # => [FEE_ASSET_ID, total_sponsored_fee_amount = 0, num_sponsorship_notes, create_notes] +proc process_network_note_sponsorships( + fee_asset_id: AssetId, + price_table_ptr: u32, + create_notes: Bool, +) -> (felt, felt, felt) + push.0 movdn.5 + # => [FEE_ASSET_ID, price_table_ptr, num_sponsorship_notes = 0, create_notes] + + push.0 movdn.5 + # => [FEE_ASSET_ID, price_table_ptr, total_sponsored_fee_amount = 0, num_sponsorship_notes, + # create_notes] # get the output note count before any sponsorship notes are created, since creating them # increments the count exec.tx::get_num_output_notes - # => [num_output_notes, FEE_ASSET_ID, total_sponsored_fee_amount, num_sponsorship_notes, - # create_notes] + # => [num_output_notes, FEE_ASSET_ID, price_table_ptr, total_sponsored_fee_amount, + # num_sponsorship_notes, create_notes] push.0 - # => [note_idx = 0, num_output_notes, FEE_ASSET_ID, total_sponsored_fee_amount, + # => [note_idx = 0, num_output_notes, FEE_ASSET_ID, price_table_ptr, total_sponsored_fee_amount, # num_sponsorship_notes, create_notes] dup.1 dup.1 neq - # => [should_loop, note_idx, num_output_notes, FEE_ASSET_ID, total_sponsored_fee_amount, - # num_sponsorship_notes, create_notes] + # => [should_loop, note_idx, num_output_notes, FEE_ASSET_ID, price_table_ptr, + # total_sponsored_fee_amount, num_sponsorship_notes, create_notes] while.true # only network output notes are sponsored dup exec.is_network_note # => [is_network_note, attachment_idx, note_idx, num_output_notes, FEE_ASSET_ID, - # total_sponsored_fee_amount, num_sponsorship_notes, create_notes] + # price_table_ptr, total_sponsored_fee_amount, num_sponsorship_notes, create_notes] if.true dup.1 # => [note_idx, attachment_idx, note_idx, num_output_notes, FEE_ASSET_ID, - # total_sponsored_fee_amount, num_sponsorship_notes, create_notes] + # price_table_ptr, total_sponsored_fee_amount, num_sponsorship_notes, create_notes] - # pass a copy of the expected fee asset ID and of the create flag along with the note - # parameters - dupw.1 movup.5 movup.5 dup.14 movdn.6 - # => [note_idx, attachment_idx, FEE_ASSET_ID, create_notes, note_idx, num_output_notes, - # FEE_ASSET_ID, total_sponsored_fee_amount, num_sponsorship_notes, create_notes] + # pass copies of the expected fee asset ID, the table pointer and the create flag along + # with the note parameters + dupw.1 movup.5 movup.5 + # => [note_idx, attachment_idx, FEE_ASSET_ID, note_idx, num_output_notes, FEE_ASSET_ID, + # price_table_ptr, total_sponsored_fee_amount, num_sponsorship_notes, create_notes] + + dup.15 movdn.6 dup.13 movdn.6 + # => [note_idx, attachment_idx, FEE_ASSET_ID, price_table_ptr, create_notes, note_idx, + # num_output_notes, FEE_ASSET_ID, price_table_ptr, total_sponsored_fee_amount, + # num_sponsorship_notes, create_notes] exec.process_network_note_sponsorship - # => [sponsored_fee_amount, note_idx, num_output_notes, FEE_ASSET_ID, + # => [sponsored_fee_amount, note_idx, num_output_notes, FEE_ASSET_ID, price_table_ptr, # total_sponsored_fee_amount, num_sponsorship_notes, create_notes] # only increment `num_sponsorship_notes` if the amount is non-zero - dup neq.0 movup.9 add movdn.8 - # => [sponsored_fee_amount, note_idx, num_output_notes, FEE_ASSET_ID, + dup neq.0 movup.10 add movdn.9 + # => [sponsored_fee_amount, note_idx, num_output_notes, FEE_ASSET_ID, price_table_ptr, # total_sponsored_fee_amount, num_sponsorship_notes, create_notes] - movup.7 add movdn.6 - # => [note_idx, num_output_notes, FEE_ASSET_ID, total_sponsored_fee_amount, - # num_sponsorship_notes, create_notes] + movup.8 add movdn.7 + # => [note_idx, num_output_notes, FEE_ASSET_ID, price_table_ptr, + # total_sponsored_fee_amount, num_sponsorship_notes, create_notes] else # do not sponsor non-network output notes drop - # => [note_idx, num_output_notes, FEE_ASSET_ID, total_sponsored_fee_amount, - # num_sponsorship_notes, create_notes] + # => [note_idx, num_output_notes, FEE_ASSET_ID, price_table_ptr, + # total_sponsored_fee_amount, num_sponsorship_notes, create_notes] end add.1 - # => [note_idx+1, num_output_notes, FEE_ASSET_ID, total_sponsored_fee_amount, - # num_sponsorship_notes, create_notes] + # => [note_idx+1, num_output_notes, FEE_ASSET_ID, price_table_ptr, + # total_sponsored_fee_amount, num_sponsorship_notes, create_notes] dup.1 dup.1 neq - # => [should_loop, note_idx, num_output_notes, FEE_ASSET_ID, total_sponsored_fee_amount, - # num_sponsorship_notes, create_notes] + # => [should_loop, note_idx, num_output_notes, FEE_ASSET_ID, price_table_ptr, + # total_sponsored_fee_amount, num_sponsorship_notes, create_notes] end - # => [note_idx, num_output_notes, FEE_ASSET_ID, total_sponsored_fee_amount, + # => [note_idx, num_output_notes, FEE_ASSET_ID, price_table_ptr, total_sponsored_fee_amount, # num_sponsorship_notes, create_notes] - drop movdn.6 dropw movup.3 drop + drop movdn.7 dropw drop movup.3 drop # => [total_sponsored_fee_amount, num_sponsorship_notes, num_output_notes] end -#! Prices the network output note at the given index and, optionally, creates its -#! FEE_SPONSORSHIP note. +#! Prices the network output note at the given index into the price table or, optionally, creates +#! its FEE_SPONSORSHIP note from it. #! -#! Handles a single network note for `create_network_note_sponsorships` (create_notes = 1) and -#! `estimate_network_note_sponsorships` (create_notes = 0); see those for the full behaviour. A -#! network note the target account prices to zero needs no sponsorship note, so none is created for -#! it. +#! With create_notes = 0 the note is priced through its target account's fee policy and the price +#! is recorded in the note's price table entry. With create_notes = 1 the recorded price is read +#! back and, if it is non-zero, the sponsorship note is created and funded with that amount of the +#! expected fee asset. A network note the target account prices to zero needs no sponsorship note, +#! so none is created for it. #! -#! Inputs: [note_idx, attachment_idx, EXPECTED_FEE_ASSET_ID, create_notes] +#! Inputs: [note_idx, attachment_idx, EXPECTED_FEE_ASSET_ID, price_table_ptr, create_notes] #! Outputs: [sponsored_fee_amount] #! #! Where: #! - note_idx is the index of the network output note to sponsor. #! - attachment_idx is the index of the note's network account target attachment. #! - EXPECTED_FEE_ASSET_ID is the ID of the asset the sponsorship note must be funded with. -#! - create_notes is 1 to create the sponsorship note, 0 to only price it. +#! - price_table_ptr is the address of the price table, indexed by output note index. +#! - create_notes is 1 to create the sponsorship note from the table, 0 to price the note into it. #! - sponsored_fee_amount is the amount the sponsorship note carries, or zero if the target prices #! the note to zero and so it needs none. #! #! Panics if: #! - the note's target ID attachment does not consist of exactly one word. -#! - the target account's fee policy requires the note's recipient preimage and it is unavailable -#! or does not hash to the recipient. -#! - the target account's active fee policy is not set. -#! - the target account charges a non-zero fee in an asset other than the expected fee asset. +#! - create_notes is 0 and the target account's fee policy requires the note's recipient preimage +#! and it is unavailable or does not hash to the recipient. +#! - create_notes is 0 and the target account's active fee policy is not set. +#! - create_notes is 0 and the target account charges a non-zero fee in an asset other than the +#! expected fee asset. #! - create_notes is 1 and the native account's vault does not hold enough of the fee asset. #! #! Invocation: exec -@locals(4) +@locals(5) proc process_network_note_sponsorship( note_idx: u16, attachment_idx: u8, expected_fee_asset_id: AssetId, + price_table_ptr: u32, create_notes: Bool ) -> felt - # cache the expected fee asset ID for the target fee asset check + # cache the expected fee asset ID for the target fee asset check and the note funding movdn.5 movdn.5 loc_storew_le.EXPECTED_FEE_ASSET_ID_LOC dropw + # => [note_idx, attachment_idx, price_table_ptr, create_notes] + + # cache the address of the note's price table entry + movup.2 dup.1 add loc_store.PRICE_ENTRY_PTR_LOC # => [note_idx, attachment_idx, create_notes] - dup movdn.3 - # => [note_idx, attachment_idx, create_notes, note_idx] + movup.2 + # => [create_notes, note_idx, attachment_idx] - exec.compute_sponsorship_fee - # => [FEE_ASSET_ID, FEE_ASSET_VALUE, target_id_prefix, create_notes, note_idx] + if.true + loc_load.PRICE_ENTRY_PTR_LOC mem_load + # => [sponsored_fee_amount, note_idx, attachment_idx] - exec.fungible_asset::to_amount_unchecked - # => [fee_amount, FEE_ASSET_ID, FEE_ASSET_VALUE, target_id_prefix, create_notes, note_idx] + dup eq.0 + if.true + # a note priced to zero needs no sponsorship note + movdn.2 drop drop + # => [sponsored_fee_amount] + else + movdn.2 dup movdn.3 + # => [note_idx, attachment_idx, sponsored_fee_amount, note_idx] - movdn.11 dup.11 eq.0 - # => [is_zero_fee, FEE_ASSET_ID, FEE_ASSET_VALUE, target_id_prefix, create_notes, note_idx, - # fee_amount] + exec.read_network_target_id drop + # => [target_id_prefix, sponsored_fee_amount, note_idx] - if.true - # if the price of the note is zero, it needs no sponsorship note - dropw dropw drop drop drop - # => [fee_amount] + swap dup movdn.3 exec.fungible_asset::create_value + # => [FEE_ASSET_VALUE, target_id_prefix, note_idx, sponsored_fee_amount] + + padw loc_loadw_le.EXPECTED_FEE_ASSET_ID_LOC + # => [FEE_ASSET_ID, FEE_ASSET_VALUE, target_id_prefix, note_idx, sponsored_fee_amount] + + exec.create_sponsorship_note + # => [sponsored_fee_amount] + end else - # reject a target whose fee asset differs from the expected fee asset - dupw padw loc_loadw_le.EXPECTED_FEE_ASSET_ID_LOC exec.word::eq - assert.err=ERR_FEE_MANAGER_TARGET_FEE_ASSET_MISMATCH - # => [FEE_ASSET_ID, FEE_ASSET_VALUE, target_id_prefix, create_notes, note_idx, fee_amount] + exec.compute_sponsorship_fee + # => [FEE_ASSET_ID, FEE_ASSET_VALUE, target_id_prefix] + + exec.fungible_asset::to_amount_unchecked + # => [sponsored_fee_amount, FEE_ASSET_ID, FEE_ASSET_VALUE, target_id_prefix] - movup.9 - # => [create_notes, FEE_ASSET_ID, FEE_ASSET_VALUE, target_id_prefix, note_idx, fee_amount] + # record the price for the payment + dup loc_load.PRICE_ENTRY_PTR_LOC mem_store + # => [sponsored_fee_amount, FEE_ASSET_ID, FEE_ASSET_VALUE, target_id_prefix] + movdn.9 swapw dropw movup.4 drop + # => [FEE_ASSET_ID, sponsored_fee_amount] + + dup.4 neq.0 if.true - exec.create_sponsorship_note - # => [fee_amount] - else - # only pricing the note: discard the inputs of the note creation - dropw dropw drop drop - # => [fee_amount] + # reject a target whose fee asset differs from the expected fee asset + dupw padw loc_loadw_le.EXPECTED_FEE_ASSET_ID_LOC exec.word::eq + assert.err=ERR_FEE_MANAGER_TARGET_FEE_ASSET_MISMATCH end + # => [FEE_ASSET_ID, sponsored_fee_amount] + + dropw + # => [sponsored_fee_amount] end end +#! Reads the target account ID of the network output note at the given index from its network +#! account target attachment. +#! +#! Inputs: [note_idx, attachment_idx] +#! Outputs: [target_id_suffix, target_id_prefix] +#! +#! Where: +#! - note_idx is the index of the network output note. +#! - attachment_idx is the index of the note's network account target attachment. +#! +#! Panics if: +#! - the attachment does not consist of exactly one word. +#! +#! Invocation: exec +@locals(4) +proc read_network_target_id(note_idx: u16, attachment_idx: u8) -> AccountId + # copy the attachment into local memory, then read the target ID from it + swap locaddr.READ_TARGET_ATTACHMENT_WORD_LOC + # => [dest_ptr, attachment_idx, note_idx] + + exec.output_note::write_attachment_to_memory + # => [num_words] + + eq.NETWORK_ACCOUNT_TARGET_ATTACHMENT_NUM_WORDS + assert.err=ERR_FEE_MANAGER_NETWORK_TARGET_WRONG_NUM_WORDS + # => [] + + padw loc_loadw_le.READ_TARGET_ATTACHMENT_WORD_LOC + # => [NETWORK_ACCOUNT_TARGET_ATTACHMENT] + + exec.network_account_target::into_target_id + # => [target_id_suffix, target_id_prefix] +end + #! Computes the fee the target network account charges for the output note at the given index. #! #! Reads the note's target account ID from its network account target attachment, gathers the @@ -779,27 +863,13 @@ end #! - the target account's active fee policy is not set. #! #! Invocation: exec -@locals(7) +@locals(3) proc compute_sponsorship_fee(note_idx: u16, attachment_idx: u8) -> (Asset, felt) # the note index is needed repeatedly below, so cache it dup loc_store.COMPUTE_NOTE_IDX_LOC # => [note_idx, attachment_idx] - # copy the network account target attachment into local memory, then read the target ID - swap locaddr.COMPUTE_ATTACHMENT_WORD_LOC - # => [dest_ptr, attachment_idx, note_idx] - - exec.output_note::write_attachment_to_memory - # => [num_words] - - eq.NETWORK_ACCOUNT_TARGET_ATTACHMENT_NUM_WORDS - assert.err=ERR_FEE_MANAGER_NETWORK_TARGET_WRONG_NUM_WORDS - # => [] - - padw loc_loadw_le.COMPUTE_ATTACHMENT_WORD_LOC - # => [NETWORK_ACCOUNT_TARGET_ATTACHMENT] - - exec.network_account_target::into_target_id + exec.read_network_target_id # => [target_id_suffix, target_id_prefix] loc_store.COMPUTE_TARGET_ID_SUFFIX_LOC diff --git a/crates/miden-standards/src/note/costs/table.rs b/crates/miden-standards/src/note/costs/table.rs index f68bbcb4dc..530e980979 100644 --- a/crates/miden-standards/src/note/costs/table.rs +++ b/crates/miden-standards/src/note/costs/table.rs @@ -2,54 +2,54 @@ // Values are maxima across the benchmarked paths; see `miden_standards::note::costs` for the // caveats on what they do and do not cover. -/// Cycles of consuming a P2ID note: 1 asset 19356, 16 assets 57924 (maximum). -pub const P2ID_CONSUMPTION_CYCLES: u32 = 57924; +/// Cycles of consuming a P2ID note: 1 asset 19392, 16 assets 57960 (maximum). +pub const P2ID_CONSUMPTION_CYCLES: u32 = 57960; -/// Cycles of consuming a P2IDE note: claim 19464, claim with 16 assets 58032 (maximum), reclaim -/// 19619. -pub const P2IDE_CONSUMPTION_CYCLES: u32 = 58032; +/// Cycles of consuming a P2IDE note: claim 19500, claim with 16 assets 58068 (maximum), reclaim +/// 19655. +pub const P2IDE_CONSUMPTION_CYCLES: u32 = 58068; -/// Cycles of consuming a SWAP note: public payback 23593 (maximum), private payback 23090. -pub const SWAP_CONSUMPTION_CYCLES: u32 = 23593; +/// Cycles of consuming a SWAP note: public payback 23629 (maximum), private payback 23126. +pub const SWAP_CONSUMPTION_CYCLES: u32 = 23629; -/// Cycles of consuming a PSWAP note: full fill 26439, partial fill 30576 (maximum). -pub const PSWAP_CONSUMPTION_CYCLES: u32 = 30576; +/// Cycles of consuming a PSWAP note: full fill 26475, partial fill 30612 (maximum). +pub const PSWAP_CONSUMPTION_CYCLES: u32 = 30612; -/// Cycles of consuming a MINT note: fungible faucet 35248, non-fungible faucet 38612 (maximum). -pub const MINT_CONSUMPTION_CYCLES: u32 = 38612; +/// Cycles of consuming a MINT note: fungible faucet 35284, non-fungible faucet 38648 (maximum). +pub const MINT_CONSUMPTION_CYCLES: u32 = 38648; /// Cycles of consuming a BURN note (single benchmarked path). -pub const BURN_CONSUMPTION_CYCLES: u32 = 29595; +pub const BURN_CONSUMPTION_CYCLES: u32 = 29631; /// Cycles of consuming a CONSTANT_FEE_POLICY_CONFIG note (single benchmarked path). -pub const CONSTANT_FEE_POLICY_CONFIG_CONSUMPTION_CYCLES: u32 = 20819; +pub const CONSTANT_FEE_POLICY_CONFIG_CONSUMPTION_CYCLES: u32 = 20855; /// Cycles of consuming a FAUCET_POLICY_CONFIG note (single benchmarked path). -pub const FAUCET_POLICY_CONFIG_CONSUMPTION_CYCLES: u32 = 27923; +pub const FAUCET_POLICY_CONFIG_CONSUMPTION_CYCLES: u32 = 27959; /// Cycles of consuming a FAUCET_METADATA_CONFIG note (single benchmarked path). -pub const FAUCET_METADATA_CONFIG_CONSUMPTION_CYCLES: u32 = 28455; +pub const FAUCET_METADATA_CONFIG_CONSUMPTION_CYCLES: u32 = 28491; /// Cycles of consuming a MIN_BURN_AMOUNT_CONFIG note (single benchmarked path). -pub const MIN_BURN_AMOUNT_CONFIG_CONSUMPTION_CYCLES: u32 = 26952; +pub const MIN_BURN_AMOUNT_CONFIG_CONSUMPTION_CYCLES: u32 = 26988; /// Cycles of consuming an ALLOWLIST_CONFIG note (single benchmarked path). -pub const ALLOWLIST_CONFIG_CONSUMPTION_CYCLES: u32 = 27698; +pub const ALLOWLIST_CONFIG_CONSUMPTION_CYCLES: u32 = 27734; /// Cycles of consuming a BLOCKLIST_CONFIG note (single benchmarked path). -pub const BLOCKLIST_CONFIG_CONSUMPTION_CYCLES: u32 = 27698; +pub const BLOCKLIST_CONFIG_CONSUMPTION_CYCLES: u32 = 27734; /// Cycles of consuming a PAUSE_CONFIG note (single benchmarked path). -pub const PAUSE_CONFIG_CONSUMPTION_CYCLES: u32 = 19430; +pub const PAUSE_CONFIG_CONSUMPTION_CYCLES: u32 = 19466; /// Cycles of consuming an OWNER_CONFIG note (single benchmarked path). -pub const OWNER_CONFIG_CONSUMPTION_CYCLES: u32 = 19011; +pub const OWNER_CONFIG_CONSUMPTION_CYCLES: u32 = 19047; /// Cycles of consuming an RBAC_CONFIG note (single benchmarked path). -pub const RBAC_CONFIG_CONSUMPTION_CYCLES: u32 = 23006; +pub const RBAC_CONFIG_CONSUMPTION_CYCLES: u32 = 23042; /// Cycles of consuming a NETWORK_ACCOUNT_CONFIG note (single benchmarked path). -pub const NETWORK_ACCOUNT_CONFIG_CONSUMPTION_CYCLES: u32 = 19911; +pub const NETWORK_ACCOUNT_CONFIG_CONSUMPTION_CYCLES: u32 = 19947; /// Cycles of consuming a FEE_SPONSORSHIP note (single benchmarked path). -pub const FEE_SPONSORSHIP_CONSUMPTION_CYCLES: u32 = 22250; +pub const FEE_SPONSORSHIP_CONSUMPTION_CYCLES: u32 = 22286; From 3ce575b07561cee51b46f726e2bfddc5588e7ffc Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Thu, 3 Sep 2026 13:04:23 +0000 Subject: [PATCH 09/12] test(standards): sponsor two network notes at their own prices Covers the price table with two network notes priced differently, so a sponsorship funded from the wrong entry would fail. Also names the table size on estimate_fee's doc and the changelog, and drops a stale comment about a pricing call in the payment tail. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01KkQzFtRjyDwQ7iVtRbsHsn --- CHANGELOG.md | 2 +- .../asm/standards/fee/mod.masm | 3 +- .../tests/auth/fee_payment/sponsorship.rs | 131 ++++++++++++++---- 3 files changed, 104 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2c9cb6cf3..47757101c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ ### Changes -- [BREAKING] Split `fee::estimate_fee` out of `fee::pay_fee`, pricing the FEE_SPONSORSHIP notes via the new `fees::estimate_network_note_sponsorships` without creating them, and `fee::apply_cycle_margins` now also takes the sponsorship-note and output-note counts ([#3784](https://github.com/0xMiden/protocol/pull/3784)). +- [BREAKING] Split `fee::estimate_fee` out of `fee::pay_fee`, pricing the FEE_SPONSORSHIP notes via the new `fees::estimate_network_note_sponsorships` without creating them, `fee::apply_cycle_margins` now also takes the sponsorship-note and output-note counts, and `fee::estimate_fee` and the `fees` sponsorship walks take the address of a 1024-element price table in the caller's local memory ([#3784](https://github.com/0xMiden/protocol/pull/3784)). - Added a check that the guardian public key is not one of the approver public keys ([#3764](https://github.com/0xMiden/protocol/pull/3764)). - [BREAKING] Incremented the MSRV to 1.98. - [BREAKING] Updated the Miden VM and crypto crate family to v0.30.0 and `midenc-hir-type` to v0.12.0. `LocalTransactionProver::new` now takes `miden_prover::Prover`, `CoreLibrary` exposes one merged package, and `TransactionVerifier::verify` now returns `VerificationOutcome` so callers can handle outstanding precompile work ([#3782](https://github.com/0xMiden/protocol/pull/3782)). diff --git a/crates/miden-standards/asm/standards/fee/mod.masm b/crates/miden-standards/asm/standards/fee/mod.masm index 57bea0d6cc..893e6e37f0 100644 --- a/crates/miden-standards/asm/standards/fee/mod.masm +++ b/crates/miden-standards/asm/standards/fee/mod.masm @@ -367,7 +367,8 @@ end #! - FEE_ASSET_ID is the ID of the native fee asset (see tx::get_fee_asset_id), which funds the #! sponsorship notes. #! - price_table_ptr is the address of the sponsorship price table the estimate fills and the -#! payment reads back (see fees::estimate_network_note_sponsorships). +#! payment reads back: fees::SPONSORSHIP_PRICE_TABLE_NUM_ENTRIES elements of the caller's local +#! memory (see fees::estimate_network_note_sponsorships). #! - num_extra_cycles is the estimated number of cycles the caller spends after this call until #! the end of the authentication procedure. #! - fee_amount is the computed fee of the transaction, denominated in the native fee asset. diff --git a/crates/miden-testing/tests/auth/fee_payment/sponsorship.rs b/crates/miden-testing/tests/auth/fee_payment/sponsorship.rs index 7affdd240c..42f0feb810 100644 --- a/crates/miden-testing/tests/auth/fee_payment/sponsorship.rs +++ b/crates/miden-testing/tests/auth/fee_payment/sponsorship.rs @@ -120,54 +120,80 @@ fn is_sponsorship_note(note: &RawOutputNote) -> bool { async fn send_network_note( target_fee: u64, ) -> anyhow::Result<(ExecutedTransaction, Note, AccountId)> { + let (executed, mut notes) = send_network_notes(&[target_fee]).await?; + let (network_note, target_id) = notes.remove(0); + Ok((executed, network_note, target_id)) +} + +/// Has a signing wallet send one P2ID network note per entry of `target_fees`, each to its own +/// network account pricing the P2ID script root at that fee, paying its fee through `pay_fee`. +/// Returns the executed transaction and, in creation order, each network note with its target's ID. +async fn send_network_notes( + target_fees: &[u64], +) -> anyhow::Result<(ExecutedTransaction, Vec<(Note, AccountId)>)> { let mut rng = RandomCoin::new(Word::from([1u32, 2, 3, 4])); - // a payload asset issued by a faucet other than the fee faucet, carried by the network note + // a payload asset issued by a faucet other than the fee faucet, carried by each network note let payload_asset: Asset = FungibleAsset::mock(50); + let payload_total: Asset = FungibleAsset::mock(50 * target_fees.len() as u64); let mut builder = MockChain::builder().verification_base_fee(VERIFICATION_BASE_FEE); - // the sponsor is a signing wallet holding the fee asset (for its own fee and the sponsorship) - // and the payload asset + // the sponsor is a signing wallet holding the fee asset (for its own fee and the sponsorships) + // and the payload assets let sponsor = builder.add_existing_wallet_with_assets( Auth::basic_ecdsa(), - [fee_asset(1_000_000)?, payload_asset], + [fee_asset(1_000_000)?, payload_total], )?; - // the target network account prices the P2ID script root at target_fee - let target = network_account( - [2; 32], - [P2idNote::script_root(), FeeSponsorshipNote::script_root()], - &[(P2idNote::script_root(), target_fee)], - [], - SponsorshipPolicy::default(), - )?; - builder.add_account(target.clone())?; + // each target network account prices the P2ID script root at its own fee + let mut targets = Vec::new(); + for (i, target_fee) in target_fees.iter().enumerate() { + let target = network_account( + [2 + i as u8; 32], + [P2idNote::script_root(), FeeSponsorshipNote::script_root()], + &[(P2idNote::script_root(), *target_fee)], + [], + SponsorshipPolicy::default(), + )?; + builder.add_account(target.clone())?; + targets.push(target); + } let mut mock_chain = builder.build()?; mock_chain.prove_next_block()?; - // the sponsor creates the P2ID network note via a send-notes transaction script - let network_note = p2id_network_note(sponsor.id(), target.id(), payload_asset, &mut rng)?; - let tx_script = SendNotesTransactionScript::new( - &sponsor.code().interface(sponsor.id()), - &[PartialNote::from(network_note.clone())], - )?; + // the sponsor creates the P2ID network notes via a send-notes transaction script + let network_notes = targets + .iter() + .map(|target| p2id_network_note(sponsor.id(), target.id(), payload_asset, &mut rng)) + .collect::>>()?; + let partial_notes: Vec = + network_notes.iter().cloned().map(PartialNote::from).collect(); + let tx_script = + SendNotesTransactionScript::new(&sponsor.code().interface(sponsor.id()), &partial_notes)?; let (auth_args, advice_value) = native_conversion_info(); - let foreign_target = mock_chain.get_foreign_account_inputs(target.id())?; + let foreign_targets = targets + .iter() + .map(|target| mock_chain.get_foreign_account_inputs(target.id())) + .collect::, _>>()?; - let executed = mock_chain + let mut tx_builder = mock_chain .build_transaction(sponsor.id()) - .foreign_accounts([foreign_target]) + .foreign_accounts(foreign_targets) .send_notes_script(&tx_script) .auth_args(auth_args) - .add_advice_map_entry(auth_args, advice_value) - .expected_output_note(RawOutputNote::Full(network_note.clone())) - .build()? - .execute() - .await?; + .add_advice_map_entry(auth_args, advice_value); + for network_note in &network_notes { + tx_builder = tx_builder.expected_output_note(RawOutputNote::Full(network_note.clone())); + } + let executed = tx_builder.build()?.execute().await?; - Ok((executed, network_note, target.id())) + let notes = network_notes + .into_iter() + .zip(targets.iter().map(|target| target.id())) + .collect(); + Ok((executed, notes)) } // TESTS @@ -233,8 +259,7 @@ async fn pay_fee_does_not_sponsor_network_note_priced_to_zero() -> anyhow::Resul output_notes.iter().all(|note| !is_sponsorship_note(note)), "a network note priced to zero should not be sponsored", ); - // the sponsor's fee note still covers the fee required for the transaction, including the - // pricing call the payment makes for the unsponsored note after the fee is computed + // the sponsor's fee note still covers the fee required for the transaction let fee_note = output_notes.get_note(1); assert_eq!(fee_note.metadata().tag(), TxFeeNote::TAG); let paid = fee_note @@ -253,6 +278,52 @@ async fn pay_fee_does_not_sponsor_network_note_priced_to_zero() -> anyhow::Resul Ok(()) } +/// Each sponsorship note carries the price its own network note's target charges: with two network +/// notes priced differently, the payment funds each from the price the estimate recorded for that +/// note, not from a single shared price. +#[tokio::test] +async fn pay_fee_sponsors_each_network_note_at_its_own_price() -> anyhow::Result<()> { + let target_fees = [FEE_AMOUNT, 3 * FEE_AMOUNT]; + let (executed, notes) = send_network_notes(&target_fees).await?; + + // five output notes: the two network notes, their sponsorship notes, and the sponsor's fee note + let output_notes = executed.output_notes(); + assert_eq!(output_notes.num_notes(), 5); + + for (i, ((network_note, target_id), target_fee)) in notes.iter().zip(target_fees).enumerate() { + assert_eq!(output_notes.get_note(i).id(), network_note.id()); + + let sponsorship = output_notes + .iter() + .find(|note| { + is_sponsorship_note(note) + && note.metadata().tag() == NoteTag::with_account_target(*target_id) + }) + .expect("each network note should have a sponsorship note tagged for its target"); + let sponsorship_assets: Vec = sponsorship.assets().iter().copied().collect(); + assert_eq!(sponsorship_assets, vec![fee_asset(target_fee)?]); + } + + let fee_note = output_notes + .iter() + .find(|note| note.metadata().tag() == TxFeeNote::TAG) + .expect("the sponsor should pay its own fee note"); + let paid = fee_note + .assets() + .iter() + .next() + .expect("fee note carries one asset") + .unwrap_fungible(); + assert!( + paid.amount() >= executed.compute_fee(), + "paid fee {} should cover the required fee {}", + paid.amount(), + executed.compute_fee(), + ); + + Ok(()) +} + /// End-to-end single hop: a local wallet creates a P2ID network note (and, via `pay_fee`, its /// FEE_SPONSORSHIP note); the target network account then consumes both, and its /// `collect_sponsored_fees` credits the prepaid fee into its vault. From 37a0d02553396fbbc80fe430574e07b4bcb8e522 Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Thu, 3 Sep 2026 13:54:45 +0000 Subject: [PATCH 10/12] refactor(standards): cache the sponsorship fee asset value word The price table holds the fee asset value word each target returned, one per output note, so the payment reads back exactly what was priced instead of rebuilding the value from an amount. Cost tables regenerated. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01KkQzFtRjyDwQ7iVtRbsHsn --- CHANGELOG.md | 2 +- bin/bench-transaction/bench-tx.json | 106 +++++++++--------- crates/miden-agglayer/src/costs/table.rs | 8 +- .../asm/standards/fee/mod.masm | 10 +- .../asm/standards/fees/mod.masm | 59 +++++----- 5 files changed, 94 insertions(+), 91 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47757101c8..c3bc32afc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ ### Changes -- [BREAKING] Split `fee::estimate_fee` out of `fee::pay_fee`, pricing the FEE_SPONSORSHIP notes via the new `fees::estimate_network_note_sponsorships` without creating them, `fee::apply_cycle_margins` now also takes the sponsorship-note and output-note counts, and `fee::estimate_fee` and the `fees` sponsorship walks take the address of a 1024-element price table in the caller's local memory ([#3784](https://github.com/0xMiden/protocol/pull/3784)). +- [BREAKING] Split `fee::estimate_fee` out of `fee::pay_fee`, pricing the FEE_SPONSORSHIP notes via the new `fees::estimate_network_note_sponsorships` without creating them, `fee::apply_cycle_margins` now also takes the sponsorship-note and output-note counts, and `fee::estimate_fee` and the `fees` sponsorship walks take the address of a price table of one word per output note in the caller's local memory ([#3784](https://github.com/0xMiden/protocol/pull/3784)). - Added a check that the guardian public key is not one of the approver public keys ([#3764](https://github.com/0xMiden/protocol/pull/3764)). - [BREAKING] Incremented the MSRV to 1.98. - [BREAKING] Updated the Miden VM and crypto crate family to v0.30.0 and `midenc-hir-type` to v0.12.0. `LocalTransactionProver::new` now takes `miden_prover::Prover`, `CoreLibrary` exposes one merged package, and `TransactionVerifier::verify` now returns `VerificationOutcome` so callers can handle outstanding precompile work ([#3782](https://github.com/0xMiden/protocol/pull/3782)). diff --git a/bin/bench-transaction/bench-tx.json b/bin/bench-transaction/bench-tx.json index 36dbdf0201..ea12dfcaa1 100644 --- a/bin/bench-transaction/bench-tx.json +++ b/bin/bench-transaction/bench-tx.json @@ -3,7 +3,7 @@ "prologue": 5329, "notes_processing": 2173, "note_execution": { - "0x37a0c87d66130e20a87710fb5be10a75ccc1ff1de4c6b14c77787ce7e0366aa3": 2131 + "0xeab5ee2cb049d9ceb37b8e6001a3d7b51b45cf499cc4f251da28087e47238b6b": 2131 }, "tx_script_processing": 41, "epilogue": { @@ -14,7 +14,7 @@ "core_rows": 81445, "chiplets_rows": 11504, "poseidon2_permutation_rows": 55392, - "range_rows": 20683, + "range_rows": 20645, "chiplets_shape": { "hasher_rows": 8656, "bitwise_rows": 656, @@ -28,7 +28,7 @@ "prologue": 5329, "notes_processing": 2173, "note_execution": { - "0xa4506f26c4c717bd0a860f6955a596f4872a5f398e815de703b5d1e52aaa24dc": 2131 + "0x650abad4e79b5ca928a1370ffd726632d2ce41de366e91517650a5cf237c28c4": 2131 }, "tx_script_processing": 41, "epilogue": { @@ -39,7 +39,7 @@ "core_rows": 13682, "chiplets_rows": 5872, "poseidon2_permutation_rows": 19888, - "range_rows": 1991, + "range_rows": 1969, "chiplets_shape": { "hasher_rows": 4224, "bitwise_rows": 848, @@ -65,7 +65,7 @@ "core_rows": 84045, "chiplets_rows": 13403, "poseidon2_permutation_rows": 55648, - "range_rows": 20541, + "range_rows": 20515, "chiplets_shape": { "hasher_rows": 10072, "bitwise_rows": 1032, @@ -91,7 +91,7 @@ "core_rows": 16282, "chiplets_rows": 7771, "poseidon2_permutation_rows": 20144, - "range_rows": 1397, + "range_rows": 1413, "chiplets_shape": { "hasher_rows": 5640, "bitwise_rows": 1224, @@ -114,7 +114,7 @@ "core_rows": 79635, "chiplets_rows": 10916, "poseidon2_permutation_rows": 53280, - "range_rows": 20499, + "range_rows": 20351, "chiplets_shape": { "hasher_rows": 8240, "bitwise_rows": 616, @@ -137,7 +137,7 @@ "core_rows": 11872, "chiplets_rows": 5284, "poseidon2_permutation_rows": 17728, - "range_rows": 1223, + "range_rows": 1237, "chiplets_shape": { "hasher_rows": 3808, "bitwise_rows": 808, @@ -155,14 +155,14 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 17828, - "auth_procedure": 12559 + "total": 17843, + "auth_procedure": 12574 }, "trace": { - "core_rows": 51188, + "core_rows": 51203, "chiplets_rows": 20591, - "poseidon2_permutation_rows": 42016, - "range_rows": 3553, + "poseidon2_permutation_rows": 42032, + "range_rows": 3561, "chiplets_shape": { "hasher_rows": 13432, "bitwise_rows": 2848, @@ -180,14 +180,14 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 17828, - "auth_procedure": 12559 + "total": 17843, + "auth_procedure": 12574 }, "trace": { - "core_rows": 61158, + "core_rows": 61173, "chiplets_rows": 23149, - "poseidon2_permutation_rows": 44160, - "range_rows": 3753, + "poseidon2_permutation_rows": 44176, + "range_rows": 3747, "chiplets_shape": { "hasher_rows": 14792, "bitwise_rows": 3104, @@ -205,13 +205,13 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 26284, - "auth_procedure": 11311 + "total": 26299, + "auth_procedure": 11326 }, "trace": { - "core_rows": 151744, + "core_rows": 151759, "chiplets_rows": 71628, - "poseidon2_permutation_rows": 114624, + "poseidon2_permutation_rows": 114640, "range_rows": 5943, "chiplets_shape": { "hasher_rows": 57472, @@ -230,14 +230,14 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 25996, - "auth_procedure": 11311 + "total": 26011, + "auth_procedure": 11326 }, "trace": { - "core_rows": 149765, + "core_rows": 149780, "chiplets_rows": 70657, - "poseidon2_permutation_rows": 116016, - "range_rows": 5957, + "poseidon2_permutation_rows": 116032, + "range_rows": 5917, "chiplets_shape": { "hasher_rows": 56624, "bitwise_rows": 3664, @@ -255,14 +255,14 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 17356, - "auth_procedure": 11311 + "total": 17371, + "auth_procedure": 11326 }, "trace": { - "core_rows": 86956, + "core_rows": 86971, "chiplets_rows": 40799, - "poseidon2_permutation_rows": 52448, - "range_rows": 4959, + "poseidon2_permutation_rows": 52464, + "range_rows": 4913, "chiplets_shape": { "hasher_rows": 30456, "bitwise_rows": 3664, @@ -863,7 +863,7 @@ "core_rows": 17359, "chiplets_rows": 7868, "poseidon2_permutation_rows": 23776, - "range_rows": 1535, + "range_rows": 1517, "chiplets_shape": { "hasher_rows": 5824, "bitwise_rows": 1160, @@ -881,14 +881,14 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 21922, - "auth_procedure": 15248 + "total": 21937, + "auth_procedure": 15263 }, "trace": { - "core_rows": 55282, + "core_rows": 55297, "chiplets_rows": 22733, - "poseidon2_permutation_rows": 47312, - "range_rows": 3741, + "poseidon2_permutation_rows": 47328, + "range_rows": 3761, "chiplets_shape": { "hasher_rows": 15072, "bitwise_rows": 3200, @@ -906,14 +906,14 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 21922, - "auth_procedure": 15248 + "total": 21937, + "auth_procedure": 15263 }, "trace": { - "core_rows": 65252, + "core_rows": 65267, "chiplets_rows": 25291, - "poseidon2_permutation_rows": 49456, - "range_rows": 3957, + "poseidon2_permutation_rows": 49472, + "range_rows": 3963, "chiplets_shape": { "hasher_rows": 16432, "bitwise_rows": 3456, @@ -931,14 +931,14 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 30378, - "auth_procedure": 14000 + "total": 30393, + "auth_procedure": 14015 }, "trace": { - "core_rows": 155838, + "core_rows": 155853, "chiplets_rows": 73770, - "poseidon2_permutation_rows": 118752, - "range_rows": 6097, + "poseidon2_permutation_rows": 118768, + "range_rows": 6217, "chiplets_shape": { "hasher_rows": 59112, "bitwise_rows": 4016, @@ -956,14 +956,14 @@ }, "tx_script_processing": 41, "epilogue": { - "total": 21450, - "auth_procedure": 14000 + "total": 21465, + "auth_procedure": 14015 }, "trace": { - "core_rows": 91050, + "core_rows": 91065, "chiplets_rows": 42941, - "poseidon2_permutation_rows": 56560, - "range_rows": 5049, + "poseidon2_permutation_rows": 56576, + "range_rows": 5093, "chiplets_shape": { "hasher_rows": 32096, "bitwise_rows": 4016, diff --git a/crates/miden-agglayer/src/costs/table.rs b/crates/miden-agglayer/src/costs/table.rs index 62f12a6b30..cab0ead0e5 100644 --- a/crates/miden-agglayer/src/costs/table.rs +++ b/crates/miden-agglayer/src/costs/table.rs @@ -2,11 +2,11 @@ // Values are maxima across the benchmarked paths; see `miden_standards::note::costs` for the // caveats on what they do and do not cover. -/// Cycles of consuming a CLAIM note: L1 origin 55238, L2 origin 65208 (maximum). -pub const CLAIM_CONSUMPTION_CYCLES: u32 = 65208; +/// Cycles of consuming a CLAIM note: L1 origin 55253, L2 origin 65223 (maximum). +pub const CLAIM_CONSUMPTION_CYCLES: u32 = 65223; -/// Cycles of consuming a B2AGG note: empty frontier 155794 (maximum), 2^31-1 leaves 91006. -pub const B2AGG_CONSUMPTION_CYCLES: u32 = 155794; +/// Cycles of consuming a B2AGG note: empty frontier 155809 (maximum), 2^31-1 leaves 91021. +pub const B2AGG_CONSUMPTION_CYCLES: u32 = 155809; /// Cycles of consuming a CONFIG_AGG_BRIDGE note (single benchmarked path). pub const CONFIG_AGG_BRIDGE_CONSUMPTION_CYCLES: u32 = 35352; diff --git a/crates/miden-standards/asm/standards/fee/mod.masm b/crates/miden-standards/asm/standards/fee/mod.masm index 893e6e37f0..4ca274080c 100644 --- a/crates/miden-standards/asm/standards/fee/mod.masm +++ b/crates/miden-standards/asm/standards/fee/mod.masm @@ -46,7 +46,7 @@ const SPONSORSHIP_WALK_PER_OUTPUT_NOTE_CYCLES = 512 const SPONSORSHIP_NOTE_CYCLES = 16384 # Local memory offset at which `pay_fee` keeps the sponsorship price table, sized for -# fees::SPONSORSHIP_PRICE_TABLE_NUM_ENTRIES elements. +# fees::SPONSORSHIP_PRICE_TABLE_NUM_ELEMENTS elements. const SPONSORSHIP_PRICE_TABLE_LOC = 0 # Estimated upper bounds on the number of kernel epilogue cycles spent after the auth procedure @@ -366,9 +366,9 @@ end #! Where: #! - FEE_ASSET_ID is the ID of the native fee asset (see tx::get_fee_asset_id), which funds the #! sponsorship notes. -#! - price_table_ptr is the address of the sponsorship price table the estimate fills and the -#! payment reads back: fees::SPONSORSHIP_PRICE_TABLE_NUM_ENTRIES elements of the caller's local -#! memory (see fees::estimate_network_note_sponsorships). +#! - price_table_ptr is the word-aligned address of the sponsorship price table the estimate fills +#! and the payment reads back: fees::SPONSORSHIP_PRICE_TABLE_NUM_ELEMENTS elements of the caller's +#! local memory (see fees::estimate_network_note_sponsorships). #! - num_extra_cycles is the estimated number of cycles the caller spends after this call until #! the end of the authentication procedure. #! - fee_amount is the computed fee of the transaction, denominated in the native fee asset. @@ -438,7 +438,7 @@ end #! - the maximum number of output notes is exceeded. #! #! Invocation: exec -@locals(1024) +@locals(4096) pub proc pay_fee(num_extra_cycles: felt, conversion_info: ConversionInfo) -> felt # the estimate fills the price table and the payment reads it back locaddr.SPONSORSHIP_PRICE_TABLE_LOC dup diff --git a/crates/miden-standards/asm/standards/fees/mod.masm b/crates/miden-standards/asm/standards/fees/mod.masm index d754f04bb8..8ce2a36873 100644 --- a/crates/miden-standards/asm/standards/fees/mod.masm +++ b/crates/miden-standards/asm/standards/fees/mod.masm @@ -8,8 +8,8 @@ # # `estimate_network_note_sponsorships` walks the transaction's output notes and, for every note # targeted at a network account, asks that account's fee policy what the note costs, recording the -# price in a caller-provided table so a caller can charge for the FEE_SPONSORSHIP notes before it -# pays for them. +# fee asset value in a caller-provided table so a caller can charge for the FEE_SPONSORSHIP notes +# before it pays for them. # # `create_network_note_sponsorships` walks the same notes and creates the paired FEE_SPONSORSHIP note # for every note the table prices above zero, funded from the native account's vault. @@ -67,11 +67,11 @@ const WORD_ALIGNMENT_MASK = 0xFFFFFFFC const SPONSORSHIP_STORAGE_LOC = 0 const SPONSORSHIP_FEATURE_NOTE_ID_LOC = SPONSORSHIP_STORAGE_LOC + FEATURE_NOTE_ID_ITEM_OFFSET -# Number of entries in the sponsorship price table that `estimate_network_note_sponsorships` fills -# and `create_network_note_sponsorships` reads, indexed by output note index. Sized for the -# transaction kernel's MAX_OUTPUT_NOTES_PER_TX limit; callers reserve this many elements of local -# memory for it. -pub const SPONSORSHIP_PRICE_TABLE_NUM_ENTRIES = 1024 +# Number of elements in the sponsorship price table that `estimate_network_note_sponsorships` fills +# and `create_network_note_sponsorships` reads: one fee asset value word per output note, indexed +# by output note index. Sized for the transaction kernel's MAX_OUTPUT_NOTES_PER_TX limit; callers +# reserve this many elements of local memory for it. +pub const SPONSORSHIP_PRICE_TABLE_NUM_ELEMENTS = 1024 * WORD_NUM_ELEMENTS # Local memory offsets at which `process_network_note_sponsorship` caches the expected fee asset ID # and the address of the note's price table entry. @@ -315,8 +315,9 @@ end #! Where: #! - FEE_ASSET_ID is the ID of the asset the sponsorship notes are funded with, typically read #! from the fee manager's fee asset ID slot. -#! - price_table_ptr is the address of a table of SPONSORSHIP_PRICE_TABLE_NUM_ENTRIES elements in -#! the caller's local memory; only the entries of network output notes are written. +#! - price_table_ptr is the word-aligned address of a table of SPONSORSHIP_PRICE_TABLE_NUM_ELEMENTS +#! elements in the caller's local memory, holding one fee asset value word per output note; only +#! the entries of network output notes are written. #! - num_sponsorship_notes is the number of notes `create_network_note_sponsorships` will create. #! - num_output_notes is the number of output notes the transaction has at this point, i.e. the #! notes `create_network_note_sponsorships` will walk. @@ -609,7 +610,8 @@ end #! #! Where: #! - FEE_ASSET_ID is the ID of the asset the sponsorship notes are funded with. -#! - price_table_ptr is the address of the price table, indexed by output note index. +#! - price_table_ptr is the word-aligned address of the price table, one fee asset value word per +#! output note. #! - create_notes is 1 to create the sponsorship notes from the table, 0 to fill the table. #! - total_sponsored_fee_amount is the total amount the sponsorship notes carry. #! - num_sponsorship_notes is the number of network notes priced above zero, i.e. of sponsorship @@ -705,9 +707,9 @@ end #! Prices the network output note at the given index into the price table or, optionally, creates #! its FEE_SPONSORSHIP note from it. #! -#! With create_notes = 0 the note is priced through its target account's fee policy and the price -#! is recorded in the note's price table entry. With create_notes = 1 the recorded price is read -#! back and, if it is non-zero, the sponsorship note is created and funded with that amount of the +#! With create_notes = 0 the note is priced through its target account's fee policy and the fee +#! asset value is recorded in the note's price table entry. With create_notes = 1 the recorded value +#! is read back and, if it is non-zero, the sponsorship note is created and funded with it in the #! expected fee asset. A network note the target account prices to zero needs no sponsorship note, #! so none is created for it. #! @@ -718,7 +720,8 @@ end #! - note_idx is the index of the network output note to sponsor. #! - attachment_idx is the index of the note's network account target attachment. #! - EXPECTED_FEE_ASSET_ID is the ID of the asset the sponsorship note must be funded with. -#! - price_table_ptr is the address of the price table, indexed by output note index. +#! - price_table_ptr is the word-aligned address of the price table, one fee asset value word per +#! output note. #! - create_notes is 1 to create the sponsorship note from the table, 0 to price the note into it. #! - sponsored_fee_amount is the amount the sponsorship note carries, or zero if the target prices #! the note to zero and so it needs none. @@ -745,30 +748,30 @@ proc process_network_note_sponsorship( movdn.5 movdn.5 loc_storew_le.EXPECTED_FEE_ASSET_ID_LOC dropw # => [note_idx, attachment_idx, price_table_ptr, create_notes] - # cache the address of the note's price table entry - movup.2 dup.1 add loc_store.PRICE_ENTRY_PTR_LOC + # cache the address of the note's price table entry, one word per output note + movup.2 dup.1 mul.WORD_NUM_ELEMENTS add loc_store.PRICE_ENTRY_PTR_LOC # => [note_idx, attachment_idx, create_notes] movup.2 # => [create_notes, note_idx, attachment_idx] if.true - loc_load.PRICE_ENTRY_PTR_LOC mem_load - # => [sponsored_fee_amount, note_idx, attachment_idx] + padw loc_load.PRICE_ENTRY_PTR_LOC mem_loadw_le + # => [FEE_ASSET_VALUE, note_idx, attachment_idx] dup eq.0 if.true # a note priced to zero needs no sponsorship note - movdn.2 drop drop - # => [sponsored_fee_amount] + dropw drop drop push.0 + # => [sponsored_fee_amount = 0] else - movdn.2 dup movdn.3 - # => [note_idx, attachment_idx, sponsored_fee_amount, note_idx] + movup.5 movup.5 dup movdn.6 + # => [note_idx, attachment_idx, FEE_ASSET_VALUE, note_idx] exec.read_network_target_id drop - # => [target_id_prefix, sponsored_fee_amount, note_idx] + # => [target_id_prefix, FEE_ASSET_VALUE, note_idx] - swap dup movdn.3 exec.fungible_asset::create_value + movdn.4 dup movdn.6 # => [FEE_ASSET_VALUE, target_id_prefix, note_idx, sponsored_fee_amount] padw loc_loadw_le.EXPECTED_FEE_ASSET_ID_LOC @@ -781,11 +784,11 @@ proc process_network_note_sponsorship( exec.compute_sponsorship_fee # => [FEE_ASSET_ID, FEE_ASSET_VALUE, target_id_prefix] - exec.fungible_asset::to_amount_unchecked - # => [sponsored_fee_amount, FEE_ASSET_ID, FEE_ASSET_VALUE, target_id_prefix] + # record the fee asset value for the payment + swapw loc_load.PRICE_ENTRY_PTR_LOC mem_storew_le swapw + # => [FEE_ASSET_ID, FEE_ASSET_VALUE, target_id_prefix] - # record the price for the payment - dup loc_load.PRICE_ENTRY_PTR_LOC mem_store + exec.fungible_asset::to_amount_unchecked # => [sponsored_fee_amount, FEE_ASSET_ID, FEE_ASSET_VALUE, target_id_prefix] movdn.9 swapw dropw movup.4 drop From 92e00db29b316f369c9103470b4d89920123342a Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Thu, 3 Sep 2026 13:54:45 +0000 Subject: [PATCH 11/12] chore(standards): size the sponsorship cycle margins from a measurement With the pricing call gone from the payment tail, a sponsorship note costs about 4300 cycles to create and each walked output note about 400, measured by diffing the auth procedure cycles of transactions with one to three plain, zero-priced and sponsored network notes. SPONSORSHIP_NOTE_CYCLES drops from 16384 to 8192; the walk margin stays. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01KkQzFtRjyDwQ7iVtRbsHsn --- crates/miden-standards/asm/standards/fee/mod.masm | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/miden-standards/asm/standards/fee/mod.masm b/crates/miden-standards/asm/standards/fee/mod.masm index 4ca274080c..e86be80b75 100644 --- a/crates/miden-standards/asm/standards/fee/mod.masm +++ b/crates/miden-standards/asm/standards/fee/mod.masm @@ -36,14 +36,15 @@ const PAY_FEE_CYCLES = 8192 # compute_fee call returns: a walk over every output note looking for network notes, then funding # a FEE_SPONSORSHIP note for each one priced above zero, which covers reading the target # attachment, the note creation and the vault withdrawal. The pricing itself runs during the -# estimate, before compute_fee, and is measured by the kernel. +# estimate, before compute_fee, and is measured by the kernel. Measured by diffing the auth +# procedure cycles of transactions with one to three plain, zero-priced and sponsored network +# notes: about 400 cycles per walked note and about 4300 per sponsorship note created. # # NOT covered by a regression test: zeroing both leaves the suite green, because the sponsoring -# case lands exactly on an ilog2 boundary either way. They are margins over a measurement, so a -# test would have to assert cycle counts rather than behaviour. SPONSORSHIP_NOTE_CYCLES was sized -# while the payment still repeated the pricing call, so it is generous. +# cases land inside the same ilog2 bucket either way. They are margins over a measurement, so a +# test would have to assert cycle counts rather than behaviour. Re-measure if the payment changes. const SPONSORSHIP_WALK_PER_OUTPUT_NOTE_CYCLES = 512 -const SPONSORSHIP_NOTE_CYCLES = 16384 +const SPONSORSHIP_NOTE_CYCLES = 8192 # Local memory offset at which `pay_fee` keeps the sponsorship price table, sized for # fees::SPONSORSHIP_PRICE_TABLE_NUM_ELEMENTS elements. From 29739e239b5ce6cabba1e0d5147f8d7ac1003078 Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Thu, 3 Sep 2026 14:54:26 +0000 Subject: [PATCH 12/12] test(standards): guard the sponsorship cycle margins Asserts the measured creation cost of one sponsorship note stays within SPONSORSHIP_NOTE_CYCLES and the cost of walking one more output note through both passes within twice the per-note walk margin, mirroring the constants next to the other fee-flow mirrors. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01KkQzFtRjyDwQ7iVtRbsHsn --- .../asm/standards/fee/mod.masm | 9 +- .../tests/auth/fee_payment/mod.rs | 6 +- .../tests/auth/fee_payment/sponsorship.rs | 89 +++++++++++++++++-- 3 files changed, 88 insertions(+), 16 deletions(-) diff --git a/crates/miden-standards/asm/standards/fee/mod.masm b/crates/miden-standards/asm/standards/fee/mod.masm index e86be80b75..630660db8b 100644 --- a/crates/miden-standards/asm/standards/fee/mod.masm +++ b/crates/miden-standards/asm/standards/fee/mod.masm @@ -36,13 +36,10 @@ const PAY_FEE_CYCLES = 8192 # compute_fee call returns: a walk over every output note looking for network notes, then funding # a FEE_SPONSORSHIP note for each one priced above zero, which covers reading the target # attachment, the note creation and the vault withdrawal. The pricing itself runs during the -# estimate, before compute_fee, and is measured by the kernel. Measured by diffing the auth +# estimate, before compute_fee, and is charged by the kernel. Measured by diffing the auth # procedure cycles of transactions with one to three plain, zero-priced and sponsored network -# notes: about 400 cycles per walked note and about 4300 per sponsorship note created. -# -# NOT covered by a regression test: zeroing both leaves the suite green, because the sponsoring -# cases land inside the same ilog2 bucket either way. They are margins over a measurement, so a -# test would have to assert cycle counts rather than behaviour. Re-measure if the payment changes. +# notes: about 800 cycles per walked note over both passes, of which the payment's walk is about +# half, and about 4300 per sponsorship note created. Guarded by the fee-payment regression tests. const SPONSORSHIP_WALK_PER_OUTPUT_NOTE_CYCLES = 512 const SPONSORSHIP_NOTE_CYCLES = 8192 diff --git a/crates/miden-testing/tests/auth/fee_payment/mod.rs b/crates/miden-testing/tests/auth/fee_payment/mod.rs index e00ffb8b1e..81421cd4a4 100644 --- a/crates/miden-testing/tests/auth/fee_payment/mod.rs +++ b/crates/miden-testing/tests/auth/fee_payment/mod.rs @@ -25,12 +25,14 @@ const VERIFICATION_BASE_FEE: u32 = 500; // that matters. // // Must be kept in sync with `miden::standards::auth::signature` (per-scheme estimates and -// `MULTISIG_AUTH_BASE_CYCLES`) and `miden::standards::fee` (`PAY_FEE_CYCLES` and the epilogue -// margins). +// `MULTISIG_AUTH_BASE_CYCLES`) and `miden::standards::fee` (`PAY_FEE_CYCLES`, the sponsorship +// margins and the epilogue margins). const ECDSA_K256_KECCAK_AUTH_CYCLES: usize = 8000; const FALCON_512_POSEIDON2_AUTH_CYCLES: usize = 80000; const MULTISIG_AUTH_BASE_CYCLES: usize = 8192; const PAY_FEE_CYCLES: usize = 8192; +const SPONSORSHIP_WALK_PER_OUTPUT_NOTE_CYCLES: usize = 512; +const SPONSORSHIP_NOTE_CYCLES: usize = 8192; const POST_AUTH_EPILOGUE_BASE_CYCLES: usize = 4096; const POST_AUTH_EPILOGUE_PER_NOTE_CYCLES: usize = 512; diff --git a/crates/miden-testing/tests/auth/fee_payment/sponsorship.rs b/crates/miden-testing/tests/auth/fee_payment/sponsorship.rs index 42f0feb810..191c3b14dc 100644 --- a/crates/miden-testing/tests/auth/fee_payment/sponsorship.rs +++ b/crates/miden-testing/tests/auth/fee_payment/sponsorship.rs @@ -28,7 +28,11 @@ use miden_standards::tx_script::SendNotesTransactionScript; use miden_testing::utils::create_spawn_note; use miden_testing::{Auth, MockChain}; -use super::VERIFICATION_BASE_FEE; +use super::{ + SPONSORSHIP_NOTE_CYCLES, + SPONSORSHIP_WALK_PER_OUTPUT_NOTE_CYCLES, + VERIFICATION_BASE_FEE, +}; // CONSTANTS // ================================================================================================ @@ -125,16 +129,37 @@ async fn send_network_note( Ok((executed, network_note, target_id)) } +/// How a P2ID note sent by [`send_notes`] is targeted. +#[derive(Clone, Copy)] +enum NoteTarget { + /// A plain note to a network account, carrying no network account target attachment, so the + /// fee flow never prices it. + Plain, + /// A network note, priced by its target at the given fee. + Network(u64), +} + /// Has a signing wallet send one P2ID network note per entry of `target_fees`, each to its own /// network account pricing the P2ID script root at that fee, paying its fee through `pay_fee`. /// Returns the executed transaction and, in creation order, each network note with its target's ID. async fn send_network_notes( target_fees: &[u64], +) -> anyhow::Result<(ExecutedTransaction, Vec<(Note, AccountId)>)> { + let targets: Vec = + target_fees.iter().map(|fee| NoteTarget::Network(*fee)).collect(); + send_notes(&targets).await +} + +/// Has a signing wallet send one P2ID note per entry of `note_targets`, each to its own network +/// account, paying its fee through `pay_fee`. Returns the executed transaction and, in creation +/// order, each note with its target's ID. +async fn send_notes( + note_targets: &[NoteTarget], ) -> anyhow::Result<(ExecutedTransaction, Vec<(Note, AccountId)>)> { let mut rng = RandomCoin::new(Word::from([1u32, 2, 3, 4])); - // a payload asset issued by a faucet other than the fee faucet, carried by each network note + // a payload asset issued by a faucet other than the fee faucet, carried by each note let payload_asset: Asset = FungibleAsset::mock(50); - let payload_total: Asset = FungibleAsset::mock(50 * target_fees.len() as u64); + let payload_total: Asset = FungibleAsset::mock(50 * note_targets.len() as u64); let mut builder = MockChain::builder().verification_base_fee(VERIFICATION_BASE_FEE); @@ -147,11 +172,15 @@ async fn send_network_notes( // each target network account prices the P2ID script root at its own fee let mut targets = Vec::new(); - for (i, target_fee) in target_fees.iter().enumerate() { + for (i, note_target) in note_targets.iter().enumerate() { + let target_fee = match note_target { + NoteTarget::Plain => 0, + NoteTarget::Network(fee) => *fee, + }; let target = network_account( [2 + i as u8; 32], [P2idNote::script_root(), FeeSponsorshipNote::script_root()], - &[(P2idNote::script_root(), *target_fee)], + &[(P2idNote::script_root(), target_fee)], [], SponsorshipPolicy::default(), )?; @@ -162,11 +191,24 @@ async fn send_network_notes( let mut mock_chain = builder.build()?; mock_chain.prove_next_block()?; - // the sponsor creates the P2ID network notes via a send-notes transaction script + // the sponsor creates the P2ID notes via a send-notes transaction script let network_notes = targets .iter() - .map(|target| p2id_network_note(sponsor.id(), target.id(), payload_asset, &mut rng)) - .collect::>>()?; + .zip(note_targets) + .map(|(target, note_target)| match note_target { + NoteTarget::Plain => Ok(P2idNote::builder() + .sender(sponsor.id()) + .target(target.id()) + .asset(payload_asset) + .note_type(NoteType::Public) + .serial_number(rng.draw_word()) + .build()? + .into()), + NoteTarget::Network(_) => { + p2id_network_note(sponsor.id(), target.id(), payload_asset, &mut rng) + }, + }) + .collect::>>()?; let partial_notes: Vec = network_notes.iter().cloned().map(PartialNote::from).collect(); let tx_script = @@ -324,6 +366,37 @@ async fn pay_fee_sponsors_each_network_note_at_its_own_price() -> anyhow::Result Ok(()) } +/// The sponsorship cycle margins are upper bounds of the measured cost of the payment pass, which +/// runs after `compute_fee` and so is estimated rather than charged by the kernel. +/// +/// A sponsored and a zero-priced network note are priced alike, so their difference isolates the +/// creation of one sponsorship note. One more plain output note costs the walk of both passes, of +/// which only the payment's is budgeted, so its bound is conservative. +#[tokio::test] +async fn sponsorship_cycle_margins_are_upper_bounds() -> anyhow::Result<()> { + let (zero_priced, _) = send_notes(&[NoteTarget::Network(0)]).await?; + let (sponsored, _) = send_notes(&[NoteTarget::Network(FEE_AMOUNT)]).await?; + let sponsorship_note_cycles = + sponsored.measurements().auth_procedure - zero_priced.measurements().auth_procedure; + assert!( + sponsorship_note_cycles <= SPONSORSHIP_NOTE_CYCLES, + "creating a sponsorship note took {sponsorship_note_cycles} cycles, exceeding the margin \ + of {SPONSORSHIP_NOTE_CYCLES}", + ); + + let (one_plain, _) = send_notes(&[NoteTarget::Plain]).await?; + let (two_plain, _) = send_notes(&[NoteTarget::Plain, NoteTarget::Plain]).await?; + let walk_cycles = + two_plain.measurements().auth_procedure - one_plain.measurements().auth_procedure; + assert!( + walk_cycles <= 2 * SPONSORSHIP_WALK_PER_OUTPUT_NOTE_CYCLES, + "walking one more output note took {walk_cycles} cycles over both passes, exceeding twice \ + the margin of {SPONSORSHIP_WALK_PER_OUTPUT_NOTE_CYCLES}", + ); + + Ok(()) +} + /// End-to-end single hop: a local wallet creates a P2ID network note (and, via `pay_fee`, its /// FEE_SPONSORSHIP note); the target network account then consumes both, and its /// `collect_sponsored_fees` credits the prepaid fee into its vault.