From a05e38abdc1155bba5e89e443f00111e1e60b233 Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Wed, 11 Mar 2026 10:52:20 +0000 Subject: [PATCH 01/17] feat(agglayer): store and use metadata hash for bridge-out leaves The metadata_hash field in bridge-out leaves was previously zeroed, which would cause EVM-side claim verification to fail (no preimage for bytes32(0) under keccak256). This stores a pre-computed keccak256(abi.encode(name, symbol, decimals)) in faucet storage and reads it via FPI during bridge-out leaf construction. Changes: - Add MetadataHash::from_token_info and encode_token_metadata helpers with Solidity ABI encoding compatibility (verified against test vectors) - Add metadata_hash_lo/hi storage slots to AggLayerFaucet - Add get_metadata_hash FPI-callable procedure to faucet MASM - Update bridge_out.masm to fetch metadata hash via FPI instead of pushing zeros - Export get_metadata_hash from faucet component - Regenerate Solidity MMR test vectors with real metadata hash - Add TODO in register_faucet for future on-chain verification Closes #2453 Co-Authored-By: Claude Opus 4.6 (1M context) --- Cargo.lock | 10 ++ Cargo.toml | 1 + crates/miden-agglayer/Cargo.toml | 1 + .../asm/agglayer/bridge/bridge_config.masm | 4 + .../asm/agglayer/bridge/bridge_out.masm | 28 +++- .../asm/agglayer/faucet/mod.masm | 36 +++++ .../miden-agglayer/asm/components/faucet.masm | 1 + .../test-vectors/mmr_frontier_vectors.json | 128 ++++++++--------- .../solidity-compat/test/MMRTestVectors.t.sol | 3 +- .../src/eth_types/metadata_hash.rs | 133 ++++++++++++++++++ crates/miden-agglayer/src/lib.rs | 54 ++++++- .../miden-testing/tests/agglayer/bridge_in.rs | 1 + .../tests/agglayer/bridge_out.rs | 5 + 13 files changed, 334 insertions(+), 71 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d60f168d6e..f3d6acdfcb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1395,6 +1395,7 @@ dependencies = [ "primitive-types", "regex", "thiserror", + "tiny-keccak", "walkdir", ] @@ -2912,6 +2913,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinytemplate" version = "1.2.1" diff --git a/Cargo.toml b/Cargo.toml index ad9288015a..9068a749b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,4 +74,5 @@ rand_chacha = { default-features = false, version = "0.9" } rstest = { version = "0.26" } serde = { default-features = false, version = "1.0" } thiserror = { default-features = false, version = "2.0" } +tiny-keccak = { default-features = false, features = ["keccak"], version = "2.0" } tokio = { default-features = false, features = ["sync"], version = "1" } diff --git a/crates/miden-agglayer/Cargo.toml b/crates/miden-agglayer/Cargo.toml index 70ca24bc7a..f2ffa8efbd 100644 --- a/crates/miden-agglayer/Cargo.toml +++ b/crates/miden-agglayer/Cargo.toml @@ -31,6 +31,7 @@ miden-utils-sync = { workspace = true } # Third-party dependencies primitive-types = { workspace = true } thiserror = { workspace = true } +tiny-keccak = { workspace = true } [dev-dependencies] miden-agglayer = { features = ["testing"], path = "." } diff --git a/crates/miden-agglayer/asm/agglayer/bridge/bridge_config.masm b/crates/miden-agglayer/asm/agglayer/bridge/bridge_config.masm index 2e2d80da89..4bd1030587 100644 --- a/crates/miden-agglayer/asm/agglayer/bridge/bridge_config.masm +++ b/crates/miden-agglayer/asm/agglayer/bridge/bridge_config.masm @@ -106,6 +106,10 @@ end #! #! Panics if the note sender is not the bridge admin. #! +#! TODO: Once the token name is available in faucet storage and abi.encode(string, string, uint8) +#! is implemented in MASM, verify the faucet's metadata_hash against the stored name/symbol/decimals +#! during registration to ensure correctness. +#! #! Inputs: [faucet_id_prefix, faucet_id_suffix, pad(14)] #! Outputs: [pad(16)] #! diff --git a/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm b/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm index 1383bf5d1f..aa152ecccf 100644 --- a/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm +++ b/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm @@ -144,12 +144,32 @@ pub proc bridge_out exec.write_address_to_memory # => [] - # TODO construct metadata hash - padw padw - # => [METADATA_HASH[8]] + # Fetch metadata hash from the faucet via FPI. + # Reload asset to extract faucet ID for the FPI call. + loc_loadw_be.BRIDGE_OUT_BURN_ASSET_LOC + # => [ASSET] + # ASSET layout: [faucet_id_prefix, faucet_id_suffix, 0, amount] + + # Extract faucet ID, drop padding and amount + movup.2 drop movup.2 drop + # => [faucet_id_prefix, faucet_id_suffix] + + procref.agglayer_faucet::get_metadata_hash + # => [PROC_MAST_ROOT(4), faucet_id_prefix, faucet_id_suffix] + + movup.5 movup.5 + # => [faucet_id_prefix, faucet_id_suffix, PROC_MAST_ROOT(4)] + + exec.tx::execute_foreign_procedure + # => [METADATA_HASH_LO(4), METADATA_HASH_HI(4), pad(8)] + + # Drop 8 trailing padding elements + swapdw dropw dropw + # => [METADATA_HASH_LO(4), METADATA_HASH_HI(4)] + push.LEAF_DATA_START_PTR push.METADATA_HASH_OFFSET add movdn.8 - # => [METADATA_HASH[8], metadata_hash_ptr] + # => [METADATA_HASH_LO(4), METADATA_HASH_HI(4), metadata_hash_ptr] exec.utils::mem_store_double_word_unaligned # Explicitly zero the 3 padding felts after METADATA_HASH for diff --git a/crates/miden-agglayer/asm/agglayer/faucet/mod.masm b/crates/miden-agglayer/asm/agglayer/faucet/mod.masm index d3b2913627..6b88d05bab 100644 --- a/crates/miden-agglayer/asm/agglayer/faucet/mod.masm +++ b/crates/miden-agglayer/asm/agglayer/faucet/mod.masm @@ -29,6 +29,11 @@ const CONVERSION_INFO_1_SLOT = word("miden::agglayer::faucet::conversion_info_1" # Slot 2: [addr_felt4, origin_network, scale, 0] — remaining address felt + origin network + scale const CONVERSION_INFO_2_SLOT = word("miden::agglayer::faucet::conversion_info_2") +# Storage slots for the pre-computed metadata hash (keccak256 of ABI-encoded token metadata). +# The 32-byte hash is split across two value slots, each holding 4 u32 felts. +const METADATA_HASH_LO_SLOT = word("miden::agglayer::faucet::metadata_hash_lo") +const METADATA_HASH_HI_SLOT = word("miden::agglayer::faucet::metadata_hash_hi") + # Memory pointers for piped advice map data const PROOF_DATA_START_PTR = 0 const LEAF_DATA_START_PTR = 536 @@ -139,6 +144,37 @@ pub proc get_scale # => [scale] end +#! Returns the pre-computed metadata hash (8 u32 felts) from faucet storage. +#! +#! The metadata hash is `keccak256(abi.encode(name, symbol, decimals))` and is stored +#! across two value slots (lo and hi, 4 felts each). +#! +#! Inputs: [pad(16)] +#! Outputs: [METADATA_HASH_LO(4), METADATA_HASH_HI(4), pad(8)] +#! +#! Invocation: call +pub proc get_metadata_hash + push.METADATA_HASH_LO_SLOT[0..2] + exec.active_account::get_item + # => [lo3, lo2, lo1, lo0, pad(16)] + + exec.word::reverse + # => [lo0, lo1, lo2, lo3, pad(16)] + + push.METADATA_HASH_HI_SLOT[0..2] + exec.active_account::get_item + # => [hi3, hi2, hi1, hi0, lo0, lo1, lo2, lo3, pad(16)] + + exec.word::reverse + # => [hi0, hi1, hi2, hi3, lo0, lo1, lo2, lo3, pad(16)] + + # Rearrange: move hi below lo + swapw + # => [lo0, lo1, lo2, lo3, hi0, hi1, hi2, hi3, pad(16)] + + exec.sys::truncate_stack +end + #! Converts a native Miden asset amount to origin asset data using the stored #! conversion metadata (origin_token_address, origin_network, and scale). #! diff --git a/crates/miden-agglayer/asm/components/faucet.masm b/crates/miden-agglayer/asm/components/faucet.masm index 641b6089e7..36eef80744 100644 --- a/crates/miden-agglayer/asm/components/faucet.masm +++ b/crates/miden-agglayer/asm/components/faucet.masm @@ -7,4 +7,5 @@ pub use ::miden::agglayer::faucet::claim pub use ::miden::agglayer::faucet::asset_to_origin_asset +pub use ::miden::agglayer::faucet::get_metadata_hash pub use ::miden::agglayer::faucet::burn diff --git a/crates/miden-agglayer/solidity-compat/test-vectors/mmr_frontier_vectors.json b/crates/miden-agglayer/solidity-compat/test-vectors/mmr_frontier_vectors.json index 79c76364dc..46501a0934 100644 --- a/crates/miden-agglayer/solidity-compat/test-vectors/mmr_frontier_vectors.json +++ b/crates/miden-agglayer/solidity-compat/test-vectors/mmr_frontier_vectors.json @@ -136,72 +136,72 @@ 1181416010 ], "leaves": [ - "0xe460585d9b2385592b26a34d6908ea58165586cb39e5e6cb365b68246d29d7f8", - "0x5a7295b074b2ffeb07bd8bacbdd97aa97b0b269db43779112ef24b52548a9a2a", - "0xde239e1e8b54de83c9b0e3f32c269b265dd0efcda92c93a2146f44302e604080", - "0x98681050a4c0e39d25f1a44d95b343a05f7139cc882f628c569b3a1ae889f0e6", - "0xd3d70b40cc2a71e9a4996a2afaabcafe93af95ba9de147e3835ccddba2d82fdd", - "0xd46fec5943f6d40c9a68076fbc325daf7763607aaa60ca9be297cade5a1efca5", - "0x4c54e0aab6332cea9a9f867933caee83c6167aa78f663129d10e56cee35aacdd", - "0xf487aba0c467c53aa4fc9a7319817e1448efd774dedb235a1ab95a5dd2592d21", - "0xc734b7fd5abe87f4dff06da98980e19894117e92738e27e8dc0826eb4dee7202", - "0x8bcc65728c792dfaa58c6b63d192c2e37cd3db7c62774e7b40b9b3232597073a", - "0x6dbb052d9082cf78a0464cae809cd6c1be9d5657fc75a0fa1efade46f047aa01", - "0x11ea20a8fb14ed8b5ba47e83935f4dc1c032be3a3a9895a65fabed6e1adbef5c", - "0xd108801a4cfa732a19995a6f930ccdda98e91ce393f55eae7f63781568b44c74", - "0x423b7a7716ba307d27c05a6bbfde03b35c9544dffcf6702f69a205cff40a51da", - "0xed832ce8f80ed861bd13b1104490724dd38ab1c9ff18fd8e02ad13eb287af68f", - "0x6eca57794d8d55ec934427971898952017d87bd2773b64c554629f32f55fc7dc", - "0xc7abf795f5ebe46e9f86ba72d58f38ef535475cc41a11913fa1ec51cf902ee1a", - "0xbffebb2a3584cb6f96af4f8da6f5eea2e64066f0caa4bc6f44abb69b621a2b79", - "0x04de39dc7a9f11eba923271d07b5fda4f6b38012858a9a5a9d8f6557706981bd", - "0xef5e2f249fce6c67f5483b52e87384c6a6f6b5f8f102ecfede50cc9f8dfa78af", - "0x34e1511b36260dd619fcb205311055b87d31bb6440c9fb2a8b272bc1dcb1d699", - "0x0640b605ee9f8d8b38118c8dd1f51ca30f3b3f9037c29e598f39b91326825c46", - "0xfe7de1151f56cc10894b6bd63fc995a741c54d9069ee97247cb28627a4838da8", - "0xf17bb6827fe8873b839ecefe872776f757ca087dd65c2c2882523b71dcd24f05", - "0x7a11106b01c8d98348739c89007dddca673f18e9c38ef2d953315a1a49b23ce0", - "0xa7f0a37834fab9ce2cfbe364ccc4c50c88d48a061f0901889cc4fdc6b088a3ea", - "0xb386fae6a43e096a3d66147212a4fc756f7ed921febb2404f1d060111e4521e8", - "0x98484766860a98231a6834276f1ca84c8cf381e4931d635268b9b7d9db976958", - "0xd5007290e81283abd144a619da55be689e7b3eeb8a8b79f0de5e1f2793b056fe", - "0xac6812ede94056979e789ac4bd7dc5e4e682ea93aaaa1aadb22645ec44e21772", - "0xdc0662d88af437d468ed541ee9088464770bbd149a5ce5b3cdd9e836888c5b9d", - "0x6c8e78ff6214e87c5a791423385e31659921f3bb09376b302dd3933f98f346b4" + "0x583cc77fc2b7280dae7433767e49a7c6d9a33f0410e179814f3aa1dbed9e5383", + "0x39b63728fe06dbc9e883852005cce44a1e6515ed55e8b1dbf3b6758179716f11", + "0xc4971cb8c3f11aedc287a9855739bb007822038c054cb6808e03131c9f91a0f1", + "0x629eb6a1e17d6aea8011061da05909d2f7312467aa8f32738861fb940b157174", + "0xf405ea66eca447509b4ffc555fb9dbcae535b11e55a4331d02819e0ca9984575", + "0xfaa2e8faa0081b6534e90f6fe58e9c5232afe98bcc9f1695e544e02f3569463d", + "0xb89ca15ba6ca7c7a208e24d7353ad31282ef134662f659fac32f27df2ae3320f", + "0x79bdf5742cc5cc4ef8f888a231e367c50b4430a9459541facb343111c92e6bf8", + "0x0822a3dc7f0c51e70dd73014e18df2981c4bde688eec541581558c3de0ad6f65", + "0xbd91e0fa090c5a988b4af55366454b0e66f565f313127d4775bb44e446baa917", + "0xfd3fe60322ffefecdd2e5b9b1ccc99f335dcb63c48bdd4c0694978ff64554abb", + "0xb8f5374c52ea2b64d00f566b798a42fabe4405817327b361cd2e57b17949917a", + "0xd84e6a9f537e1e71ef75ee1b4c9aecaa4f192b65fd3b2c5a276aa82193196c00", + "0xaa746c560d044f6c4ddab4a0553cde8fe6aa95478fe198bcb4b0b9ad3c3b92af", + "0xf9bf642edac2a5f80a899ab3a91aefb6d9afbaf107fa34557c9dc66c6bed4611", + "0xf3b649080b6f226a027260cef334003468ddd40f70f8268b8019613f30f31429", + "0x2e8c2d56396ac75cf085c44ad3939b83f15b3ef886092faaf26373f9083fc49b", + "0x74c483d10393a141f7d1a6d583c324e7b8293f4d8bfa612cfff0a51a8dcd1ddc", + "0x16693082ba7d19cf38216153780011320b4d22133bb541006542f8b24c0bac29", + "0x7132c9fbb1f7ee387c6cdf1ef1554eaa4b791f0de1c2e858a640f3c0e867b1be", + "0x7b0f681fc08c9193034a590e818206c8972887710115677df57113e9b40823cb", + "0xf9513376461d437192b658deaa647a8625e7354f4d59a778114552feaa8b2e70", + "0x4439d51fd28dc9016bbab806805aa36a53fb9a4f02c379b47656d2b4c45c7b39", + "0x3a44b8129f9468dc743ffd55d2cc0390ed565ebaf8955e38a4e8d41714f874c3", + "0xacfc9d4916104a4d0965d1caa24cdf31fa2cf65474f1986175f49ced505d7470", + "0x34a969176d30df0525c1eb1b349d3a24b1a684f5a6f4cf60797d5d213b7007a0", + "0xe16eb94a82b246fa6534867df6ee6217c8b1c850d835d72548d8f85d1504330d", + "0x188e9c5333cc6d9cd5f8c21a71917e22044b1dc6cdc3241ac9187ccd25598884", + "0x2ca6faff026b921ce865e1161688e7debc733c2d699937ce858783ffbca666d8", + "0x0c2819f9ad1daa7dc7a42c0b8c682091dd77c9aa78bbde349b40efd152843b2c", + "0x53c565760b2e54abcf98f888b83a1178a20f47db78aa048738217c0d3e59937b", + "0xc2668ecfa5198b70c0389bc5b71a70f1e2ffe0be832846e1889ad80ee3a8ef34" ], "origin_token_address": "0x7a6fC3e8b57c6D1924F1A9d0E2b3c4D5e6F70891", "roots": [ - "0xacd6f8510c036081e605dd2c8749d2b7d3b289913514d10af9538cb4b32b7ded", - "0x2d7b622637d38f862a074a0160bc1e54ad7df147ff3374af82777b37021b22e1", - "0xf9bdf29ab9c4cbd2927b759b9f8ddafa90317bdb91f388b8eee08038ff5ded00", - "0x80134ca84d0d742662f3ec22543f4cf33f02dc0b628f51d1df1c521ef3018395", - "0x21d6f3b63306929d624f01ffdbe216acb822bf080bcf04b7e6021db957e7bee4", - "0x7932d55a970d094161976d0b562805779d55a81b08a501983c2b121a0c989a1e", - "0x43f09c6c8a277ee6fbc0e3f8261ba4570f32d1cbfff06bf662aa8e5feeb742bc", - "0x9ae3a76a5c7fcc2af6e3cb937b7e1a4ba397a46029987b06fec29257ba408564", - "0x007e432139766ea419be4aeda41a59e20114c0b772b61e43b3a344fa1c4e1196", - "0xdf60f37334585bc10d67b107b417a14181158ac9828f56a9337684a81e7405d9", - "0xba49ac55a723278ef6cd8f12193a405bc90cd2b6e88f8791f8d48d69fe952104", - "0x4ab8529bce44bcfb8c8e90c9adebebca9e51f44b0e8a048d99bf7717cb58eae7", - "0xf9313f060db170a5287bcc78443267e893f638731dd48a9131b120f9c5833f88", - "0x49a9e6e504f2a6938bbefba42ec2b4930eed298a04eac403af1e0a6286017960", - "0xe318ce76597523c02da0094bcfd970e88c9544c6393d9bfe17d96e2a17f4856d", - "0x00d4099acc3d2a2cdd76f693fb527b218f369bc8e92af4a39328809738497a9d", - "0xf4db3da65c8fda88ad4a1ad1aca66e9260d5230a962791b57d396948a78fe96e", - "0x6813db5a7b4ac98c11d84412df7d6552941d30c7adb95e7025b13d747cf0f3f7", - "0xf1e93cbb96e5fabaee7cbb44f87f44832c9c290a5f85631d8c86493bab6ba0d5", - "0x654a2e78a6e49c969a0fedad0e4372862950ca371406c122779cf62e16dfe7e7", - "0x1a07ce13254cfb6697256a401063d6c43e5a89b8b1945c90bce62c464da1ba27", - "0xedaf2d835d1e6fdd801555835b2cadcd04517f8668f30658019869d0376c6c36", - "0x82adda5fd38a4718f37b2d4fe9fe99b364cced5de9bdfa4c6bdcd118da42c64c", - "0x2d28e62dd13f99153b5e9eb4d68cc1f99a5bd510375f2d1ed522c0062a2d38d7", - "0xd87e80ebe2f69df6735911707780df6b882189db786b5507310249a26d3db69d", - "0x5406d2fbc12edcccd2b8c755b7063ababc760ce23da62032d500a10d49756994", - "0xce99e7d0f9d77226cae034297dfec349d866f892eb753a8c7f5bba4bec52364c", - "0x4419d0e6c47cac3e4fc917f91d878582ed4496bef8e7df219be4d483e496ff0a", - "0xafe2c2b44e58c34576299a201d4918f47d5a48b6fa7a229eaf59e226120b12ac", - "0x9d2989190f9edb660b043a55f3051412280dd7bb7d4d042e3695d3a2b23f5b8d", - "0x18b772e2e093d5f69151c3b6da00d42a2066d1f5980e5f9210ae902f5a5643ca", - "0x6717e563a6c40e1562235c4cbbc2ba0de5be6be07101715e8d3922361b77d394" + "0x8943af888cfe3ea3924601d71a6baacd7b87c826da39e9a682eb285ff5031c1b", + "0x805f8bbc68e3df18db265cfe1fa972faf9e29521978640b34450a8ea8bf7a665", + "0xa0da7520543874392c8332ea2d567ebb4b2b10f6897d34f5404263f0f97b1cb8", + "0x15fd7076633c5dc177f675b9bd39418043177d5cf565f3521a25c502c794f102", + "0x5f3b6ebb3858d5481a1ab0ebc7bf51e66d6dcdaafd861b9e6af088963a5a2282", + "0xce80b3372dd297c3d9cbdce7e0a3b7cf39dabfa03665dc1a2778955935fc06da", + "0xaca8bafe682b461769752c13e7313aa65b83ad1a0019f644a5bdf5e453e1274b", + "0x5453a77465da8cf9803ee6e1ff5960cf96ebd14a2e0cd4299995334ed73e802f", + "0x6bb045a5956579c11c97a673f87292085ba3addda57dcd8c40fbd4db63d8b07f", + "0x4bb0337c1f708b56efc1f0f4279d9ca9c94de2187c406c1619947158d83028ea", + "0x12fac4e9109f79710654946bd345128f8202e403a4ba3fed44efc8d5d0e1cd9e", + "0x2938ec7bc586f16a6cca58e1c3a4b060d135d954649a2abdec9094ebf212fadc", + "0x4d287ab8ab3e87b07528774b18fdb81511402f42d4482bdea43c0a1cbe161128", + "0xa801b801f5018d5e39d7c5aba92e4a4fce13845bab8bf51f198c8165aa20f67d", + "0x343379b96ae9187d4cb8f20706cf4e884f517ef02e3700a9f7500e32f3c14fda", + "0xb18e90e56bbca6ee8a7e653eafef633c01d4e41ef9fdbccfb99c50a5c3ae8f8f", + "0xe2d7f15c9ca938f88337cbcc534475eb6d625cce8fbd24f2389d4874cee21962", + "0x6bb8d1fc420da55e1f42becd9ddd6be0a2b184d7827a410e68314f50bdfc55d5", + "0x4379c4b7761be8fb99d8c33f075d6a8206a15017e7e9a9b41f66eefbac85e99e", + "0x225f99e77a68aad477ae85289bfcf54c86845ab6f5dcb0eea9cb97137a9de128", + "0xcb18011fca44a052414a2f86eb19c63a986868f7cef55f9d1f936e0fd8a1e18a", + "0xbac2a9dbeeeb688616c998fe977f0db04d6021b90fa4e7f0aa1347e8ae8eccef", + "0x94c4018c9810210df4a63db14f1949f6599da6f3c1760efcd4402388a8d9c3b6", + "0x21de8642d818c1ddb0d5f9b5c06a73c1db6c03a753828192a151c08a5e524c80", + "0xd1845fc44e07f7751ab65b05782f1179b5a9212a0f8e980e0e07b56da7663351", + "0xf861aa5ac7127d103e3174753736f3e3110f1317bc1f1c93d638b429ce8a3c9b", + "0xff8c1364e2ff988dbd8780352eeef599341bb010eb48af3019f8540b2b52b90e", + "0xf232b957fa2c9e83120050f8c4324247e12dfbd8f876880383a066f19d018ec6", + "0xd223658da6e25f5362c1abb49484414e4b9594ac7515e0b7d8aabd919866598c", + "0xcf038032ed455f73f04d503cf5796b196dea55d967ec6617f0a1a1623f144ebd", + "0xfa9da8a43eff2ac92f2c3996b2f5b18a92b95a56e61f26bb30ed47122cfc9e9f", + "0x1a17ad0ab073918397c17419deab441d666c0abdeb9f2104c47af4589dd4a2c3" ] } \ No newline at end of file diff --git a/crates/miden-agglayer/solidity-compat/test/MMRTestVectors.t.sol b/crates/miden-agglayer/solidity-compat/test/MMRTestVectors.t.sol index b3b090b471..83a2c5e7d3 100644 --- a/crates/miden-agglayer/solidity-compat/test/MMRTestVectors.t.sol +++ b/crates/miden-agglayer/solidity-compat/test/MMRTestVectors.t.sol @@ -24,7 +24,8 @@ contract MMRTestVectors is Test, DepositContractV2 { uint8 constant LEAF_TYPE = 0; uint32 constant ORIGIN_NETWORK = 64; address constant ORIGIN_TOKEN_ADDR = 0x7a6fC3e8b57c6D1924F1A9d0E2b3c4D5e6F70891; - bytes32 constant METADATA_HASH = bytes32(0); + // keccak256(abi.encode("AGG", "AGG", uint8(8))) + bytes32 constant METADATA_HASH = 0xc98c1a2dbf558d2ddf62e6ad8f93f6abfacf0cf446d095fc3f20b628d1144b3d; // Fixed seed for deterministic "random" destination vectors. // Keeping this constant ensures everyone regenerates the exact same JSON vectors. diff --git a/crates/miden-agglayer/src/eth_types/metadata_hash.rs b/crates/miden-agglayer/src/eth_types/metadata_hash.rs index 287f66e71a..f89c6cddce 100644 --- a/crates/miden-agglayer/src/eth_types/metadata_hash.rs +++ b/crates/miden-agglayer/src/eth_types/metadata_hash.rs @@ -1,7 +1,9 @@ +use alloc::vec; use alloc::vec::Vec; use miden_core_lib::handlers::bytes_to_packed_u32_felts; use miden_protocol::Felt; +use tiny_keccak::{Hasher, Keccak}; // ================================================================================================ // METADATA HASH @@ -11,6 +13,9 @@ use miden_protocol::Felt; /// /// This type provides a typed representation of metadata hashes for the agglayer bridge, /// while maintaining compatibility with the existing MASM processing pipeline. +/// +/// The metadata hash is `keccak256(abi.encode(name, symbol, decimals))` where the encoding +/// follows Solidity's `abi.encode` format for `(string, string, uint8)`. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct MetadataHash([u8; 32]); @@ -20,6 +25,26 @@ impl MetadataHash { Self(bytes) } + /// Computes the metadata hash from raw ABI-encoded metadata bytes. + /// + /// This computes `keccak256(metadata_bytes)`. + pub fn from_abi_encoded(metadata_bytes: &[u8]) -> Self { + let mut hasher = Keccak::v256(); + hasher.update(metadata_bytes); + let mut output = [0u8; 32]; + hasher.finalize(&mut output); + Self(output) + } + + /// Computes the metadata hash from token information. + /// + /// This computes `keccak256(abi.encode(name, symbol, decimals))` matching the Solidity + /// bridge's `getTokenMetadata` encoding. + pub fn from_token_info(name: &str, symbol: &str, decimals: u8) -> Self { + let encoded = encode_token_metadata(name, symbol, decimals); + Self::from_abi_encoded(&encoded) + } + /// Returns the raw 32-byte array. pub const fn as_bytes(&self) -> &[u8; 32] { &self.0 @@ -32,3 +57,111 @@ impl MetadataHash { bytes_to_packed_u32_felts(&self.0) } } + +// ABI ENCODING +// ================================================================================================ + +/// ABI-encodes token metadata as `abi.encode(name, symbol, decimals)`. +/// +/// This produces the same encoding as Solidity's `abi.encode(string, string, uint8)`: +/// - 3 x 32-byte words for offsets/value of each parameter +/// - 32-byte length + padded data for name string +/// - 32-byte length + padded data for symbol string +pub fn encode_token_metadata(name: &str, symbol: &str, decimals: u8) -> Vec { + let name_bytes = name.as_bytes(); + let symbol_bytes = symbol.as_bytes(); + + let name_padded_data_len = pad_to_32(name_bytes.len()); + let symbol_padded_data_len = pad_to_32(symbol_bytes.len()); + + // The 3 head slots (offsets + decimals) take 3 * 32 = 96 bytes + let name_offset: usize = 3 * 32; // 0x60 + let symbol_offset: usize = name_offset + 32 + name_padded_data_len; + + let total_len = symbol_offset + 32 + symbol_padded_data_len; + let mut buf = vec![0u8; total_len]; + + // Write offset to name (big-endian u256) + buf[31] = name_offset as u8; + buf[30] = (name_offset >> 8) as u8; + + // Write offset to symbol (big-endian u256) + buf[63] = symbol_offset as u8; + buf[62] = (symbol_offset >> 8) as u8; + + // Write decimals (big-endian u256, value in last byte) + buf[95] = decimals; + + // Write name: length word + data + let name_len_offset = name_offset; + buf[name_len_offset + 31] = name_bytes.len() as u8; + buf[name_len_offset + 30] = (name_bytes.len() >> 8) as u8; + buf[name_len_offset + 32..name_len_offset + 32 + name_bytes.len()].copy_from_slice(name_bytes); + + // Write symbol: length word + data + let symbol_len_offset = symbol_offset; + buf[symbol_len_offset + 31] = symbol_bytes.len() as u8; + buf[symbol_len_offset + 30] = (symbol_bytes.len() >> 8) as u8; + buf[symbol_len_offset + 32..symbol_len_offset + 32 + symbol_bytes.len()] + .copy_from_slice(symbol_bytes); + + buf +} + +/// Rounds up to the nearest multiple of 32. +fn pad_to_32(len: usize) -> usize { + (len + 31) & !31 +} + +// TESTS +// ================================================================================================ + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_encode_token_metadata_matches_solidity() { + // From solidity-compat/test-vectors/claim_asset_vectors_local_tx.json: + // Token: "Test Token", symbol: "TEST", decimals: 18 + // metadata_hash: 0x4d0d9fb7f9ab2f012da088dc1c228173723db7e09147fe4fea2657849d580161 + let expected_hash = + hex_to_bytes32("4d0d9fb7f9ab2f012da088dc1c228173723db7e09147fe4fea2657849d580161"); + + let hash = MetadataHash::from_token_info("Test Token", "TEST", 18); + assert_eq!(hash.as_bytes(), &expected_hash); + } + + #[test] + fn test_encode_token_metadata_format() { + let encoded = encode_token_metadata("Test Token", "TEST", 18); + + // Verify the ABI encoding structure: + // Offset to name = 0x60 (96) + assert_eq!(encoded[31], 0x60); + // Offset to symbol = 0x60 + 0x20 (name length) + 0x20 (name data padded) = 0xa0 + assert_eq!(encoded[63], 0xa0); + // Decimals = 18 + assert_eq!(encoded[95], 18); + // Name length = 10 ("Test Token") + assert_eq!(encoded[96 + 31], 10); + // Name data starts at 96 + 32 = 128 + assert_eq!(&encoded[128..138], b"Test Token"); + } + + #[test] + fn test_from_abi_encoded_matches_from_token_info() { + let encoded = encode_token_metadata("Test Token", "TEST", 18); + let hash_from_encoded = MetadataHash::from_abi_encoded(&encoded); + let hash_from_info = MetadataHash::from_token_info("Test Token", "TEST", 18); + assert_eq!(hash_from_encoded, hash_from_info); + } + + fn hex_to_bytes32(hex: &str) -> [u8; 32] { + let mut bytes = [0u8; 32]; + for (i, byte) in bytes.iter_mut().enumerate() { + *byte = u8::from_str_radix(&hex[i * 2..i * 2 + 2], 16).unwrap(); + } + bytes + } +} diff --git a/crates/miden-agglayer/src/lib.rs b/crates/miden-agglayer/src/lib.rs index 21070e557b..06a79a44bf 100644 --- a/crates/miden-agglayer/src/lib.rs +++ b/crates/miden-agglayer/src/lib.rs @@ -311,6 +311,14 @@ static CONVERSION_INFO_2_SLOT_NAME: LazyLock = LazyLock::new(|| StorageSlotName::new("miden::agglayer::faucet::conversion_info_2") .expect("conversion info 2 storage slot name should be valid") }); +static METADATA_HASH_LO_SLOT_NAME: LazyLock = LazyLock::new(|| { + StorageSlotName::new("miden::agglayer::faucet::metadata_hash_lo") + .expect("metadata hash lo storage slot name should be valid") +}); +static METADATA_HASH_HI_SLOT_NAME: LazyLock = LazyLock::new(|| { + StorageSlotName::new("miden::agglayer::faucet::metadata_hash_hi") + .expect("metadata hash hi storage slot name should be valid") +}); /// An [`AccountComponent`] implementing the AggLayer Faucet. /// @@ -329,6 +337,8 @@ static CONVERSION_INFO_2_SLOT_NAME: LazyLock = LazyLock::new(|| /// - [`Self::conversion_info_1_slot`]: Stores the first 4 felts of the origin token address. /// - [`Self::conversion_info_2_slot`]: Stores the remaining 5th felt of the origin token address + /// origin network + scale. +/// - [`Self::metadata_hash_lo_slot`]: Stores the first 4 u32 felts of the metadata hash. +/// - [`Self::metadata_hash_hi_slot`]: Stores the last 4 u32 felts of the metadata hash. #[derive(Debug, Clone)] pub struct AggLayerFaucet { metadata: TokenMetadata, @@ -336,6 +346,7 @@ pub struct AggLayerFaucet { origin_token_address: EthAddressFormat, origin_network: u32, scale: u8, + metadata_hash: MetadataHash, } impl AggLayerFaucet { @@ -346,6 +357,7 @@ impl AggLayerFaucet { /// - The decimals parameter exceeds maximum value of [`TokenMetadata::MAX_DECIMALS`]. /// - The max supply exceeds maximum possible amount for a fungible asset. /// - The token supply exceeds the max supply. + #[allow(clippy::too_many_arguments)] pub fn new( symbol: TokenSymbol, decimals: u8, @@ -355,6 +367,7 @@ impl AggLayerFaucet { origin_token_address: EthAddressFormat, origin_network: u32, scale: u8, + metadata_hash: MetadataHash, ) -> Result { let metadata = TokenMetadata::with_supply(symbol, decimals, max_supply, token_supply)?; Ok(Self { @@ -363,6 +376,7 @@ impl AggLayerFaucet { origin_token_address, origin_network, scale, + metadata_hash, }) } @@ -394,6 +408,16 @@ impl AggLayerFaucet { pub fn conversion_info_2_slot() -> &'static StorageSlotName { &CONVERSION_INFO_2_SLOT_NAME } + + /// Storage slot name for the first 4 u32 felts of the metadata hash. + pub fn metadata_hash_lo_slot() -> &'static StorageSlotName { + &METADATA_HASH_LO_SLOT_NAME + } + + /// Storage slot name for the last 4 u32 felts of the metadata hash. + pub fn metadata_hash_hi_slot() -> &'static StorageSlotName { + &METADATA_HASH_HI_SLOT_NAME + } } impl From for AccountComponent { @@ -419,8 +443,24 @@ impl From for AccountComponent { let conversion_slot2 = StorageSlot::with_value(CONVERSION_INFO_2_SLOT_NAME.clone(), conversion_slot2_word); - let agglayer_storage_slots = - vec![metadata_slot, bridge_slot, conversion_slot1, conversion_slot2]; + let hash_elements = faucet.metadata_hash.to_elements(); + let metadata_hash_lo = StorageSlot::with_value( + METADATA_HASH_LO_SLOT_NAME.clone(), + Word::new([hash_elements[0], hash_elements[1], hash_elements[2], hash_elements[3]]), + ); + let metadata_hash_hi = StorageSlot::with_value( + METADATA_HASH_HI_SLOT_NAME.clone(), + Word::new([hash_elements[4], hash_elements[5], hash_elements[6], hash_elements[7]]), + ); + + let agglayer_storage_slots = vec![ + metadata_slot, + bridge_slot, + conversion_slot1, + conversion_slot2, + metadata_hash_lo, + metadata_hash_hi, + ]; agglayer_faucet_component(agglayer_storage_slots) } } @@ -461,6 +501,7 @@ pub fn faucet_registry_key(faucet_id: AccountId) -> Word { /// /// # Panics /// Panics if the token symbol is invalid or metadata validation fails. +#[allow(clippy::too_many_arguments)] fn create_agglayer_faucet_component( token_symbol: &str, decimals: u8, @@ -470,6 +511,7 @@ fn create_agglayer_faucet_component( origin_token_address: &EthAddressFormat, origin_network: u32, scale: u8, + metadata_hash: MetadataHash, ) -> AccountComponent { let symbol = TokenSymbol::new(token_symbol).expect("token symbol should be valid"); AggLayerFaucet::new( @@ -481,6 +523,7 @@ fn create_agglayer_faucet_component( *origin_token_address, origin_network, scale, + metadata_hash, ) .expect("agglayer faucet metadata should be valid") .into() @@ -541,6 +584,7 @@ fn create_agglayer_faucet_builder( origin_token_address: &EthAddressFormat, origin_network: u32, scale: u8, + metadata_hash: MetadataHash, ) -> AccountBuilder { let agglayer_component = create_agglayer_faucet_component( token_symbol, @@ -551,6 +595,7 @@ fn create_agglayer_faucet_builder( origin_token_address, origin_network, scale, + metadata_hash, ); Account::builder(seed.into()) @@ -562,6 +607,7 @@ fn create_agglayer_faucet_builder( /// Creates a new agglayer faucet account with the specified configuration. /// /// This creates a new account suitable for production use. +#[allow(clippy::too_many_arguments)] pub fn create_agglayer_faucet( seed: Word, token_symbol: &str, @@ -571,6 +617,7 @@ pub fn create_agglayer_faucet( origin_token_address: &EthAddressFormat, origin_network: u32, scale: u8, + metadata_hash: MetadataHash, ) -> Account { create_agglayer_faucet_builder( seed, @@ -582,6 +629,7 @@ pub fn create_agglayer_faucet( origin_token_address, origin_network, scale, + metadata_hash, ) .with_auth_component(AccountComponent::from(NoAuth)) .build() @@ -603,6 +651,7 @@ pub fn create_existing_agglayer_faucet( origin_token_address: &EthAddressFormat, origin_network: u32, scale: u8, + metadata_hash: MetadataHash, ) -> Account { create_agglayer_faucet_builder( seed, @@ -614,6 +663,7 @@ pub fn create_existing_agglayer_faucet( origin_token_address, origin_network, scale, + metadata_hash, ) .with_auth_component(AccountComponent::from(NoAuth)) .build_existing() diff --git a/crates/miden-testing/tests/agglayer/bridge_in.rs b/crates/miden-testing/tests/agglayer/bridge_in.rs index 1d579d3218..290ecdb233 100644 --- a/crates/miden-testing/tests/agglayer/bridge_in.rs +++ b/crates/miden-testing/tests/agglayer/bridge_in.rs @@ -156,6 +156,7 @@ async fn test_bridge_in_claim_to_p2id(#[case] data_source: ClaimDataSource) -> a &origin_token_address, origin_network, scale, + leaf_data.metadata_hash, ); builder.add_account(agglayer_faucet.clone())?; diff --git a/crates/miden-testing/tests/agglayer/bridge_out.rs b/crates/miden-testing/tests/agglayer/bridge_out.rs index 8b40e17584..577abf9bf8 100644 --- a/crates/miden-testing/tests/agglayer/bridge_out.rs +++ b/crates/miden-testing/tests/agglayer/bridge_out.rs @@ -7,6 +7,7 @@ use miden_agglayer::{ ConfigAggBridgeNote, EthAddressFormat, ExitRoot, + MetadataHash, create_existing_agglayer_faucet, create_existing_bridge_account, }; @@ -130,6 +131,7 @@ async fn bridge_out_consecutive() -> anyhow::Result<()> { .expect("valid shared origin token address"); let origin_network = 64u32; let scale = 0u8; + let metadata_hash = MetadataHash::from_token_info("AGG", "AGG", 8); let faucet = create_existing_agglayer_faucet( builder.rng_mut().draw_word(), "AGG", @@ -140,6 +142,7 @@ async fn bridge_out_consecutive() -> anyhow::Result<()> { &origin_token_address, origin_network, scale, + metadata_hash, ); builder.add_account(faucet.clone())?; @@ -328,6 +331,7 @@ async fn test_bridge_out_fails_with_unregistered_faucet() -> anyhow::Result<()> // CREATE AGGLAYER FAUCET ACCOUNT (NOT registered in the bridge) // -------------------------------------------------------------------------------------------- let origin_token_address = EthAddressFormat::new([0u8; 20]); + let metadata_hash = MetadataHash::from_token_info("AGG", "AGG", 8); let faucet = create_existing_agglayer_faucet( builder.rng_mut().draw_word(), "AGG", @@ -338,6 +342,7 @@ async fn test_bridge_out_fails_with_unregistered_faucet() -> anyhow::Result<()> &origin_token_address, 0, // origin_network 0, // scale + metadata_hash, ); builder.add_account(faucet.clone())?; From c4169070a91ff93e95445e5bc15600d5adc75f3a Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Wed, 11 Mar 2026 11:01:13 +0000 Subject: [PATCH 02/17] refactor: address self-review feedback on metadata hash - Add bounds assertion to encode_token_metadata for string lengths - Make encode_token_metadata pub(crate) since it's only used internally Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/miden-agglayer/src/eth_types/metadata_hash.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/miden-agglayer/src/eth_types/metadata_hash.rs b/crates/miden-agglayer/src/eth_types/metadata_hash.rs index f89c6cddce..5fa988703a 100644 --- a/crates/miden-agglayer/src/eth_types/metadata_hash.rs +++ b/crates/miden-agglayer/src/eth_types/metadata_hash.rs @@ -67,10 +67,15 @@ impl MetadataHash { /// - 3 x 32-byte words for offsets/value of each parameter /// - 32-byte length + padded data for name string /// - 32-byte length + padded data for symbol string -pub fn encode_token_metadata(name: &str, symbol: &str, decimals: u8) -> Vec { +pub(crate) fn encode_token_metadata(name: &str, symbol: &str, decimals: u8) -> Vec { let name_bytes = name.as_bytes(); let symbol_bytes = symbol.as_bytes(); + // ABI encoding uses 32-byte u256 for offsets and lengths. We only write the lower 2 bytes, + // so enforce a reasonable limit. Token names/symbols are typically < 32 bytes. + assert!(name_bytes.len() <= 1024, "token name too long for ABI encoding"); + assert!(symbol_bytes.len() <= 1024, "token symbol too long for ABI encoding"); + let name_padded_data_len = pad_to_32(name_bytes.len()); let symbol_padded_data_len = pad_to_32(symbol_bytes.len()); From d6d9127c32ad1e987acb29cbc19d386cf8789844 Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Wed, 11 Mar 2026 11:06:59 +0000 Subject: [PATCH 03/17] chore: allow CC0-1.0 license in cargo-deny for tiny-keccak Co-Authored-By: Claude Opus 4.6 (1M context) --- deny.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/deny.toml b/deny.toml index 3679142018..29cb472998 100644 --- a/deny.toml +++ b/deny.toml @@ -22,6 +22,7 @@ allow = [ "Apache-2.0", "BSD-2-Clause", "BSD-3-Clause", + "CC0-1.0", "ISC", "MIT", "Unicode-3.0", From 5c717fd10ea7642669813fa7f095e10ceea5aead Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Wed, 11 Mar 2026 11:17:19 +0000 Subject: [PATCH 04/17] chore: add changelog entry for metadata hash feature Co-Authored-By: Claude Opus 4.6 (1M context) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c548751e0..98a8b9b862 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ - Resolve standard note scripts directly in `TransactionExecutorHost` instead of querying the data store ([#2417](https://github.com/0xMiden/miden-base/pull/2417)). - Added `DEFAULT_TAG` constant to `miden::standards::note_tag` MASM module ([#2482](https://github.com/0xMiden/miden-base/pull/2482)). - Added `NoteExecutionHint` variant constants (`NONE`, `ALWAYS`, `AFTER_BLOCK`, `ON_BLOCK_SLOT`) to `miden::standards::note::execution_hint` MASM module ([#2493](https://github.com/0xMiden/miden-base/pull/2493)). +- Added metadata hash storage to AggLayer faucet and FPI retrieval during bridge-out leaf construction ([#2583](https://github.com/0xMiden/protocol/pull/2583)). ### Changes From 5a2bbd8506c608d524e193d73689a48e7b6ba66f Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Thu, 12 Mar 2026 16:26:38 +0000 Subject: [PATCH 05/17] refactor: replace tiny-keccak with miden-crypto and alloy-sol-types for ABI encoding Swap `tiny-keccak` for `miden_crypto::hash::keccak::Keccak256` to avoid an extra cargo dependency, and replace the hand-rolled ABI encoding with `alloy-sol-types` `sol!` macro for type-safe `abi.encode(string, string, uint8)`. Tests now read expected values from the test vector JSON instead of hardcoding them. Co-Authored-By: Claude Opus 4.6 (1M context) --- Cargo.lock | 228 ++++++++++++++++-- Cargo.toml | 2 +- crates/miden-agglayer/Cargo.toml | 7 +- .../src/eth_types/metadata_hash.rs | 142 +++++------ 4 files changed, 278 insertions(+), 101 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f3d6acdfcb..3269ee01de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -64,6 +64,81 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "alloy-primitives" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3b431b4e72cd8bd0ec7a50b4be18e73dab74de0dba180eef171055e5d5926e" +dependencies = [ + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "itoa", + "paste", + "ruint", + "rustc-hash", + "sha3", +] + +[[package]] +name = "alloy-sol-macro" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab81bab693da9bb79f7a95b64b394718259fdd7e41dceeced4cad57cb71c4f6a" +dependencies = [ + "alloy-sol-macro-expander", + "alloy-sol-macro-input", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "alloy-sol-macro-expander" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "489f1620bb7e2483fb5819ed01ab6edc1d2f93939dce35a5695085a1afd1d699" +dependencies = [ + "alloy-sol-macro-input", + "const-hex", + "heck", + "indexmap", + "proc-macro-error2", + "proc-macro2", + "quote", + "sha3", + "syn 2.0.114", + "syn-solidity", +] + +[[package]] +name = "alloy-sol-macro-input" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56cef806ad22d4392c5fc83cf8f2089f988eb99c7067b4e0c6f1971fc1cca318" +dependencies = [ + "const-hex", + "dunce", + "heck", + "macro-string", + "proc-macro2", + "quote", + "syn 2.0.114", + "syn-solidity", +] + +[[package]] +name = "alloy-sol-types" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64612d29379782a5dde6f4b6570d9c756d734d760c0c94c254d361e678a6591f" +dependencies = [ + "alloy-primitives", + "alloy-sol-macro", +] + [[package]] name = "anes" version = "0.1.6" @@ -302,6 +377,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + [[package]] name = "cast" version = "0.3.0" @@ -446,6 +527,18 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "const-hex" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "531185e432bb31db1ecda541e9e7ab21468d4d844ad7505e0546a49b4945d49b" +dependencies = [ + "cfg-if", + "cpufeatures", + "proptest", + "serde_core", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -458,6 +551,15 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -642,10 +744,12 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ + "convert_case", "proc-macro2", "quote", "rustc_version 0.4.1", "syn 2.0.114", + "unicode-xid", ] [[package]] @@ -666,6 +770,12 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921" +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "ecdsa" version = "0.16.9" @@ -1071,6 +1181,12 @@ dependencies = [ "serde_core", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.5.2" @@ -1355,6 +1471,17 @@ dependencies = [ "tracing-subscriber 0.3.22", ] +[[package]] +name = "macro-string" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "matchers" version = "0.2.0" @@ -1383,6 +1510,7 @@ dependencies = [ name = "miden-agglayer" version = "0.14.0-alpha.2" dependencies = [ + "alloy-sol-types", "fs-err", "miden-agglayer", "miden-assembly", @@ -1394,8 +1522,9 @@ dependencies = [ "miden-utils-sync", "primitive-types", "regex", + "serde", + "serde_json", "thiserror", - "tiny-keccak", "walkdir", ] @@ -1517,7 +1646,7 @@ dependencies = [ "miden-crypto-derive", "num", "num-complex", - "rand", + "rand 0.9.2", "rand_chacha", "rand_core 0.9.5", "rand_hc", @@ -1667,7 +1796,7 @@ dependencies = [ "miden-utils-sync", "miden-verifier", "pprof", - "rand", + "rand 0.9.2", "rand_chacha", "rand_xoshiro", "regex", @@ -1719,7 +1848,7 @@ dependencies = [ "miden-processor", "miden-protocol", "miden-standards", - "rand", + "rand 0.9.2", "regex", "thiserror", "walkdir", @@ -1744,7 +1873,7 @@ dependencies = [ "miden-tx", "miden-tx-batch-prover", "primitive-types", - "rand", + "rand 0.9.2", "rand_chacha", "rstest", "serde", @@ -2213,6 +2342,28 @@ dependencies = [ "toml_edit", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -2230,7 +2381,7 @@ checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40" dependencies = [ "bitflags 2.10.0", "num-traits", - "rand", + "rand 0.9.2", "rand_chacha", "rand_xorshift", "regex-syntax", @@ -2272,6 +2423,15 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "rand" version = "0.9.2" @@ -2449,12 +2609,39 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "ruint" +version = "1.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c141e807189ad38a07276942c6623032d3753c8859c146104ac2e4d68865945a" +dependencies = [ + "proptest", + "rand 0.8.5", + "rand 0.9.2", + "ruint-macro", + "serde_core", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" + [[package]] name = "rustc-demangle" version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustc_version" version = "0.2.3" @@ -2826,6 +3013,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn-solidity" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53f425ae0b12e2f5ae65542e00898d500d4d318b4baf09f40fd0d410454e9947" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "target-triple" version = "1.0.0" @@ -2913,15 +3112,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - [[package]] name = "tinytemplate" version = "1.2.1" @@ -3160,6 +3350,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "unicode-width" version = "0.1.14" @@ -3584,7 +3780,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4ff3b651754a7bd216f959764d0a5ab6f4b551c9a3a08fb9ccecbed594b614a" dependencies = [ - "rand", + "rand 0.9.2", "winter-utils", ] diff --git a/Cargo.toml b/Cargo.toml index 9068a749b8..e33356e9a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,6 +65,7 @@ miden-utils-sync = { default-features = false, version = "0.20" } miden-verifier = { default-features = false, version = "0.20" } # External dependencies +alloy-sol-types = { default-features = false, version = "1.5" } anyhow = { default-features = false, features = ["backtrace", "std"], version = "1.0" } assert_matches = { default-features = false, version = "1.5" } fs-err = { default-features = false, version = "3" } @@ -74,5 +75,4 @@ rand_chacha = { default-features = false, version = "0.9" } rstest = { version = "0.26" } serde = { default-features = false, version = "1.0" } thiserror = { default-features = false, version = "2.0" } -tiny-keccak = { default-features = false, features = ["keccak"], version = "2.0" } tokio = { default-features = false, features = ["sync"], version = "1" } diff --git a/crates/miden-agglayer/Cargo.toml b/crates/miden-agglayer/Cargo.toml index f2ffa8efbd..525ad66160 100644 --- a/crates/miden-agglayer/Cargo.toml +++ b/crates/miden-agglayer/Cargo.toml @@ -29,12 +29,17 @@ miden-standards = { workspace = true } miden-utils-sync = { workspace = true } # Third-party dependencies +alloy-sol-types = { workspace = true } primitive-types = { workspace = true } thiserror = { workspace = true } -tiny-keccak = { workspace = true } + +# Crypto +miden-crypto = { workspace = true } [dev-dependencies] miden-agglayer = { features = ["testing"], path = "." } +serde = { features = ["derive"], version = "1.0" } +serde_json = { version = "1.0" } [build-dependencies] fs-err = { workspace = true } diff --git a/crates/miden-agglayer/src/eth_types/metadata_hash.rs b/crates/miden-agglayer/src/eth_types/metadata_hash.rs index 5fa988703a..4e2b9ef071 100644 --- a/crates/miden-agglayer/src/eth_types/metadata_hash.rs +++ b/crates/miden-agglayer/src/eth_types/metadata_hash.rs @@ -1,9 +1,9 @@ -use alloc::vec; use alloc::vec::Vec; +use alloy_sol_types::{SolValue, sol}; use miden_core_lib::handlers::bytes_to_packed_u32_felts; +use miden_crypto::hash::keccak::Keccak256; use miden_protocol::Felt; -use tiny_keccak::{Hasher, Keccak}; // ================================================================================================ // METADATA HASH @@ -29,11 +29,8 @@ impl MetadataHash { /// /// This computes `keccak256(metadata_bytes)`. pub fn from_abi_encoded(metadata_bytes: &[u8]) -> Self { - let mut hasher = Keccak::v256(); - hasher.update(metadata_bytes); - let mut output = [0u8; 32]; - hasher.finalize(&mut output); - Self(output) + let digest = Keccak256::hash(metadata_bytes); + Self(<[u8; 32]>::from(digest)) } /// Computes the metadata hash from token information. @@ -61,61 +58,24 @@ impl MetadataHash { // ABI ENCODING // ================================================================================================ +sol! { + struct TokenMetadata { + string name; + string symbol; + uint8 decimals; + } +} + /// ABI-encodes token metadata as `abi.encode(name, symbol, decimals)`. /// -/// This produces the same encoding as Solidity's `abi.encode(string, string, uint8)`: -/// - 3 x 32-byte words for offsets/value of each parameter -/// - 32-byte length + padded data for name string -/// - 32-byte length + padded data for symbol string +/// This produces the same encoding as Solidity's `abi.encode(string, string, uint8)`. pub(crate) fn encode_token_metadata(name: &str, symbol: &str, decimals: u8) -> Vec { - let name_bytes = name.as_bytes(); - let symbol_bytes = symbol.as_bytes(); - - // ABI encoding uses 32-byte u256 for offsets and lengths. We only write the lower 2 bytes, - // so enforce a reasonable limit. Token names/symbols are typically < 32 bytes. - assert!(name_bytes.len() <= 1024, "token name too long for ABI encoding"); - assert!(symbol_bytes.len() <= 1024, "token symbol too long for ABI encoding"); - - let name_padded_data_len = pad_to_32(name_bytes.len()); - let symbol_padded_data_len = pad_to_32(symbol_bytes.len()); - - // The 3 head slots (offsets + decimals) take 3 * 32 = 96 bytes - let name_offset: usize = 3 * 32; // 0x60 - let symbol_offset: usize = name_offset + 32 + name_padded_data_len; - - let total_len = symbol_offset + 32 + symbol_padded_data_len; - let mut buf = vec![0u8; total_len]; - - // Write offset to name (big-endian u256) - buf[31] = name_offset as u8; - buf[30] = (name_offset >> 8) as u8; - - // Write offset to symbol (big-endian u256) - buf[63] = symbol_offset as u8; - buf[62] = (symbol_offset >> 8) as u8; - - // Write decimals (big-endian u256, value in last byte) - buf[95] = decimals; - - // Write name: length word + data - let name_len_offset = name_offset; - buf[name_len_offset + 31] = name_bytes.len() as u8; - buf[name_len_offset + 30] = (name_bytes.len() >> 8) as u8; - buf[name_len_offset + 32..name_len_offset + 32 + name_bytes.len()].copy_from_slice(name_bytes); - - // Write symbol: length word + data - let symbol_len_offset = symbol_offset; - buf[symbol_len_offset + 31] = symbol_bytes.len() as u8; - buf[symbol_len_offset + 30] = (symbol_bytes.len() >> 8) as u8; - buf[symbol_len_offset + 32..symbol_len_offset + 32 + symbol_bytes.len()] - .copy_from_slice(symbol_bytes); - - buf -} - -/// Rounds up to the nearest multiple of 32. -fn pad_to_32(len: usize) -> usize { - (len + 31) & !31 + TokenMetadata { + name: name.into(), + symbol: symbol.into(), + decimals, + } + .abi_encode_params() } // TESTS @@ -123,35 +83,52 @@ fn pad_to_32(len: usize) -> usize { #[cfg(test)] mod tests { + extern crate std; + + use std::path::Path; + + use miden_protocol::utils::hex_to_bytes; + use serde::Deserialize; + use super::*; + /// Partial deserialization of claim_asset_vectors_local_tx.json + #[derive(Deserialize)] + struct ClaimAssetVectors { + metadata: std::string::String, + metadata_hash: std::string::String, + } + + fn load_test_vectors() -> ClaimAssetVectors { + let path = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("solidity-compat/test-vectors/claim_asset_vectors_local_tx.json"); + let data = std::fs::read_to_string(path).expect("failed to read test vectors"); + serde_json::from_str(&data).expect("failed to parse test vectors") + } + #[test] fn test_encode_token_metadata_matches_solidity() { - // From solidity-compat/test-vectors/claim_asset_vectors_local_tx.json: - // Token: "Test Token", symbol: "TEST", decimals: 18 - // metadata_hash: 0x4d0d9fb7f9ab2f012da088dc1c228173723db7e09147fe4fea2657849d580161 - let expected_hash = - hex_to_bytes32("4d0d9fb7f9ab2f012da088dc1c228173723db7e09147fe4fea2657849d580161"); + let vectors = load_test_vectors(); + + let expected_hash: [u8; 32] = + hex_to_bytes(&vectors.metadata_hash).expect("valid metadata_hash hex"); - let hash = MetadataHash::from_token_info("Test Token", "TEST", 18); + // Parse the ABI-encoded metadata from the test vector + let metadata_bytes = hex_to_vec(&vectors.metadata[2..]); + + // Verify that hashing the ABI-encoded metadata produces the expected hash + let hash = MetadataHash::from_abi_encoded(&metadata_bytes); assert_eq!(hash.as_bytes(), &expected_hash); } #[test] fn test_encode_token_metadata_format() { - let encoded = encode_token_metadata("Test Token", "TEST", 18); + let vectors = load_test_vectors(); + let expected_metadata = hex_to_vec(&vectors.metadata[2..]); - // Verify the ABI encoding structure: - // Offset to name = 0x60 (96) - assert_eq!(encoded[31], 0x60); - // Offset to symbol = 0x60 + 0x20 (name length) + 0x20 (name data padded) = 0xa0 - assert_eq!(encoded[63], 0xa0); - // Decimals = 18 - assert_eq!(encoded[95], 18); - // Name length = 10 ("Test Token") - assert_eq!(encoded[96 + 31], 10); - // Name data starts at 96 + 32 = 128 - assert_eq!(&encoded[128..138], b"Test Token"); + // The test vectors use: name="Test Token", symbol="TEST", decimals=18 + let encoded = encode_token_metadata("Test Token", "TEST", 18); + assert_eq!(encoded, expected_metadata); } #[test] @@ -162,11 +139,10 @@ mod tests { assert_eq!(hash_from_encoded, hash_from_info); } - fn hex_to_bytes32(hex: &str) -> [u8; 32] { - let mut bytes = [0u8; 32]; - for (i, byte) in bytes.iter_mut().enumerate() { - *byte = u8::from_str_radix(&hex[i * 2..i * 2 + 2], 16).unwrap(); - } - bytes + fn hex_to_vec(hex: &str) -> std::vec::Vec { + (0..hex.len()) + .step_by(2) + .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap()) + .collect() } } From da64d057dd650babcdfdfaf32c33c54afcab6a40 Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Thu, 12 Mar 2026 16:26:53 +0000 Subject: [PATCH 06/17] refactor: replace truncate_stack with explicit drops in get_metadata_hash Use `swapdw dropw dropw` instead of `exec.sys::truncate_stack` to explicitly drop the 8 excess padding elements in `get_metadata_hash`. Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/miden-agglayer/asm/agglayer/faucet/mod.masm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/miden-agglayer/asm/agglayer/faucet/mod.masm b/crates/miden-agglayer/asm/agglayer/faucet/mod.masm index 6b88d05bab..7a161501f8 100644 --- a/crates/miden-agglayer/asm/agglayer/faucet/mod.masm +++ b/crates/miden-agglayer/asm/agglayer/faucet/mod.masm @@ -172,7 +172,9 @@ pub proc get_metadata_hash swapw # => [lo0, lo1, lo2, lo3, hi0, hi1, hi2, hi3, pad(16)] - exec.sys::truncate_stack + # Drop 8 excess padding elements (24 -> 16) + swapdw dropw dropw + # => [METADATA_HASH_LO(4), METADATA_HASH_HI(4), pad(8)] end #! Converts a native Miden asset amount to origin asset data using the stored From c57ca647767566e99f1a1990efc2ad505becaa43 Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Thu, 12 Mar 2026 16:27:39 +0000 Subject: [PATCH 07/17] docs: simplify TODO comment and compute METADATA_HASH inline in Solidity test Replace verbose TODO in bridge_config.masm with a concise version referencing issue #2586. In the Solidity test, compute METADATA_HASH via `keccak256(abi.encode(...))` instead of hardcoding the constant. Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/miden-agglayer/asm/agglayer/bridge/bridge_config.masm | 5 ++--- .../miden-agglayer/solidity-compat/test/MMRTestVectors.t.sol | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/crates/miden-agglayer/asm/agglayer/bridge/bridge_config.masm b/crates/miden-agglayer/asm/agglayer/bridge/bridge_config.masm index 4bd1030587..47d6a0fc22 100644 --- a/crates/miden-agglayer/asm/agglayer/bridge/bridge_config.masm +++ b/crates/miden-agglayer/asm/agglayer/bridge/bridge_config.masm @@ -106,9 +106,8 @@ end #! #! Panics if the note sender is not the bridge admin. #! -#! TODO: Once the token name is available in faucet storage and abi.encode(string, string, uint8) -#! is implemented in MASM, verify the faucet's metadata_hash against the stored name/symbol/decimals -#! during registration to ensure correctness. +#! TODO: verify correctness upon faucet registration, see https://github.com/0xMiden/protocol/issues/2586. +#! For now, we trust the bridge admin. #! #! Inputs: [faucet_id_prefix, faucet_id_suffix, pad(14)] #! Outputs: [pad(16)] diff --git a/crates/miden-agglayer/solidity-compat/test/MMRTestVectors.t.sol b/crates/miden-agglayer/solidity-compat/test/MMRTestVectors.t.sol index 83a2c5e7d3..b9bb7a30d1 100644 --- a/crates/miden-agglayer/solidity-compat/test/MMRTestVectors.t.sol +++ b/crates/miden-agglayer/solidity-compat/test/MMRTestVectors.t.sol @@ -24,8 +24,7 @@ contract MMRTestVectors is Test, DepositContractV2 { uint8 constant LEAF_TYPE = 0; uint32 constant ORIGIN_NETWORK = 64; address constant ORIGIN_TOKEN_ADDR = 0x7a6fC3e8b57c6D1924F1A9d0E2b3c4D5e6F70891; - // keccak256(abi.encode("AGG", "AGG", uint8(8))) - bytes32 constant METADATA_HASH = 0xc98c1a2dbf558d2ddf62e6ad8f93f6abfacf0cf446d095fc3f20b628d1144b3d; + bytes32 constant METADATA_HASH = keccak256(abi.encode("AGG", "AGG", uint8(8))); // Fixed seed for deterministic "random" destination vectors. // Keeping this constant ensures everyone regenerates the exact same JSON vectors. From 57fd36381319b688537ba0f590f09388dd386995 Mon Sep 17 00:00:00 2001 From: Marti Date: Fri, 13 Mar 2026 19:01:57 +0000 Subject: [PATCH 08/17] chore: consolidate tests --- .../src/eth_types/metadata_hash.rs | 29 +++++-------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/crates/miden-agglayer/src/eth_types/metadata_hash.rs b/crates/miden-agglayer/src/eth_types/metadata_hash.rs index 4e2b9ef071..43e3faee75 100644 --- a/crates/miden-agglayer/src/eth_types/metadata_hash.rs +++ b/crates/miden-agglayer/src/eth_types/metadata_hash.rs @@ -107,36 +107,21 @@ mod tests { } #[test] - fn test_encode_token_metadata_matches_solidity() { + fn test_metadata_hash_matches_solidity() { let vectors = load_test_vectors(); - + let expected_metadata = hex_to_vec(&vectors.metadata[2..]); let expected_hash: [u8; 32] = hex_to_bytes(&vectors.metadata_hash).expect("valid metadata_hash hex"); - // Parse the ABI-encoded metadata from the test vector - let metadata_bytes = hex_to_vec(&vectors.metadata[2..]); - - // Verify that hashing the ABI-encoded metadata produces the expected hash - let hash = MetadataHash::from_abi_encoded(&metadata_bytes); - assert_eq!(hash.as_bytes(), &expected_hash); - } - - #[test] - fn test_encode_token_metadata_format() { - let vectors = load_test_vectors(); - let expected_metadata = hex_to_vec(&vectors.metadata[2..]); - // The test vectors use: name="Test Token", symbol="TEST", decimals=18 let encoded = encode_token_metadata("Test Token", "TEST", 18); - assert_eq!(encoded, expected_metadata); - } + assert_eq!(encoded, expected_metadata, "ABI encoding must match Solidity"); + + let hash = MetadataHash::from_abi_encoded(&encoded); + assert_eq!(hash.as_bytes(), &expected_hash, "keccak256 hash must match Solidity"); - #[test] - fn test_from_abi_encoded_matches_from_token_info() { - let encoded = encode_token_metadata("Test Token", "TEST", 18); - let hash_from_encoded = MetadataHash::from_abi_encoded(&encoded); let hash_from_info = MetadataHash::from_token_info("Test Token", "TEST", 18); - assert_eq!(hash_from_encoded, hash_from_info); + assert_eq!(hash, hash_from_info, "from_abi_encoded and from_token_info must agree"); } fn hex_to_vec(hex: &str) -> std::vec::Vec { From 6eb0e624e2d976b2b60c07e3a73385efa43d2ec7 Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Sat, 14 Mar 2026 08:35:54 +0000 Subject: [PATCH 09/17] chore: address Copilot review comments Use workspace dependency for serde in dev-dependencies and rename sol!-generated TokenMetadata to SolTokenMetadata to avoid ambiguity. Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/miden-agglayer/Cargo.toml | 2 +- crates/miden-agglayer/src/eth_types/metadata_hash.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/miden-agglayer/Cargo.toml b/crates/miden-agglayer/Cargo.toml index 525ad66160..efe8b903c1 100644 --- a/crates/miden-agglayer/Cargo.toml +++ b/crates/miden-agglayer/Cargo.toml @@ -38,7 +38,7 @@ miden-crypto = { workspace = true } [dev-dependencies] miden-agglayer = { features = ["testing"], path = "." } -serde = { features = ["derive"], version = "1.0" } +serde = { features = ["derive"], workspace = true } serde_json = { version = "1.0" } [build-dependencies] diff --git a/crates/miden-agglayer/src/eth_types/metadata_hash.rs b/crates/miden-agglayer/src/eth_types/metadata_hash.rs index 43e3faee75..3ce550d5cd 100644 --- a/crates/miden-agglayer/src/eth_types/metadata_hash.rs +++ b/crates/miden-agglayer/src/eth_types/metadata_hash.rs @@ -59,7 +59,7 @@ impl MetadataHash { // ================================================================================================ sol! { - struct TokenMetadata { + struct SolTokenMetadata { string name; string symbol; uint8 decimals; @@ -70,7 +70,7 @@ sol! { /// /// This produces the same encoding as Solidity's `abi.encode(string, string, uint8)`. pub(crate) fn encode_token_metadata(name: &str, symbol: &str, decimals: u8) -> Vec { - TokenMetadata { + SolTokenMetadata { name: name.into(), symbol: symbol.into(), decimals, From 0623afa7b8e53def27161fdf761d5c2a04bc2dfe Mon Sep 17 00:00:00 2001 From: Marti Date: Sat, 14 Mar 2026 11:40:12 +0100 Subject: [PATCH 10/17] Update crates/miden-agglayer/asm/agglayer/faucet/mod.masm --- crates/miden-agglayer/asm/agglayer/faucet/mod.masm | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/miden-agglayer/asm/agglayer/faucet/mod.masm b/crates/miden-agglayer/asm/agglayer/faucet/mod.masm index 7a161501f8..02878f085a 100644 --- a/crates/miden-agglayer/asm/agglayer/faucet/mod.masm +++ b/crates/miden-agglayer/asm/agglayer/faucet/mod.masm @@ -30,7 +30,6 @@ const CONVERSION_INFO_1_SLOT = word("miden::agglayer::faucet::conversion_info_1" const CONVERSION_INFO_2_SLOT = word("miden::agglayer::faucet::conversion_info_2") # Storage slots for the pre-computed metadata hash (keccak256 of ABI-encoded token metadata). -# The 32-byte hash is split across two value slots, each holding 4 u32 felts. const METADATA_HASH_LO_SLOT = word("miden::agglayer::faucet::metadata_hash_lo") const METADATA_HASH_HI_SLOT = word("miden::agglayer::faucet::metadata_hash_hi") From 5b902f03716bf13ebebec9bf454cbae24b422dfb Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Sat, 14 Mar 2026 11:03:08 +0000 Subject: [PATCH 11/17] fix: explicit pad before FPI call in metadata hash retrieval Add padw padw padw padw before the get_metadata_hash FPI call, matching the pattern established in bridge_in.masm (cfb6211cd). Co-Authored-By: Claude Opus 4.6 (1M context) --- .../asm/agglayer/bridge/bridge_out.masm | 18 +++++++++++------- .../asm/agglayer/faucet/mod.masm | 1 + 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm b/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm index aa152ecccf..e74d7ac70c 100644 --- a/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm +++ b/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm @@ -145,25 +145,29 @@ pub proc bridge_out # => [] # Fetch metadata hash from the faucet via FPI. - # Reload asset to extract faucet ID for the FPI call. + + # Explicitly pad the stack for FPI call (get_metadata_hash takes no inputs) + padw padw padw padw + # => [pad(16)] + + # Reload asset to extract faucet ID for the FPI call loc_loadw_be.BRIDGE_OUT_BURN_ASSET_LOC - # => [ASSET] - # ASSET layout: [faucet_id_prefix, faucet_id_suffix, 0, amount] + # => [faucet_id_prefix, faucet_id_suffix, 0, amount, pad(16)] # Extract faucet ID, drop padding and amount movup.2 drop movup.2 drop - # => [faucet_id_prefix, faucet_id_suffix] + # => [faucet_id_prefix, faucet_id_suffix, pad(16)] procref.agglayer_faucet::get_metadata_hash - # => [PROC_MAST_ROOT(4), faucet_id_prefix, faucet_id_suffix] + # => [PROC_MAST_ROOT(4), faucet_id_prefix, faucet_id_suffix, pad(16)] movup.5 movup.5 - # => [faucet_id_prefix, faucet_id_suffix, PROC_MAST_ROOT(4)] + # => [faucet_id_prefix, faucet_id_suffix, PROC_MAST_ROOT(4), pad(16)] exec.tx::execute_foreign_procedure # => [METADATA_HASH_LO(4), METADATA_HASH_HI(4), pad(8)] - # Drop 8 trailing padding elements + # Clean up FPI output padding swapdw dropw dropw # => [METADATA_HASH_LO(4), METADATA_HASH_HI(4)] diff --git a/crates/miden-agglayer/asm/agglayer/faucet/mod.masm b/crates/miden-agglayer/asm/agglayer/faucet/mod.masm index 02878f085a..7a161501f8 100644 --- a/crates/miden-agglayer/asm/agglayer/faucet/mod.masm +++ b/crates/miden-agglayer/asm/agglayer/faucet/mod.masm @@ -30,6 +30,7 @@ const CONVERSION_INFO_1_SLOT = word("miden::agglayer::faucet::conversion_info_1" const CONVERSION_INFO_2_SLOT = word("miden::agglayer::faucet::conversion_info_2") # Storage slots for the pre-computed metadata hash (keccak256 of ABI-encoded token metadata). +# The 32-byte hash is split across two value slots, each holding 4 u32 felts. const METADATA_HASH_LO_SLOT = word("miden::agglayer::faucet::metadata_hash_lo") const METADATA_HASH_HI_SLOT = word("miden::agglayer::faucet::metadata_hash_hi") From 6df58ee65f24d546effdabc7f8a57bc9bc8172d1 Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Sat, 14 Mar 2026 11:29:17 +0000 Subject: [PATCH 12/17] fix: explicit pad and reorder FPI setup for metadata hash retrieval - Add padw padw padw padw before the get_metadata_hash FPI call, matching the pattern from bridge_in.masm (cfb6211cd). - Reorder: load PROC_MAST_ROOT first via procref, then faucet ID on top via mem_load, eliminating movup.5 movup.5. - Use temp global memory (not loc_loadw_be) to load faucet ID after procref, since loc_loadw_be after procref conflicts with FPI state. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../asm/agglayer/bridge/bridge_out.masm | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm b/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm index e74d7ac70c..5366077fd2 100644 --- a/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm +++ b/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm @@ -36,6 +36,10 @@ const LET_ROOT_HI_SLOT=word("miden::agglayer::let::root_hi") const LET_NUM_LEAVES_SLOT=word("miden::agglayer::let::num_leaves") # Memory pointers +# Temporary storage for faucet ID during metadata hash FPI setup. +# Placed just before LEAF_DATA_START_PTR (uses 2 felts: prefix at 42, suffix at 43). +const FAUCET_ID_TMP_PREFIX_PTR=42 +const FAUCET_ID_TMP_SUFFIX_PTR=43 const LEAF_DATA_START_PTR=44 # Memory pointer for loading the LET (Local Exit Tree) frontier into memory. # The memory layout at this address matches what append_and_update_frontier expects: @@ -146,22 +150,23 @@ pub proc bridge_out # Fetch metadata hash from the faucet via FPI. + # Extract faucet ID and save to temp memory for FPI setup + loc_loadw_be.BRIDGE_OUT_BURN_ASSET_LOC + # => [faucet_id_prefix, faucet_id_suffix, 0, amount] + push.FAUCET_ID_TMP_PREFIX_PTR mem_store + push.FAUCET_ID_TMP_SUFFIX_PTR mem_store + drop drop + # => [] + # Explicitly pad the stack for FPI call (get_metadata_hash takes no inputs) padw padw padw padw # => [pad(16)] - # Reload asset to extract faucet ID for the FPI call - loc_loadw_be.BRIDGE_OUT_BURN_ASSET_LOC - # => [faucet_id_prefix, faucet_id_suffix, 0, amount, pad(16)] - - # Extract faucet ID, drop padding and amount - movup.2 drop movup.2 drop - # => [faucet_id_prefix, faucet_id_suffix, pad(16)] - + # Load PROC_MAST_ROOT first, then faucet ID on top (avoids movup stack reordering) procref.agglayer_faucet::get_metadata_hash - # => [PROC_MAST_ROOT(4), faucet_id_prefix, faucet_id_suffix, pad(16)] + # => [PROC_MAST_ROOT(4), pad(16)] - movup.5 movup.5 + mem_load.FAUCET_ID_TMP_SUFFIX_PTR mem_load.FAUCET_ID_TMP_PREFIX_PTR # => [faucet_id_prefix, faucet_id_suffix, PROC_MAST_ROOT(4), pad(16)] exec.tx::execute_foreign_procedure From 696b0b29fa68667a7dde0369e3ba0660e2ef44b5 Mon Sep 17 00:00:00 2001 From: Marti Date: Sun, 15 Mar 2026 09:01:31 +0000 Subject: [PATCH 13/17] chore: explicit padding comments; load proc-ref before faucet ID --- .../asm/agglayer/bridge/bridge_out.masm | 51 ++++++++++--------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm b/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm index 5366077fd2..0bcfdff206 100644 --- a/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm +++ b/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm @@ -88,7 +88,7 @@ const BURN_NOTE_NUM_STORAGE_ITEMS=0 #! 4. Computes Keccak hash and adds it to the MMR frontier #! 5. Creates a BURN note with the bridged out asset #! -#! Inputs: [ASSET, dest_network_id, dest_address(5), pad(4)] +#! Inputs: [ASSET, dest_network_id, dest_address(5), pad(6)] #! Outputs: [] #! #! Where: @@ -107,12 +107,13 @@ pub proc bridge_out loc_store.DESTINATION_ADDRESS_2_LOC loc_store.DESTINATION_ADDRESS_3_LOC loc_store.DESTINATION_ADDRESS_4_LOC - # => [] + # => [pad(16)] # --- 1. Validate faucet registration and convert asset via FPI --- loc_loadw_be.BRIDGE_OUT_BURN_ASSET_LOC + # => [ASSET, pad(12)] exec.convert_asset - # => [AMOUNT_U256[0](4), AMOUNT_U256[1](4), origin_addr(5), origin_network, dest_network_id, dest_address(5)] + # => [AMOUNT_U256_0, AMOUNT_U256_1, origin_addr(5), origin_network, pad(12)] # --- 2. Write all leaf data fields to memory --- @@ -120,23 +121,23 @@ pub proc bridge_out push.LEAF_DATA_START_PTR push.AMOUNT_OFFSET add movdn.8 exec.utils::mem_store_double_word_unaligned - # => [origin_addr(5), origin_network, dest_network_id, dest_address(5)] + # => [origin_addr(5), origin_network, pad(12)] # Store origin_token_address (5 felts) push.LEAF_DATA_START_PTR push.ORIGIN_TOKEN_ADDRESS_OFFSET add exec.write_address_to_memory - # => [origin_network, dest_network_id, dest_address(5)] + # => [origin_network, pad(15)] # Store origin_network push.LEAF_DATA_START_PTR push.ORIGIN_NETWORK_OFFSET add mem_store - # => [dest_network_id, dest_address(5)] + # => [pad(16)] # Store destination_network loc_load.DESTINATION_NETWORK_LOC push.LEAF_DATA_START_PTR push.DESTINATION_NETWORK_OFFSET add mem_store - # => [dest_address(5)] + # => [pad(16)] # Store destination_address loc_load.DESTINATION_ADDRESS_4_LOC @@ -146,40 +147,38 @@ pub proc bridge_out loc_load.DESTINATION_ADDRESS_0_LOC push.LEAF_DATA_START_PTR push.DESTINATION_ADDRESS_OFFSET add exec.write_address_to_memory - # => [] + # => [pad(16)] # Fetch metadata hash from the faucet via FPI. # Extract faucet ID and save to temp memory for FPI setup loc_loadw_be.BRIDGE_OUT_BURN_ASSET_LOC - # => [faucet_id_prefix, faucet_id_suffix, 0, amount] + # => [faucet_id_prefix, faucet_id_suffix, 0, amount, pad(12)] push.FAUCET_ID_TMP_PREFIX_PTR mem_store push.FAUCET_ID_TMP_SUFFIX_PTR mem_store drop drop - # => [] - - # Explicitly pad the stack for FPI call (get_metadata_hash takes no inputs) - padw padw padw padw # => [pad(16)] - # Load PROC_MAST_ROOT first, then faucet ID on top (avoids movup stack reordering) procref.agglayer_faucet::get_metadata_hash - # => [PROC_MAST_ROOT(4), pad(16)] + # => [PROC_MAST_ROOT, pad(16)] - mem_load.FAUCET_ID_TMP_SUFFIX_PTR mem_load.FAUCET_ID_TMP_PREFIX_PTR - # => [faucet_id_prefix, faucet_id_suffix, PROC_MAST_ROOT(4), pad(16)] + # Reload asset to extract faucet ID for the FPI call + padw loc_loadw_be.BRIDGE_OUT_BURN_ASSET_LOC + # => [ASSET, PROC_MAST_ROOT, pad(16)] + # ASSET layout: [faucet_id_prefix, faucet_id_suffix, 0, amount] - exec.tx::execute_foreign_procedure - # => [METADATA_HASH_LO(4), METADATA_HASH_HI(4), pad(8)] + # Extract faucet ID, drop padding and amount + movup.2 drop movup.2 drop + # => [faucet_id_prefix, faucet_id_suffix, PROC_MAST_ROOT, pad(16)] - # Clean up FPI output padding - swapdw dropw dropw - # => [METADATA_HASH_LO(4), METADATA_HASH_HI(4)] + exec.tx::execute_foreign_procedure + # => [METADATA_HASH_LO, METADATA_HASH_HI, pad(8)] push.LEAF_DATA_START_PTR push.METADATA_HASH_OFFSET add movdn.8 - # => [METADATA_HASH_LO(4), METADATA_HASH_HI(4), metadata_hash_ptr] + # => [METADATA_HASH_LO, METADATA_HASH_HI, metadata_hash_ptr, pad(8)] exec.utils::mem_store_double_word_unaligned + # => [pad(16)] # Explicitly zero the 3 padding felts after METADATA_HASH for # leaf_utils::pack_leaf_data @@ -194,21 +193,25 @@ pub proc bridge_out push.0 push.LEAF_DATA_START_PTR push.PADDING_OFFSET add.2 add mem_store + # => [pad(16)] # Leaf type push.LEAF_TYPE_ASSET exec.utils::swap_u32_bytes push.LEAF_DATA_START_PTR push.LEAF_TYPE_OFFSET add - # => [leaf_type_ptr, leaf_type] + # => [leaf_type_ptr, leaf_type, pad(16)] mem_store # --- 3. Compute leaf value and add to MMR frontier --- push.LEAF_DATA_START_PTR exec.add_leaf_bridge + # => [pad(16)] # --- 4. Create BURN output note for ASSET --- loc_loadw_be.BRIDGE_OUT_BURN_ASSET_LOC + # => [ASSET, pad(12)] exec.create_burn_note + # => [pad(16)] end # HELPER PROCEDURES From a130febe4b291ba1aeae69132f4168ca7c7a6295 Mon Sep 17 00:00:00 2001 From: Marti Date: Sun, 15 Mar 2026 09:03:49 +0000 Subject: [PATCH 14/17] chore: rearrange mem writing sections --- .../asm/agglayer/bridge/bridge_out.masm | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm b/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm index 0bcfdff206..1aecd49122 100644 --- a/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm +++ b/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm @@ -115,8 +115,7 @@ pub proc bridge_out exec.convert_asset # => [AMOUNT_U256_0, AMOUNT_U256_1, origin_addr(5), origin_network, pad(12)] - # --- 2. Write all leaf data fields to memory --- - + # --- 2. Write all leaf data fields to memory (except metadata hash) --- # Store scaled AMOUNT (8 felts) push.LEAF_DATA_START_PTR push.AMOUNT_OFFSET add movdn.8 @@ -133,6 +132,13 @@ pub proc bridge_out mem_store # => [pad(16)] + # Leaf type + push.LEAF_TYPE_ASSET + exec.utils::swap_u32_bytes + push.LEAF_DATA_START_PTR push.LEAF_TYPE_OFFSET add + # => [leaf_type_ptr, leaf_type, pad(16)] + mem_store + # Store destination_network loc_load.DESTINATION_NETWORK_LOC push.LEAF_DATA_START_PTR push.DESTINATION_NETWORK_OFFSET add @@ -149,8 +155,7 @@ pub proc bridge_out exec.write_address_to_memory # => [pad(16)] - # Fetch metadata hash from the faucet via FPI. - + # --- 3. Fetch metadata hash from the faucet via FPI and write to memory --- # Extract faucet ID and save to temp memory for FPI setup loc_loadw_be.BRIDGE_OUT_BURN_ASSET_LOC # => [faucet_id_prefix, faucet_id_suffix, 0, amount, pad(12)] @@ -195,19 +200,12 @@ pub proc bridge_out mem_store # => [pad(16)] - # Leaf type - push.LEAF_TYPE_ASSET - exec.utils::swap_u32_bytes - push.LEAF_DATA_START_PTR push.LEAF_TYPE_OFFSET add - # => [leaf_type_ptr, leaf_type, pad(16)] - mem_store - - # --- 3. Compute leaf value and add to MMR frontier --- + # --- 4. Compute leaf value and add to MMR frontier --- push.LEAF_DATA_START_PTR exec.add_leaf_bridge # => [pad(16)] - # --- 4. Create BURN output note for ASSET --- + # --- 5. Create BURN output note for ASSET --- loc_loadw_be.BRIDGE_OUT_BURN_ASSET_LOC # => [ASSET, pad(12)] exec.create_burn_note From 24dba22aaba6c51ecf17fe9b218f2091235ba6a9 Mon Sep 17 00:00:00 2001 From: Marti Date: Sun, 15 Mar 2026 11:15:38 +0000 Subject: [PATCH 15/17] chore: no need to store faucet ID to mem --- .../asm/agglayer/bridge/bridge_out.masm | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm b/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm index 1aecd49122..d8906c85b4 100644 --- a/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm +++ b/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm @@ -36,10 +36,6 @@ const LET_ROOT_HI_SLOT=word("miden::agglayer::let::root_hi") const LET_NUM_LEAVES_SLOT=word("miden::agglayer::let::num_leaves") # Memory pointers -# Temporary storage for faucet ID during metadata hash FPI setup. -# Placed just before LEAF_DATA_START_PTR (uses 2 felts: prefix at 42, suffix at 43). -const FAUCET_ID_TMP_PREFIX_PTR=42 -const FAUCET_ID_TMP_SUFFIX_PTR=43 const LEAF_DATA_START_PTR=44 # Memory pointer for loading the LET (Local Exit Tree) frontier into memory. # The memory layout at this address matches what append_and_update_frontier expects: @@ -156,14 +152,6 @@ pub proc bridge_out # => [pad(16)] # --- 3. Fetch metadata hash from the faucet via FPI and write to memory --- - # Extract faucet ID and save to temp memory for FPI setup - loc_loadw_be.BRIDGE_OUT_BURN_ASSET_LOC - # => [faucet_id_prefix, faucet_id_suffix, 0, amount, pad(12)] - push.FAUCET_ID_TMP_PREFIX_PTR mem_store - push.FAUCET_ID_TMP_SUFFIX_PTR mem_store - drop drop - # => [pad(16)] - procref.agglayer_faucet::get_metadata_hash # => [PROC_MAST_ROOT, pad(16)] From de38d2fb99e6ac9bcd5462977738ca63e27aa83a Mon Sep 17 00:00:00 2001 From: Marti Date: Sun, 15 Mar 2026 12:24:26 +0100 Subject: [PATCH 16/17] Apply suggestion from @mmagician --- crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm b/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm index d8906c85b4..d178c605ea 100644 --- a/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm +++ b/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm @@ -85,7 +85,7 @@ const BURN_NOTE_NUM_STORAGE_ITEMS=0 #! 5. Creates a BURN note with the bridged out asset #! #! Inputs: [ASSET, dest_network_id, dest_address(5), pad(6)] -#! Outputs: [] +#! Outputs: [pad(16)] #! #! Where: #! - ASSET is the asset to be bridged out (layout: [faucet_id_prefix, faucet_id_suffix, 0, amount]). From 731801185ebeb08246aa4ca41923e7cbde58554c Mon Sep 17 00:00:00 2001 From: Marti Date: Sun, 15 Mar 2026 12:35:51 +0100 Subject: [PATCH 17/17] Update deny.toml --- deny.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/deny.toml b/deny.toml index 29cb472998..3679142018 100644 --- a/deny.toml +++ b/deny.toml @@ -22,7 +22,6 @@ allow = [ "Apache-2.0", "BSD-2-Clause", "BSD-3-Clause", - "CC0-1.0", "ISC", "MIT", "Unicode-3.0",