diff --git a/.gitignore b/.gitignore index 5c2730e..d783ef5 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,5 @@ logs/ .claude/ .beads/ .logs/ +miden-client +logs/ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..125611b --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,5 @@ +# Mayor Context (miden) + +> **Recovery**: Run `gt prime` after compaction, clear, or new session + +Full context is injected by `gt prime` at session start. diff --git a/Cargo.lock b/Cargo.lock index adc471a..9a0148c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -36,12 +36,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - [[package]] name = "alloy-consensus" version = "0.8.3" @@ -49,7 +43,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e88e1edea70787c33e11197d3f32ae380f3db19e6e061e539a5bcf8184a6b326" dependencies = [ "alloy-eips", - "alloy-primitives", + "alloy-primitives 0.8.26", "alloy-rlp", "alloy-serde", "alloy-trie", @@ -68,7 +62,7 @@ checksum = "57b1bb53f40c0273cd1975573cd457b39213e68584e36d1401d25fd0398a1d65" dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 0.8.26", "alloy-rlp", ] @@ -78,7 +72,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0069cf0642457f87a01a014f6dc29d5d893cd4fd8fddf0c3cdfad1bb3ebafc41" dependencies = [ - "alloy-primitives", + "alloy-primitives 0.8.26", "alloy-rlp", "serde", ] @@ -89,7 +83,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c986539255fb839d1533c128e190e557e52ff652c9ef62939e233a81dd93f7e" dependencies = [ - "alloy-primitives", + "alloy-primitives 0.8.26", "alloy-rlp", "derive_more 1.0.0", "k256", @@ -104,7 +98,7 @@ checksum = "5f9fadfe089e9ccc0650473f2d4ef0a28bc015bbca5631d9f0f09e49b557fdb3" dependencies = [ "alloy-eip2930", "alloy-eip7702", - "alloy-primitives", + "alloy-primitives 0.8.26", "alloy-rlp", "alloy-serde", "c-kzg", @@ -120,7 +114,7 @@ version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4584e3641181ff073e9d5bec5b3b8f78f9749d9fb108a1cfbc4399a4a139c72a" dependencies = [ - "alloy-primitives", + "alloy-primitives 0.8.26", "alloy-sol-type-parser", "serde", "serde_json", @@ -134,7 +128,7 @@ checksum = "9081c099e798b8a2bba2145eb82a9a146f01fc7a35e9ab6e7b43305051f97550" dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 0.8.26", "alloy-serde", "serde", ] @@ -150,7 +144,7 @@ dependencies = [ "cfg-if", "const-hex", "derive_more 2.1.1", - "foldhash 0.1.5", + "foldhash", "hashbrown 0.15.5", "indexmap", "itoa", @@ -166,6 +160,23 @@ dependencies = [ "tiny-keccak", ] +[[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 2.1.1", + "itoa", + "paste", + "ruint", + "rustc-hash", + "sha3", +] + [[package]] name = "alloy-rlp" version = "0.3.13" @@ -194,7 +205,7 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3410a472ce26c457e9780f708ee6bd540b30f88f1f31fdab7a11d00bd6aa1aee" dependencies = [ - "alloy-primitives", + "alloy-primitives 0.8.26", "alloy-rpc-types-engine", "alloy-rpc-types-eth", "alloy-serde", @@ -209,7 +220,7 @@ checksum = "03bd16fa4959255ebf4a7702df08f325e5631df5cdca07c8a8e58bdc10fe02e3" dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 0.8.26", "alloy-rlp", "alloy-serde", "derive_more 1.0.0", @@ -229,10 +240,10 @@ dependencies = [ "alloy-consensus-any", "alloy-eips", "alloy-network-primitives", - "alloy-primitives", + "alloy-primitives 0.8.26", "alloy-rlp", "alloy-serde", - "alloy-sol-types", + "alloy-sol-types 0.8.26", "derive_more 1.0.0", "itertools 0.13.0", "serde", @@ -245,7 +256,7 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5851bf8d5ad33014bd0c45153c603303e730acc8a209450a7ae6b4a12c2789e2" dependencies = [ - "alloy-primitives", + "alloy-primitives 0.8.26", "serde", "serde_json", ] @@ -256,8 +267,22 @@ version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e68b32b6fa0d09bb74b4cefe35ccc8269d711c26629bc7cd98a47eeb12fe353f" dependencies = [ - "alloy-sol-macro-expander", - "alloy-sol-macro-input", + "alloy-sol-macro-expander 0.8.26", + "alloy-sol-macro-input 0.8.26", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[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 1.5.7", + "alloy-sol-macro-input 1.5.7", "proc-macro-error2", "proc-macro2", "quote", @@ -270,7 +295,7 @@ version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2afe6879ac373e58fd53581636f2cce843998ae0b058ebe1e4f649195e2bd23c" dependencies = [ - "alloy-sol-macro-input", + "alloy-sol-macro-input 0.8.26", "const-hex", "heck", "indexmap", @@ -278,10 +303,28 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.117", - "syn-solidity", + "syn-solidity 0.8.26", "tiny-keccak", ] +[[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 1.5.7", + "const-hex", + "heck", + "indexmap", + "proc-macro-error2", + "proc-macro2", + "quote", + "sha3", + "syn 2.0.117", + "syn-solidity 1.5.7", +] + [[package]] name = "alloy-sol-macro-input" version = "0.8.26" @@ -295,7 +338,23 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.117", - "syn-solidity", + "syn-solidity 0.8.26", +] + +[[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.117", + "syn-solidity 1.5.7", ] [[package]] @@ -315,19 +374,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e960c4b52508ef2ae1e37cae5058e905e9ae099b107900067a503f8c454036f" dependencies = [ "alloy-json-abi", - "alloy-primitives", - "alloy-sol-macro", + "alloy-primitives 0.8.26", + "alloy-sol-macro 0.8.26", "const-hex", "serde", ] +[[package]] +name = "alloy-sol-types" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64612d29379782a5dde6f4b6570d9c756d734d760c0c94c254d361e678a6591f" +dependencies = [ + "alloy-primitives 1.5.7", + "alloy-sol-macro 1.5.7", +] + [[package]] name = "alloy-trie" version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d95a94854e420f07e962f7807485856cde359ab99ab6413883e15235ad996e8b" dependencies = [ - "alloy-primitives", + "alloy-primitives 0.8.26", "alloy-rlp", "arrayvec", "derive_more 1.0.0", @@ -703,6 +772,15 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bit-set" version = "0.8.0" @@ -1399,6 +1477,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "erased-serde" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + [[package]] name = "errno" version = "0.3.14" @@ -1498,7 +1587,7 @@ dependencies = [ "futures-core", "futures-sink", "nanorand", - "spin", + "spin 0.9.8", ] [[package]] @@ -1513,12 +1602,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" -[[package]] -name = "foldhash" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" - [[package]] name = "fs-err" version = "3.3.0" @@ -1702,6 +1785,18 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "group" version = "0.13.0" @@ -1744,7 +1839,7 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "foldhash 0.1.5", + "foldhash", "serde", ] @@ -1753,14 +1848,6 @@ name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash 0.2.0", - "rayon", - "serde", - "serde_core", -] [[package]] name = "hashlink" @@ -2287,12 +2374,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -2389,10 +2470,11 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "miden-agglayer" -version = "0.14.0-alpha.1" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e492a6044cf8875a64d7eec130d260f2eda1c783795261f00d5d52837ed027bd" +checksum = "4302fc29d77db3d2c6323d1b211e503aafb91db2d572ef30c68829347fe79352" dependencies = [ + "alloy-sol-types 1.5.7", "fs-err", "miden-assembly", "miden-core", @@ -2409,37 +2491,39 @@ dependencies = [ [[package]] name = "miden-air" -version = "0.20.6" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cca9632323bd4e32ae5b21b101ed417a646f5d72196b1bf3f1ca889a148322a" +checksum = "d15646ebc95906b2a7cb66711d1e184f53fd6edc2605730bbcf0c2a129f792cf" dependencies = [ "miden-core", + "miden-crypto", "miden-utils-indexing", "thiserror 2.0.18", - "winter-air", - "winter-prover", + "tracing", ] [[package]] name = "miden-assembly" -version = "0.20.6" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2395b2917aea613a285d3425d1ca07e6c45442e2b34febdea2081db555df62fc" +checksum = "ae6013b3a390e0dcb29242f4480a7727965887bbf0903466c88f362b4cb20c0e" dependencies = [ "env_logger", "log", "miden-assembly-syntax", "miden-core", "miden-mast-package", + "miden-package-registry", + "miden-project", "smallvec", "thiserror 2.0.18", ] [[package]] name = "miden-assembly-syntax" -version = "0.20.6" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f9bed037d137f209b9e7b28811ec78c0536b3f9259d6f4ceb5823c87513b346" +checksum = "996156b8f7c5fe6be17dea71089c6d7985c2dec1e3a4fec068b1dfc690e25df5" dependencies = [ "aho-corasick", "env_logger", @@ -2455,15 +2539,16 @@ dependencies = [ "regex", "rustc_version 0.4.1", "semver 1.0.27", + "serde", "smallvec", "thiserror 2.0.18", ] [[package]] name = "miden-block-prover" -version = "0.14.0-alpha.1" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9564dfb23c529aad68369845b6897a6f62bacdeab7c00db432a5f16670764d4" +checksum = "cde56bcea3cebe307786a856e204d84e7987c318e5a2909bcbb655d16286ce31" dependencies = [ "miden-protocol", "thiserror 2.0.18", @@ -2471,16 +2556,16 @@ dependencies = [ [[package]] name = "miden-client" -version = "0.14.0-alpha.1" -source = "git+https://github.com/0xMiden/miden-client?branch=agglayer-integration-tests#ac3d953d6092a93e01b2a4bb9c464f395525390d" +version = "0.14.0" +source = "git+https://github.com/0xMiden/miden-client.git?rev=f7cdf263781999e8f81ba608f9c5a91218747d98#f7cdf263781999e8f81ba608f9c5a91218747d98" dependencies = [ "anyhow", "async-trait", "chrono", "futures", "getrandom 0.3.4", + "gloo-timers", "hex", - "miden-mast-package", "miden-node-proto-build", "miden-note-transport-proto-build", "miden-protocol", @@ -2495,6 +2580,7 @@ dependencies = [ "serde", "serde_json", "thiserror 2.0.18", + "tokio", "tonic", "tonic-health", "tonic-prost", @@ -2507,8 +2593,8 @@ dependencies = [ [[package]] name = "miden-client-sqlite-store" -version = "0.14.0-alpha.1" -source = "git+https://github.com/0xMiden/miden-client?branch=agglayer-integration-tests#ac3d953d6092a93e01b2a4bb9c464f395525390d" +version = "0.14.0" +source = "git+https://github.com/0xMiden/miden-client.git?rev=f7cdf263781999e8f81ba608f9c5a91218747d98#f7cdf263781999e8f81ba608f9c5a91218747d98" dependencies = [ "anyhow", "async-trait", @@ -2525,9 +2611,9 @@ dependencies = [ [[package]] name = "miden-core" -version = "0.20.6" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8714aa5f86c59e647b7417126b32adc4ef618f835964464f5425549df76b6d03" +checksum = "fdec54a321cdf3d23e9ef615e91cb858038c6b4d4202507bdec048fc6d7763e4" dependencies = [ "derive_more 2.1.1", "itertools 0.14.0", @@ -2536,37 +2622,37 @@ dependencies = [ "miden-formatting", "miden-utils-core-derive", "miden-utils-indexing", + "miden-utils-sync", "num-derive", "num-traits", "proptest", "proptest-derive", + "serde", "thiserror 2.0.18", - "winter-math", - "winter-utils", ] [[package]] name = "miden-core-lib" -version = "0.20.6" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bb16a4d39202c59a7964d3585cd5af21a46a759ff6452cb5f20723ed5af4362" +checksum = "621e8fa911a790bcf3cd3aedce80bc10922a19d6181f08ff3ca078f955cff70b" dependencies = [ "env_logger", "fs-err", "miden-assembly", "miden-core", "miden-crypto", + "miden-package-registry", "miden-processor", "miden-utils-sync", - "sha2", "thiserror 2.0.18", ] [[package]] name = "miden-crypto" -version = "0.19.8" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be59336a868de7c379eace9450563c2d7f4a0b7ab936835ec5a340dcd8d9a5ed" +checksum = "0ed0a034a460e27723dcfdf25effffab84331c3b46b13e7a1bd674197cc71bfe" dependencies = [ "blake3", "cc", @@ -2575,32 +2661,41 @@ dependencies = [ "ed25519-dalek", "flume", "glob", - "hashbrown 0.16.1", "hkdf", "k256", "miden-crypto-derive", + "miden-field", + "miden-serde-utils", "num", "num-complex", + "p3-blake3", + "p3-challenger", + "p3-dft", + "p3-goldilocks", + "p3-keccak", + "p3-matrix", + "p3-maybe-rayon", + "p3-miden-lifted-stark", + "p3-symmetric", + "p3-util", "rand 0.9.2", "rand_chacha 0.9.0", "rand_core 0.9.5", "rand_hc", "rayon", + "serde", "sha2", "sha3", "subtle", "thiserror 2.0.18", - "winter-crypto", - "winter-math", - "winter-utils", "x25519-dalek", ] [[package]] name = "miden-crypto-derive" -version = "0.19.8" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06f2e7a7ac7d01e03a7340224722e75494826fe6b61a3796b77fbb044d018287" +checksum = "e8bf6ebde028e79bcc61a3632d2f375a5cc64caa17d014459f75015238cb1e08" dependencies = [ "quote", "syn 2.0.117", @@ -2608,9 +2703,9 @@ dependencies = [ [[package]] name = "miden-debug-types" -version = "0.20.6" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd1494f102ad5b9fa43e391d2601186dc601f41ab7dcd8a23ecca9bf3ef930f4" +checksum = "d6e50274d11c80b901cf6c90362de8c98c8c8ad6030c80624d683b63d899a0fb" dependencies = [ "memchr", "miden-crypto", @@ -2624,6 +2719,24 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "miden-field" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38011348f4fb4c9e5ce1f471203d024721c00e3b60a91aa91aaefe6738d8b5ea" +dependencies = [ + "miden-serde-utils", + "num-bigint", + "p3-challenger", + "p3-field", + "p3-goldilocks", + "paste", + "rand 0.10.1", + "serde", + "subtle", + "thiserror 2.0.18", +] + [[package]] name = "miden-formatting" version = "0.1.1" @@ -2635,13 +2748,15 @@ dependencies = [ [[package]] name = "miden-mast-package" -version = "0.20.6" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692185bfbe0ecdb28bf623f1f8c88282cd6727ba081a28e23b301bdde1b45be4" +checksum = "bc8b2e3447fcde1f0e6b76e5219f129517639772cb02ca543177f0584e315288" dependencies = [ "derive_more 2.1.1", "miden-assembly-syntax", "miden-core", + "miden-debug-types", + "serde", "thiserror 2.0.18", ] @@ -2651,8 +2766,6 @@ version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eef536978f24a179d94fa2a41e4f92b28e7d8aab14b8d23df28ad2a3d7098b20" dependencies = [ - "backtrace", - "backtrace-ext", "cfg-if", "futures", "indenter", @@ -2663,13 +2776,9 @@ dependencies = [ "rustc_version 0.2.3", "rustversion", "serde_json", - "spin", + "spin 0.9.8", "strip-ansi-escapes", - "supports-color", - "supports-hyperlinks", - "supports-unicode", "syn 2.0.117", - "terminal_size 0.3.0", "textwrap", "thiserror 2.0.18", "trybuild", @@ -2689,9 +2798,9 @@ dependencies = [ [[package]] name = "miden-node-proto-build" -version = "0.14.0-alpha.4" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20ac57ddec6bf630b5301630c953ed23661575b70e2fcc3ff221c660f3006a30" +checksum = "289194699f5acd63feb735c692fab0ff2e77aa67bf67dfd2608e0c573591a14b" dependencies = [ "build-rs", "fs-err", @@ -2702,9 +2811,9 @@ dependencies = [ [[package]] name = "miden-note-transport-proto-build" -version = "0.2.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec23738a2eee393524a849a8dce982ad824050cc54abde145d4fb62b92c84198" +checksum = "5e183c4e36c155edea086b79cd4395d70e3a905eb86778827e12a8e0d0110d2b" dependencies = [ "fs-err", "miette", @@ -2712,11 +2821,26 @@ dependencies = [ "tonic-prost-build", ] +[[package]] +name = "miden-package-registry" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "969ba3942052e52b3968e34dbd1c52c707e75777ee42ebdae2c8f57af56cf6cf" +dependencies = [ + "miden-assembly-syntax", + "miden-core", + "miden-mast-package", + "pubgrub", + "serde", + "smallvec", + "thiserror 2.0.18", +] + [[package]] name = "miden-processor" -version = "0.20.6" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e09f7916b1e7505f74a50985a185fdea4c0ceb8f854a34c90db28e3f7da7ab6" +checksum = "6ec6cecbf22bd92b73a931ee80b424e46b8b7cdf4f2f3c364c25c5c15d2840da" dependencies = [ "itertools 0.14.0", "miden-air", @@ -2729,14 +2853,29 @@ dependencies = [ "thiserror 2.0.18", "tokio", "tracing", - "winter-prover", +] + +[[package]] +name = "miden-project" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3840520c01881534fbbceb6b3687ec1c407fbaf310a35ce415fd3510abc52fdb" +dependencies = [ + "miden-assembly-syntax", + "miden-core", + "miden-mast-package", + "miden-package-registry", + "serde", + "serde-untagged", + "thiserror 2.0.18", + "toml 1.0.6+spec-1.1.0", ] [[package]] name = "miden-protocol" -version = "0.14.0-alpha.1" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a88effeac994eb55b8dc4f93fbfd71a5d916dfaba1099896e27a0ee42c488c1" +checksum = "e860cc978d3467297de076e9bd22f0573b82ef73a3d223d6bb957731a45b8164" dependencies = [ "bech32", "fs-err", @@ -2758,16 +2897,15 @@ dependencies = [ "semver 1.0.27", "serde", "thiserror 2.0.18", - "toml 0.9.12+spec-1.1.0", + "toml 1.0.6+spec-1.1.0", "walkdir", - "winter-rand-utils", ] [[package]] name = "miden-protocol-macros" -version = "0.14.0-alpha.1" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb28b730005e5f8b08d615ea9216f8cab77b3a7439fa54d5e39d2ec43ef53a3" +checksum = "4daec4a5a6f050a670a8639e78e017ab11ef0bf2e253b012505f25e6247c13e7" dependencies = [ "proc-macro2", "quote", @@ -2776,23 +2914,27 @@ dependencies = [ [[package]] name = "miden-prover" -version = "0.20.6" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d45e30526be72b8af0fd1d8b24c9cba8ac1187ca335dcee38b8e5e20234e7698" +checksum = "6bb2c94e36f57684d7fa0cd382adeedc1728d502dbbe69ad1c12f4a931f45511" dependencies = [ + "bincode", "miden-air", + "miden-core", + "miden-crypto", "miden-debug-types", "miden-processor", + "serde", + "thiserror 2.0.18", + "tokio", "tracing", - "winter-maybe-async", - "winter-prover", ] [[package]] name = "miden-remote-prover-client" -version = "0.14.0-alpha.4" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64fae30a877ba6481e62ed2a553277633c91a592686afa73ba91c2f15ed7e17f" +checksum = "47e756825b1b271549df850ae87b6544be9e5d68353f9d2afc7a55a567e1a01e" dependencies = [ "build-rs", "fs-err", @@ -2815,10 +2957,10 @@ name = "miden-rpc-proxy" version = "0.1.0" dependencies = [ "alloy-consensus", - "alloy-primitives", + "alloy-primitives 0.8.26", "alloy-rlp", "alloy-rpc-types", - "alloy-sol-types", + "alloy-sol-types 0.8.26", "anyhow", "clap", "dashmap", @@ -2827,7 +2969,6 @@ dependencies = [ "jsonrpsee", "k256", "miden-agglayer", - "miden-assembly", "miden-client", "miden-client-sqlite-store", "miden-protocol", @@ -2851,11 +2992,21 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "miden-serde-utils" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff78082e9b4ca89863e68da01b35f8a4029ee6fd912e39fa41fde4273a7debab" +dependencies = [ + "p3-field", + "p3-goldilocks", +] + [[package]] name = "miden-standards" -version = "0.14.0-alpha.1" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cef036bbfec29acba92751a13d05844bbcf080140201097b419c9ad1927e367" +checksum = "f455a087f41c30636b45ead961d1e66114d2d20661887b307cede05307eeb942" dependencies = [ "fs-err", "miden-assembly", @@ -2871,9 +3022,9 @@ dependencies = [ [[package]] name = "miden-testing" -version = "0.14.0-alpha.1" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e980777d0f7e6069942b14d4e7cb3d4d137b323ddfa15722a3bd21e9d13fdd2e" +checksum = "a84430e84c6dee90d9bd92568be1c3082113f0b4b36f9db7933380f0295207f9" dependencies = [ "anyhow", "itertools 0.14.0", @@ -2890,14 +3041,13 @@ dependencies = [ "rand 0.9.2", "rand_chacha 0.9.0", "thiserror 2.0.18", - "winterfell", ] [[package]] name = "miden-tx" -version = "0.14.0-alpha.1" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c67e0df9adcf29c9111df65acf408ae05952b8bc6569f571963676f97668d83f" +checksum = "6d788795041ce5e6f947a3256314373171e4877c11b86fafeabcec4d8b8628d9" dependencies = [ "miden-processor", "miden-protocol", @@ -2909,9 +3059,9 @@ dependencies = [ [[package]] name = "miden-tx-batch-prover" -version = "0.14.0-alpha.1" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba29f8f6ecae671eff8b52b4c19eca8db5964c0b45b5d68c3ce38a57a8367931" +checksum = "ce059e2d599266b00708f6f1bff6af5cf82683e76df3ec812c2d1c72e880f943" dependencies = [ "miden-protocol", "miden-tx", @@ -2919,9 +3069,9 @@ dependencies = [ [[package]] name = "miden-utils-core-derive" -version = "0.20.6" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b1d490e6d7b509622d3c2cc69ffd66ad48bf953dc614579b568fe956ce0a6c" +checksum = "3846c8674ccec0c37005f99c1a599a24790ba2a5e5f4e1c7aec5f456821df835" dependencies = [ "proc-macro2", "quote", @@ -2930,9 +3080,9 @@ dependencies = [ [[package]] name = "miden-utils-diagnostics" -version = "0.20.6" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52658f6dc091c1c78e8b35ee3e7ff3dad53051971a3c514e461f581333758fe7" +checksum = "397f5d1e8679cf17cf7713ffd9654840791a6ed5818b025bbc2fbfdce846579a" dependencies = [ "miden-crypto", "miden-debug-types", @@ -2943,44 +3093,52 @@ dependencies = [ [[package]] name = "miden-utils-indexing" -version = "0.20.6" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeff7bcb7875b222424bdfb657a7cf21a55e036aa7558ebe1f5d2e413b440d0d" +checksum = "c8834e76299686bcce3de1685158aa4cff49b7fa5e0e00a6cc811e8f2cf5775f" dependencies = [ + "miden-crypto", + "serde", "thiserror 2.0.18", ] [[package]] name = "miden-utils-sync" -version = "0.20.6" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d53d1ab5b275d8052ad9c4121071cb184bc276ee74354b0d8a2075e5c1d1f0" +checksum = "6a9e9747e9664c1a0997bb040ae291306ea0a1c74a572141ec66cec855c1b0e8" dependencies = [ "lock_api", "loom", + "once_cell", "parking_lot", ] [[package]] name = "miden-verifier" -version = "0.20.6" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b13816663794beb15c8a4721c15252eb21f3b3233525684f60c7888837a98ff4" +checksum = "c4580df640d889c9f3c349cd2268968e44a99a8cf0df6c36ae5b1fb273712b00" dependencies = [ + "bincode", "miden-air", "miden-core", + "miden-crypto", + "serde", "thiserror 2.0.18", "tracing", - "winter-verifier", ] [[package]] name = "midenc-hir-type" -version = "0.4.3" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d4cfab04baffdda3fb9eafa5f873604059b89a1699aa95e4f1057397a69f0b5" +checksum = "2eb29d7c049fb69373c7e775e3d4411e63e4ee608bc43826282ba62c6ec9f891" dependencies = [ "miden-formatting", + "miden-serde-utils", + "serde", + "serde_repr", "smallvec", "thiserror 2.0.18", ] @@ -2999,7 +3157,7 @@ dependencies = [ "supports-color", "supports-hyperlinks", "supports-unicode", - "terminal_size 0.4.3", + "terminal_size", "textwrap", "unicode-width 0.1.14", ] @@ -3223,70 +3381,385 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" [[package]] -name = "parity-scale-codec" -version = "3.7.5" +name = "p3-air" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" +checksum = "9f2ec9cbfc642fc5173817287c3f8b789d07743b5f7e812d058b7a03e344f9ab" dependencies = [ - "arrayvec", - "bitvec", - "byte-slice-cast", - "const_format", - "impl-trait-for-tuples", - "parity-scale-codec-derive", - "rustversion", - "serde", + "p3-field", + "p3-matrix", + "tracing", ] [[package]] -name = "parity-scale-codec-derive" -version = "3.7.5" +name = "p3-blake3" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" +checksum = "b667f43b19499dd939c9e2553aa95688936a88360d50117dae3c8848d07dbc70" dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.117", + "blake3", + "p3-symmetric", + "p3-util", ] [[package]] -name = "parking_lot" -version = "0.12.5" +name = "p3-challenger" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +checksum = "4a0b490c745a7d2adeeafff06411814c8078c432740162332b3cd71be0158a76" dependencies = [ - "lock_api", - "parking_lot_core", + "p3-field", + "p3-maybe-rayon", + "p3-monty-31", + "p3-symmetric", + "p3-util", + "tracing", ] [[package]] -name = "parking_lot_core" -version = "0.9.12" +name = "p3-commit" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +checksum = "916ae7989d5c3b49f887f5c55b2f9826bdbb81aaebf834503c4145d8b267c829" dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link", + "itertools 0.14.0", + "p3-field", + "p3-matrix", + "p3-util", + "serde", ] [[package]] -name = "paste" -version = "1.0.15" +name = "p3-dft" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +checksum = "55301e91544440254977108b85c32c09d7ea05f2f0dd61092a2825339906a4a7" +dependencies = [ + "itertools 0.14.0", + "p3-field", + "p3-matrix", + "p3-maybe-rayon", + "p3-util", + "spin 0.10.0", + "tracing", +] [[package]] -name = "pem" -version = "3.0.6" +name = "p3-field" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +checksum = "85affca7fc983889f260655c4cf74163eebb94605f702e4b6809ead707cba54f" dependencies = [ - "base64", - "serde_core", + "itertools 0.14.0", + "num-bigint", + "p3-maybe-rayon", + "p3-util", + "paste", + "rand 0.10.1", + "serde", + "tracing", +] + +[[package]] +name = "p3-goldilocks" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca1081f5c47b940f2d75a11c04f62ea1cc58a5d480dd465fef3861c045c63cd" +dependencies = [ + "num-bigint", + "p3-challenger", + "p3-dft", + "p3-field", + "p3-mds", + "p3-poseidon1", + "p3-poseidon2", + "p3-symmetric", + "p3-util", + "paste", + "rand 0.10.1", + "serde", +] + +[[package]] +name = "p3-keccak" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcf27615ece1995e4fcf4c69740f1cf515d1481367a20b4b3ce7f4f1b8d70f7" +dependencies = [ + "p3-symmetric", + "p3-util", + "tiny-keccak", +] + +[[package]] +name = "p3-matrix" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53428126b009071563d1d07305a9de8be0d21de00b57d2475289ee32ffca6577" +dependencies = [ + "itertools 0.14.0", + "p3-field", + "p3-maybe-rayon", + "p3-util", + "rand 0.10.1", + "serde", + "tracing", +] + +[[package]] +name = "p3-maybe-rayon" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "082bf467011c06c768c579ec6eb9accb5e1e62108891634cc770396e917f978a" +dependencies = [ + "rayon", +] + +[[package]] +name = "p3-mds" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35209e6214102ea6ec6b8cb1b9c15a9b8e597a39f9173597c957f123bced81b3" +dependencies = [ + "p3-dft", + "p3-field", + "p3-symmetric", + "p3-util", + "rand 0.10.1", +] + +[[package]] +name = "p3-miden-lifted-air" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5c31c65fdc88952d7b301546add9670676e5b878aa0066dd929f107c203b006" +dependencies = [ + "p3-air", + "p3-field", + "p3-matrix", + "p3-util", + "thiserror 2.0.18", +] + +[[package]] +name = "p3-miden-lifted-fri" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab9932f1b0a16609a45cd4ee10a4d35412728bc4b38837c7979d7c85d8dcc9fc" +dependencies = [ + "p3-challenger", + "p3-commit", + "p3-dft", + "p3-field", + "p3-matrix", + "p3-maybe-rayon", + "p3-miden-lmcs", + "p3-miden-transcript", + "p3-util", + "rand 0.10.1", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "p3-miden-lifted-stark" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3956ab7270c3cdd53ca9796d39ae1821984eb977415b0672110f9666bff5d8" +dependencies = [ + "p3-challenger", + "p3-dft", + "p3-field", + "p3-matrix", + "p3-maybe-rayon", + "p3-miden-lifted-air", + "p3-miden-lifted-fri", + "p3-miden-lmcs", + "p3-miden-stateful-hasher", + "p3-miden-transcript", + "p3-util", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "p3-miden-lmcs" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c46791c983e772136db3d48f102431457451447abb9087deb6c8ce3c1efc86" +dependencies = [ + "p3-commit", + "p3-field", + "p3-matrix", + "p3-maybe-rayon", + "p3-miden-stateful-hasher", + "p3-miden-transcript", + "p3-symmetric", + "p3-util", + "rand 0.10.1", + "serde", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "p3-miden-stateful-hasher" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec47a9d9615eb3d9d2a59b00d19751d9ad85384b55886827913d680d912eac6a" +dependencies = [ + "p3-field", + "p3-symmetric", +] + +[[package]] +name = "p3-miden-transcript" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c565647487e4a949f67e6f115b0391d6cb82ac8e561165789939bab23d0ae7" +dependencies = [ + "p3-challenger", + "p3-field", + "serde", + "thiserror 2.0.18", +] + +[[package]] +name = "p3-monty-31" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffa8c99ec50c035020bbf5457c6a729ba6a975719c1a8dd3f16421081e4f650c" +dependencies = [ + "itertools 0.14.0", + "num-bigint", + "p3-dft", + "p3-field", + "p3-matrix", + "p3-maybe-rayon", + "p3-mds", + "p3-poseidon1", + "p3-poseidon2", + "p3-symmetric", + "p3-util", + "paste", + "rand 0.10.1", + "serde", + "spin 0.10.0", + "tracing", +] + +[[package]] +name = "p3-poseidon1" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a018b618e3fa0aec8be933b1d8e404edd23f46991f6bf3f5c2f3f95e9413fe9" +dependencies = [ + "p3-field", + "p3-symmetric", + "rand 0.10.1", +] + +[[package]] +name = "p3-poseidon2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "256a668a9ba916f8767552f13d0ba50d18968bc74a623bfdafa41e2970c944d0" +dependencies = [ + "p3-field", + "p3-mds", + "p3-symmetric", + "p3-util", + "rand 0.10.1", +] + +[[package]] +name = "p3-symmetric" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c60a71a1507c13611b0f2b0b6e83669fd5b76f8e3115bcbced5ccfdf3ca7807" +dependencies = [ + "itertools 0.14.0", + "p3-field", + "p3-util", + "serde", +] + +[[package]] +name = "p3-util" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8b766b9e9254bf3fa98d76e42cf8a5b30628c182dfd5272d270076ee12f0fc0" +dependencies = [ + "rayon", + "serde", + "transpose", +] + +[[package]] +name = "parity-scale-codec" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "const_format", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "rustversion", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", ] [[package]] @@ -3461,6 +3934,17 @@ dependencies = [ "uint 0.10.0", ] +[[package]] +name = "priority-queue" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93980406f12d9f8140ed5abe7155acb10bb1e69ea55c88960b9c2f117445ef96" +dependencies = [ + "equivalent", + "indexmap", + "serde", +] + [[package]] name = "proc-macro-crate" version = "3.5.0" @@ -3623,6 +4107,20 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "pubgrub" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f5df7e552bc7edd075f5783a87fbfc21d6a546e32c16985679c488c18192d83" +dependencies = [ + "indexmap", + "log", + "priority-queue", + "rustc-hash", + "thiserror 2.0.18", + "version-ranges", +] + [[package]] name = "pulldown-cmark" version = "0.13.1" @@ -3698,6 +4196,15 @@ dependencies = [ "rand_core 0.9.5", ] +[[package]] +name = "rand" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +dependencies = [ + "rand_core 0.10.1", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -3736,6 +4243,12 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + [[package]] name = "rand_hc" version = "0.3.2" @@ -3975,19 +4488,6 @@ dependencies = [ "semver 1.0.27", ] -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", -] - [[package]] name = "rustix" version = "1.1.4" @@ -3997,7 +4497,7 @@ dependencies = [ "bitflags", "errno", "libc", - "linux-raw-sys 0.12.1", + "linux-raw-sys", "windows-sys 0.61.2", ] @@ -4186,6 +4686,18 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-untagged" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" +dependencies = [ + "erased-serde", + "serde", + "serde_core", + "typeid", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -4219,6 +4731,17 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "serde_spanned" version = "0.6.9" @@ -4388,6 +4911,15 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spin" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" +dependencies = [ + "lock_api", +] + [[package]] name = "spki" version = "0.7.3" @@ -4404,6 +4936,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strength_reduce" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" + [[package]] name = "string_cache" version = "0.8.9" @@ -4514,6 +5052,18 @@ dependencies = [ "syn 2.0.117", ] +[[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.117", +] + [[package]] name = "sync_wrapper" version = "1.0.2" @@ -4541,7 +5091,7 @@ dependencies = [ "fastrand", "getrandom 0.4.2", "once_cell", - "rustix 1.1.4", + "rustix", "windows-sys 0.61.2", ] @@ -4563,23 +5113,13 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "terminal_size" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" -dependencies = [ - "rustix 0.38.44", - "windows-sys 0.48.0", -] - [[package]] name = "terminal_size" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" dependencies = [ - "rustix 1.1.4", + "rustix", "windows-sys 0.60.2", ] @@ -4779,21 +5319,6 @@ dependencies = [ "toml_edit 0.22.27", ] -[[package]] -name = "toml" -version = "0.9.12+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" -dependencies = [ - "indexmap", - "serde_core", - "serde_spanned 1.0.4", - "toml_datetime 0.7.5+spec-1.1.0", - "toml_parser", - "toml_writer", - "winnow", -] - [[package]] name = "toml" version = "1.0.6+spec-1.1.0" @@ -4818,15 +5343,6 @@ dependencies = [ "serde", ] -[[package]] -name = "toml_datetime" -version = "0.7.5+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" -dependencies = [ - "serde_core", -] - [[package]] name = "toml_datetime" version = "1.0.0+spec-1.1.0" @@ -5096,6 +5612,16 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "transpose" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad61aed86bc3faea4300c7aee358b4c6d0c8d6ccc36524c96e4c92ccf26e77e" +dependencies = [ + "num-integer", + "strength_reduce", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -5118,6 +5644,12 @@ dependencies = [ "toml 1.0.6+spec-1.1.0", ] +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + [[package]] name = "typenum" version = "1.19.0" @@ -5248,6 +5780,15 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version-ranges" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e9bd4e9c9ff6a2a9b5969462ba26216af3e010df0377dad8320ab515262ef8" +dependencies = [ + "smallvec", +] + [[package]] name = "version_check" version = "0.9.5" @@ -5517,15 +6058,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-sys" version = "0.60.2" @@ -5739,119 +6271,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "winter-air" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef01227f23c7c331710f43b877a8333f5f8d539631eea763600f1a74bf018c7c" -dependencies = [ - "libm", - "winter-crypto", - "winter-fri", - "winter-math", - "winter-utils", -] - -[[package]] -name = "winter-crypto" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cdb247bc142438798edb04067ab72a22cf815f57abbd7b78a6fa986fc101db8" -dependencies = [ - "blake3", - "sha3", - "winter-math", - "winter-utils", -] - -[[package]] -name = "winter-fri" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd592b943f9d65545683868aaf1b601eb66e52bfd67175347362efff09101d3a" -dependencies = [ - "winter-crypto", - "winter-math", - "winter-utils", -] - -[[package]] -name = "winter-math" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aecfb48ee6a8b4746392c8ff31e33e62df8528a3b5628c5af27b92b14aef1ea" -dependencies = [ - "winter-utils", -] - -[[package]] -name = "winter-maybe-async" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d31a19dae58475d019850e25b0170e94b16d382fbf6afee9c0e80fdc935e73e" -dependencies = [ - "quote", - "syn 2.0.117", -] - -[[package]] -name = "winter-prover" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84cc631ed56cd39b78ef932c1ec4060cc6a44d114474291216c32f56655b3048" -dependencies = [ - "tracing", - "winter-air", - "winter-crypto", - "winter-fri", - "winter-math", - "winter-maybe-async", - "winter-utils", -] - -[[package]] -name = "winter-rand-utils" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4ff3b651754a7bd216f959764d0a5ab6f4b551c9a3a08fb9ccecbed594b614a" -dependencies = [ - "rand 0.9.2", - "winter-utils", -] - -[[package]] -name = "winter-utils" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9951263ef5317740cd0f49e618db00c72fabb70b75756ea26c4d5efe462c04dd" -dependencies = [ - "rayon", -] - -[[package]] -name = "winter-verifier" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0425ea81f8f703a1021810216da12003175c7974a584660856224df04b2e2fdb" -dependencies = [ - "winter-air", - "winter-crypto", - "winter-fri", - "winter-math", - "winter-utils", -] - -[[package]] -name = "winterfell" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f824ddd5aec8ca6a54307f20c115485a8a919ea94dd26d496d856ca6185f4f" -dependencies = [ - "winter-air", - "winter-prover", - "winter-verifier", -] - [[package]] name = "wit-bindgen" version = "0.51.0" diff --git a/Cargo.toml b/Cargo.toml index 2dffc80..aa3c55f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,16 +23,19 @@ alloy-rlp = "0.3" alloy-rpc-types = "0.8" alloy-consensus = { version = "0.8", features = ["k256"] } -# Miden crates (agglayer-integration-tests branch, PR #1882) -miden-client = { git = "https://github.com/0xMiden/miden-client", branch = "agglayer-integration-tests", features = ["testing", "tonic"] } -miden-client-sqlite-store = { git = "https://github.com/0xMiden/miden-client", branch = "agglayer-integration-tests", package = "miden-client-sqlite-store" } - -# Miden base crates (0.14-alpha, matching miden-client agglayer-integration-tests branch) -miden-protocol = { version = "0.14.0-alpha", features = ["std"] } -miden-standards = { version = "0.14.0-alpha" } -miden-agglayer = { version = "0.14.0-alpha" } -miden-tx = { version = "0.14.0-alpha" } -miden-assembly = "0.20" +# miden-client (pinned by rev to ajl-agglayer-integration-tests-v0.14.0 branch head for reproducibility) +miden-client = { git = "https://github.com/0xMiden/miden-client.git", rev = "f7cdf263781999e8f81ba608f9c5a91218747d98", default-features = false, features = ["testing", "tonic"] } +miden-client-sqlite-store = { git = "https://github.com/0xMiden/miden-client.git", rev = "f7cdf263781999e8f81ba608f9c5a91218747d98" } + +# miden-base — loose caret pins. Pinning to `=0.14.4` exactly caused a runtime +# STARK verification regression in the miden-node NTX builder; the 0.14.6 +# transitive node deps that cargo picks when left alone are what actually work +# with miden-client @ f7cdf26 in practice. Keep the caret; rely on the git rev +# pin above for reproducibility. +miden-protocol = { version = "0.14", default-features = false, features = ["std"] } +miden-standards = { version = "0.14", default-features = false } +miden-agglayer = { version = "0.14" } +miden-tx = { version = "0.14", default-features = false } # Additional dependencies sha3 = "0.10" @@ -69,7 +72,7 @@ tempfile = "3" rand = "0.9" k256 = "0.13" # Enable testing feature for miden-protocol in tests (for AccountId::dummy) -miden-protocol = { version = "0.14.0-alpha", features = ["std", "testing"] } +miden-protocol = { version = "0.14", default-features = false, features = ["std", "testing"] } [[bin]] name = "verify-notes" diff --git a/Dockerfile.miden-node b/Dockerfile.miden-node index b49453c..eb5ef6c 100644 --- a/Dockerfile.miden-node +++ b/Dockerfile.miden-node @@ -1,8 +1,10 @@ -# Build stage for testing-node-builder (from miden-client workspace) -FROM rust:1.91-slim-bookworm AS builder +# Build miden-node + miden-genesis from 0xMiden/node v0.14.6 (production release). +# This replaces the earlier testing-node-builder from miden-client; the agglayer +# bridge is now seeded via miden-genesis + validator bootstrap --genesis-config-file. +# The faucet is NOT created in genesis — the proxy deploys it at first claim. +FROM rust:1.93-slim-bookworm AS builder -# Install build dependencies -RUN apt-get update && apt-get install -y \ +RUN apt-get update && apt-get install -y --no-install-recommends \ pkg-config \ libssl-dev \ libsqlite3-dev \ @@ -10,76 +12,94 @@ RUN apt-get update && apt-get install -y \ git \ cmake \ clang \ + llvm \ + libclang-dev \ + ca-certificates \ && rm -rf /var/lib/apt/lists/* WORKDIR /build -# Clone miden-client from agglayer-integration-tests branch (PR #1882) -# Source: https://github.com/0xMiden/miden-client -RUN git clone --depth 1 --branch agglayer-integration-tests \ - https://github.com/0xMiden/miden-client.git . - -# Save the commit SHA for labeling -RUN git rev-parse HEAD > /tmp/miden-client-commit.txt && \ - echo "Miden-client commit: $(cat /tmp/miden-client-commit.txt)" +# Clone the production node repo pinned to v0.14.6. +# Matches the miden-node-* 0.14.6 transitive deps our proxy's Cargo.lock resolves. +RUN git clone --depth 1 --branch v0.14.6 https://github.com/0xMiden/node.git . && \ + git rev-parse HEAD > /tmp/miden-node-commit.txt && \ + echo "Tag: v0.14.6" >> /tmp/miden-node-commit.txt -# Patch: bind RPC to 0.0.0.0 instead of 127.0.0.1 for Docker port forwarding -RUN sed -i 's|format!("127.0.0.1:{}", self.rpc_port)|format!("0.0.0.0:{}", self.rpc_port)|' \ - crates/testing/node-builder/src/lib.rs +# Build both binaries we need. +RUN cargo build --release --locked --bin miden-node && \ + cargo build --release --locked --bin miden-genesis -# Build the testing-node-builder binary -RUN cargo build --release --package node-builder - -# Runtime stage +# Runtime FROM debian:bookworm-slim -# Copy the commit SHA from builder -COPY --from=builder /tmp/miden-client-commit.txt /app/miden-client-commit.txt - -RUN apt-get update && apt-get install -y \ +RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates \ libssl3 \ libsqlite3-0 \ + sqlite3 \ curl \ netcat-openbsd \ && rm -rf /var/lib/apt/lists/* WORKDIR /app -COPY --from=builder /build/target/release/testing-node-builder /usr/local/bin/ +COPY --from=builder /build/target/release/miden-node /usr/local/bin/miden-node +COPY --from=builder /build/target/release/miden-genesis /usr/local/bin/miden-genesis +COPY --from=builder /tmp/miden-node-commit.txt /app/miden-node-commit.txt -# Add label with source info -LABEL org.opencontainers.image.source="https://github.com/0xMiden/miden-client" \ - org.opencontainers.image.ref.name="agglayer-integration-tests" +LABEL org.opencontainers.image.source="https://github.com/0xMiden/node" \ + org.opencontainers.image.ref.name="v0.14.6" -# gRPC port EXPOSE 57291 -# Create data directory -RUN mkdir -p /app/data - -# Create entrypoint script -# testing-node-builder uses ./data as its data dir and AGGLAYER_GENESIS=1 -# to enable agglayer genesis accounts (bridge_admin, ger_manager, bridge, faucet). -# The .mac files are written to ./data/ alongside the node database. -RUN printf '%s\n' \ - '#!/bin/bash' \ - 'set -e' \ - '' \ - '# Log version info' \ - 'echo "=== Miden Testing Node Startup ==="' \ - 'if [ -f /app/miden-client-commit.txt ]; then' \ - ' echo "Miden-client commit: $(cat /app/miden-client-commit.txt)"' \ - 'fi' \ - '' \ - '# Enable agglayer genesis accounts by default' \ - 'export AGGLAYER_GENESIS="${AGGLAYER_GENESIS:-1}"' \ - 'echo "AGGLAYER_GENESIS=$AGGLAYER_GENESIS"' \ - '' \ - 'echo "Starting testing-node-builder..."' \ - 'exec testing-node-builder "$@"' \ - > /usr/local/bin/entrypoint.sh && \ - chmod +x /usr/local/bin/entrypoint.sh +RUN mkdir -p /app/data /app/accounts /app/genesis + +# Bootstrap flow: +# 1. miden-genesis → writes bridge.mac, bridge_admin.mac, ger_manager.mac, genesis.toml +# to /app/genesis +# 2. miden-node validator bootstrap → consumes genesis.toml, emits genesis.dat into /app/data +# and copies .mac files into /app/accounts +# 3. miden-node store bootstrap → initializes store DB in /app/data +# 4. miden-node bundled start → runs the node +# +# On subsequent starts (volumes persisted), /app/data/genesis.dat exists so we skip +# bootstrap and go straight to start. +# +# Validator key: we rely on miden-node's INSECURE_VALIDATOR_KEY_HEX default for dev. +COPY <<'EOF' /usr/local/bin/entrypoint.sh +#!/bin/bash +set -euo pipefail + +echo "=== miden-node (v0.14.6 production) ===" +cat /app/miden-node-commit.txt + +DATA_DIR="${DATA_DIR:-/app/data}" +ACCOUNTS_DIR="${ACCOUNTS_DIR:-/app/accounts}" +GENESIS_DIR="${GENESIS_DIR:-/app/genesis}" +RPC_URL="${RPC_URL:-http://0.0.0.0:57291}" + +if [ ! -f "${DATA_DIR}/genesis.dat" ]; then + echo "First boot — running full bootstrap" + # validator bootstrap refuses non-empty dirs; start from scratch. + rm -rf "${DATA_DIR}" "${ACCOUNTS_DIR}" "${GENESIS_DIR}" + mkdir -p "${DATA_DIR}" "${ACCOUNTS_DIR}" "${GENESIS_DIR}" + + echo "[1/2] miden-genesis → ${GENESIS_DIR}" + miden-genesis --output-dir "${GENESIS_DIR}" + ls -la "${GENESIS_DIR}" + + echo "[2/2] bundled bootstrap (validator + store in one shot)" + miden-node bundled bootstrap --data-directory "${DATA_DIR}" --accounts-directory "${ACCOUNTS_DIR}" --genesis-config-file "${GENESIS_DIR}/genesis.toml" + + echo "Bootstrap complete" +else + echo "Existing genesis.dat found — skipping bootstrap" +fi + +echo "Starting miden-node bundled..." +exec miden-node bundled start --rpc.url "${RPC_URL}" --data-directory "${DATA_DIR}" "$@" +EOF +RUN chmod +x /usr/local/bin/entrypoint.sh ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] CMD [] diff --git a/README.md b/README.md index e87e319..e4ae3c7 100644 --- a/README.md +++ b/README.md @@ -90,9 +90,9 @@ and reuse them for all subsequent claims. - Rust 1.82 or later - Docker (for kurtosis-cdk integration) -- Miden node **agglayer-v0.1** tag (required for compatibility) +- Miden node **v0.14.6** tag (required for compatibility) - Built from source via `Dockerfile.miden-node` - - Source: https://github.com/0xPolygonMiden/miden-node (tag: agglayer-v0.1) + - Source: https://github.com/0xPolygonMiden/miden-node (tag: v0.14.6) - kurtosis-cdk deployment (for bridge-service integration) ## Building diff --git a/cache/solidity-files-cache.json b/cache/solidity-files-cache.json new file mode 100644 index 0000000..c1bedea --- /dev/null +++ b/cache/solidity-files-cache.json @@ -0,0 +1 @@ +{"_format":"","paths":{"artifacts":"out","build_infos":"out/build-info","sources":"src","tests":"test","scripts":"script","libraries":["lib"]},"files":{"/private/var/folders/7n/tm66gd7150xgn977n2g_c9yr0000gn/T/tmp.1VUVHm6ogJ/src/TestToken.sol":{"lastModificationDate":1774878394435,"contentHash":"a49bb0840fe40f7b","interfaceReprHash":null,"sourceName":"/private/var/folders/7n/tm66gd7150xgn977n2g_c9yr0000gn/T/tmp.1VUVHm6ogJ/src/TestToken.sol","imports":[],"versionRequirement":"^0.8.20","artifacts":{"TestToken":{"0.8.28":{"default":{"path":"TestToken.sol/TestToken.json","build_id":"72664b54bf1f6a85"}}}},"seenByCompiler":true},"/private/var/folders/7n/tm66gd7150xgn977n2g_c9yr0000gn/T/tmp.1hxrgibVb0/src/TestToken.sol":{"lastModificationDate":1774878463950,"contentHash":"a49bb0840fe40f7b","interfaceReprHash":null,"sourceName":"/private/var/folders/7n/tm66gd7150xgn977n2g_c9yr0000gn/T/tmp.1hxrgibVb0/src/TestToken.sol","imports":[],"versionRequirement":"^0.8.20","artifacts":{"TestToken":{"0.8.28":{"default":{"path":"src/TestToken.sol/TestToken.json","build_id":"796149271a07a4e7"}}}},"seenByCompiler":true}},"builds":["72664b54bf1f6a85","796149271a07a4e7"],"profiles":{"default":{"solc":{"optimizer":{"enabled":false,"runs":200},"metadata":{"useLiteralContent":false,"bytecodeHash":"ipfs","appendCBOR":true},"outputSelection":{"*":{"*":["abi","evm.bytecode.object","evm.bytecode.sourceMap","evm.bytecode.linkReferences","evm.deployedBytecode.object","evm.deployedBytecode.sourceMap","evm.deployedBytecode.linkReferences","evm.deployedBytecode.immutableReferences","evm.methodIdentifiers","metadata"]}},"evmVersion":"prague","viaIR":false,"libraries":{}},"vyper":{"evmVersion":"prague","outputSelection":{"*":{"*":["abi","evm.bytecode","evm.deployedBytecode"]}}}}},"preprocessed":false,"mocks":[]} \ No newline at end of file diff --git a/docker-compose.local.yml b/docker-compose.local.yml index 2eb7c86..62d27c6 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -2,10 +2,10 @@ # No external dependencies (postgres, bridge-service, l1-anvil) services: - # Miden node (agglayer-v0.1 - built from source) + # Miden node (v0.14.6 - built from source) # Image tag uses miden-node repo commit SHA, not proxy repo miden-node: - image: miden-infra/miden-node:${MIDEN_NODE_COMMIT:-agglayer-v0.1} + image: miden-infra/miden-node:${MIDEN_NODE_COMMIT:-v0.14.6} build: context: . dockerfile: Dockerfile.miden-node diff --git a/docker-compose.yml b/docker-compose.yml index 5df631a..9b02382 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,7 +16,7 @@ services: timeout: 5s retries: 5 - # Miden node (agglayer-v0.1 - built from source) + # Miden node (v0.14.6 - built from source) miden-node: image: miden-infra/miden-node build: diff --git a/docs/BRIDGE-FLOW.md b/docs/BRIDGE-FLOW.md index c176414..7a2b897 100644 --- a/docs/BRIDGE-FLOW.md +++ b/docs/BRIDGE-FLOW.md @@ -798,7 +798,7 @@ kurtosis-cdk (upstream) | Service | Image | Port | Purpose | |---------|-------|------|---------| -| miden-node | `miden-infra/miden-node:agglayer-v0.1` | 57291 | Miden network node | +| miden-node | `miden-infra/miden-node:v0.14.6` | 57291 | Miden network node | | miden-proxy | `miden-infra/miden-proxy:latest` | 8546 | This proxy | | miden-l2-forwarder | `nginx:alpine` | 8545 | TCP forwarding (bridge expects 8545) | | miden-bridge-ui | `miden-infra/bridge-ui:latest` | 80 | Deposit web UI | diff --git a/docs/TESTING.md b/docs/TESTING.md index 9f873d9..5ca554b 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -54,7 +54,7 @@ Shows transaction results and logs. ### Dockerfile.miden-node -Builds miden-node from source at the `agglayer-v0.1` tag. First build takes several minutes. +Builds miden-node from source at the `v0.14.6` tag. First build takes several minutes. Features: - Clones from https://github.com/0xMiden/miden-node diff --git a/docs/eip-8141-vs-miden-txs.md b/docs/eip-8141-vs-miden-txs.md new file mode 100644 index 0000000..f471a5d --- /dev/null +++ b/docs/eip-8141-vs-miden-txs.md @@ -0,0 +1,206 @@ +# EIP-8141 (Frame Transaction) vs Miden Transaction Model + +## 1. Purpose and Scope + +**EIP-8141 ("Frame Transaction")** +- A new Ethereum transaction type (`0x06`) under EIP-2718. +- Goal: decouple transaction validation, execution, and gas payment from ECDSA. Enables native account abstraction and post-quantum signature schemes. +- Authors: Vitalik Buterin, lightclient, Felix Lange, Yoav Weiss, Alex Forshtat, Dror Tirosh, Shahaf Nacson, Derek Chiang. Status: Draft (created 2026-01-29). +- Scope: L1 Ethereum protocol change. Modifies how transactions are structured, validated, and paid for. Does not change the EVM execution model itself -- it wraps EVM calls into a new "frame" abstraction within a single transaction envelope. + +**Miden Transaction Model** +- The native transaction model of the Miden rollup (ZK rollup built on Miden VM). +- Goal: enable parallel, private transaction execution with client-side proving. A transaction is the state transition of a single account, consuming/producing notes (UTXO-like objects). +- Scope: entire rollup protocol. Defines execution semantics, proving, account model, note model, and asset model from the ground up. + +## 2. Transaction Structure + +**EIP-8141** +``` +[chain_id, nonce, sender, frames, max_priority_fee_per_gas, max_fee_per_gas, max_fee_per_blob_gas, blob_versioned_hashes] +frames = [[mode, target, gas_limit, data], ...] +``` +- A transaction is a list of **frames**, each with a mode (DEFAULT, VERIFY, SENDER), target address, gas limit, and data. +- Frames execute sequentially. Each frame is essentially a top-level EVM call with a specific caller context. +- VERIFY frames handle authentication (signature verification, approval). SENDER frames execute on behalf of the sender. DEFAULT frames execute as `ENTRY_POINT`. +- The `APPROVE` opcode is the key new primitive -- it sets transaction-scoped `sender_approved` and `payer_approved` flags. +- Gas is allocated per-frame; unused gas does not carry over between frames. +- Supports atomic batching of consecutive SENDER frames. + +**Miden** + +A transaction is a Miden VM program with inputs: +- A single **account** (the entity whose state transitions) +- Zero or more **input notes** (consumed) +- Blockchain state (reference block) +- Optional **transaction script** (executor-defined code) +- Optional transaction arguments and foreign account data + +Execution flow: +1. **Prologue**: validate on-chain commitments +2. **Note processing**: execute each note's script against the account sequentially +3. **Transaction script processing**: execute executor-defined code (e.g., sign the transaction, mint tokens) +4. **Epilogue**: compute final state, verify nonce increment, check asset conservation, generate ZK proof + +Outputs: updated account state + zero or more **output notes**. + +## 3. Key Structural Differences + +| Dimension | EIP-8141 | Miden | +|-----------|----------|-------| +| **Granularity** | Single tx touches multiple accounts via frames | Single tx transitions exactly one account | +| **Multi-party interaction** | One tx can call N contracts in N frames | Requires two transactions: sender creates note, receiver consumes note | +| **Ordering** | Frames within a tx are ordered; txs within a block are ordered globally | Transactions on different accounts can execute in parallel; no global ordering needed | +| **Batching** | Atomic batch flag on consecutive SENDER frames | Up to 1000 notes consumed + 1000 produced per transaction; batching at the note level | +| **Validation location** | On-chain (EVM execution) | Client-side (local Miden VM execution + proof generation); network only verifies proof | +| **Privacy** | Fully transparent (standard Ethereum) | Private accounts/notes possible; only commitments stored on-chain | +| **Proof** | None (standard EVM execution) | ZK-STARK proof generated per transaction | + +## 4. Account Model Comparison + +**EIP-8141** +- Retains Ethereum's account model: EOAs and contract accounts at 20-byte addresses. +- The key innovation is that accounts no longer need ECDSA. Any account with code can define its own validation logic via VERIFY frames. EOAs get "default code" that supports both secp256k1 and P256 signatures. +- `sender` is an explicit field in the transaction (no longer derived from signature recovery). +- Nonce is still a single uint64 per account, incremented during APPROVE. +- Account state: balance, nonce, code, storage (unchanged from current Ethereum). + +**Miden** +- Accounts are smart contracts with: ID, code (interface), storage (up to 256 slots of Merkle-tree-backed data), vault (asset collection), nonce. +- Account types: basic accounts, faucets (can mint assets). Can be public (state on-chain) or private (only commitment on-chain). +- Account code is composed of **components** (e.g., basic wallet, authentication) -- this is structurally analogous to EIP-8141's frame decomposition, where auth, execution, and system logic are separate composable units (see Section 10). The interface explicitly exposes which methods notes and transaction scripts can call. +- Authentication is fully programmable: the account's code defines `auth_tx` which is called during the epilogue. Could be single-sig, multi-sig, any scheme. +- No notion of EOA. Every account is a smart contract. + +## 5. Asset/Token Handling + +**EIP-8141** +- No changes to Ethereum's asset model. ETH is native; ERC-20/721/1155 are contract-based. +- Frame transactions have no `value` field. To send ETH, you use a SENDER frame that calls the target with a value via the account's code (or default code for EOAs encodes `[[target, value, data]]` in RLP). +- Gas payment abstraction: the payer can be different from the sender (paymaster pattern is first-class). + +**Miden** +- **Native asset model**: every asset is encoded in 32 bytes containing the faucet ID and asset details. No ERC-20 contracts needed for standard tokens. +- Assets are stored directly in account vaults (sparse Merkle trees) and note vaults (lists up to 256 assets). +- Only faucet accounts can mint assets. Fungible assets have max supply 2^63 - 1. +- Non-fungible assets are represented by hashing asset data. +- Asset conservation is enforced at the protocol level: sum of input assets must equal sum of output assets (unless the account is a faucet). +- Asset callbacks: faucets can register callbacks invoked when their assets are added to vaults/notes (enables blocklists, pause functionality). +- Alternative asset models (e.g., ERC-20-style global state) are possible but not the default. + +## 6. Execution Model + +**EIP-8141** +- EVM execution. Each frame is a top-level EVM call. +- New opcodes: `APPROVE` (0xaa), `TXPARAM` (0xb0), `FRAMEDATALOAD` (0xb1), `FRAMEDATACOPY` (0xb2). +- Warm/cold state journal is shared across frames. Transient storage (`TSTORE`/`TLOAD`) is reset between frames. +- `ORIGIN` returns the frame's caller (not the EOA signer, since there may not be one). +- Max 1000 frames per transaction. +- Gas model: EIP-1559 + EIP-4844 blob fees. Per-frame gas limits. Intrinsic cost of 15,000 gas. + +**Miden** +- Miden VM execution (stack-based VM with field arithmetic over a 64-bit prime field). +- Programs compile to Miden Assembly (MASM). Execution produces a STARK proof. +- Transaction kernel: a reference implementation of the protocol that mediates all account state access. Note scripts and transaction scripts cannot directly modify account state -- they must call methods exposed by the account's interface, which in turn call kernel procedures. +- Max 2^30 VM cycles per transaction. +- No gas in the Ethereum sense for local transactions. Fees are computed and deducted in the epilogue in the chain's native asset. Local proving has no gas limit -- complexity is bounded only by the cycle limit. +- Foreign procedure invocation: note/transaction scripts can read state from other accounts (oracles, price feeds) during execution. + +## 7. Notes vs UTXOs vs Account State + +This is the deepest conceptual divergence. + +**EIP-8141**: purely account-based. There are no UTXOs or note-like objects. State lives in account storage and balances. A frame transaction is a sequence of calls that mutate account states atomically within one transaction. + +**Miden**: hybrid UTXO + account model. +- **Notes** are the UTXO-like primitive. A note has assets, a script (spending conditions), storage (parameters), a serial number (for uniqueness/privacy), and metadata. +- Notes are created in one transaction and consumed in another. This two-phase transfer enables parallel execution: two accounts that don't share notes can transact simultaneously. +- **Nullifiers** (hash of note components) track consumption without revealing which note was spent -- directly borrowed from Zcash. +- Note types: P2ID (pay-to-ID), P2IDE (with time-lock/reclaim), SWAP (atomic exchange). Custom scripts can define arbitrary consumption logic. +- Notes can be private (only hash on-chain) or public (full data on-chain). +- "Erasable notes" can be consumed before being recorded on-chain, enabling sub-second settlement for specific use cases (e.g., order books). + +The Miden note is conceptually closest to a Bitcoin UTXO with programmable spending conditions, but living inside an account-based system. EIP-8141 has no analog -- it stays firmly in Ethereum's account-centric world. + +## 8. Validation and Authentication + +**EIP-8141** +- VERIFY frames define validation. They run as STATICCALL (no state modification). +- The `APPROVE` opcode signals successful validation to the protocol. +- Signature hash is computed canonically with VERIFY frame data elided (since it contains the signature itself). +- Default code supports secp256k1 and P256 for EOA backward compatibility. +- Mempool rules constrain what validation can do: no banned opcodes, no reading external mutable state, max 100K gas for the validation prefix. + +**Miden** +- Authentication is part of the account's code, called during the transaction epilogue. +- The standard `auth::singlesig::auth_tx` component verifies a signature against a stored public key and a transaction commitment. But any logic is possible. +- No mempool rules analogous to EIP-8141's -- validation happens locally on the client. The network only sees the proof. +- Note scripts define consumption authorization (who can spend a note), which is separate from account authentication. + +## 9. Gas/Fee Abstraction + +**EIP-8141** +- First-class paymaster support. A VERIFY frame can `APPROVE(0x2)` to pay gas from any account. +- Canonical paymaster contract pattern for mempool safety (timelocked withdrawals, reservation accounting). +- Non-canonical paymasters allowed with strict limits (max 1 pending tx per non-canonical paymaster). +- EOAs can act as paymasters via default code. + +**Miden** +- Fees are deducted from the account's vault in the native asset during the epilogue. +- No paymaster pattern needed at the protocol level -- since users prove locally, there is no "who pays for execution" problem in the same way. The account itself always pays. +- Network transactions (proven by the operator) have fee parameters defined by the reference block. +- Local transactions are "free" in terms of on-chain gas -- the user bears the computational cost of proving. + +## 10. The Deepest Parallel: Components as Frames + +The most striking structural similarity between EIP-8141 and Miden is one that's easy to miss: **Miden's component-based account code is the account-side analog of EIP-8141's frame decomposition.** + +In EIP-8141, a transaction is broken into typed frames: +- **VERIFY** frame: authentication logic (is this tx authorized?) +- **SENDER** frame: user-intended execution (transfer, swap, etc.) +- **DEFAULT** frame: system/third-party logic (callbacks, hooks) + +In Miden, an account's code is broken into typed **components**: +- **Authentication component** (e.g., `auth::singlesig::auth_tx`): validates the transaction during the epilogue — directly analogous to a VERIFY frame +- **Wallet component** (e.g., `basic_wallet`): exposes `send_asset`/`receive_asset` — the execution logic that note scripts and transaction scripts call, analogous to SENDER frames +- **Custom components**: any additional interface methods the account exposes — analogous to DEFAULT frames that handle external calls + +Both designs arrive at the same decomposition principle: **separate authentication from execution from system logic**, and make each piece independently composable. + +The key difference is *where* this decomposition lives: + +| | EIP-8141 | Miden | +|--|----------|-------| +| **Decomposed at** | Transaction level (frames in the tx envelope) | Account level (components in the account code) | +| **Composed by** | Transaction sender (chooses which frames to include) | Account deployer (chooses which components to install) | +| **Swappable** | Per-transaction (different frames each time) | Per-account (components are fixed after deployment, but accounts can be upgraded) | +| **Auth logic** | VERIFY frame runs as STATICCALL on the account | Auth component runs inside the ZK proof during epilogue | +| **Execution logic** | SENDER frame calls account code | Note/tx scripts call wallet component methods | + +This means EIP-8141 gives *callers* flexibility (compose frames per-tx), while Miden gives *accounts* flexibility (compose components per-account). Miden's approach is more restrictive per-transaction (the account's interface is fixed) but more powerful at the account level (components can define entirely new execution semantics, not just different call contexts). + +The convergence is not accidental. Both are solving the same fundamental problem: Ethereum's original design fused "who is this?" (authentication), "what do they want?" (execution), and "who pays?" (gas) into a single indivisible transaction. EIP-8141 untangles these at the protocol level within the existing EVM. Miden untangles them from the ground up in the account model itself, leveraging the fact that a clean-sheet ZK rollup doesn't need backward compatibility with EOAs. + +## 11. Conceptual Overlaps + +| EIP-8141 Concept | Closest Miden Analog | Notes | +|-------------------|---------------------|-------| +| Frame (VERIFY) | Account authentication procedure | Both separate validation from execution | +| Frame (SENDER) | Transaction script | Both execute user-intended operations | +| Frame (DEFAULT) | Note script execution / foreign calls | Both handle third-party or system logic | +| Atomic batch | Single transaction (inherently atomic) | Miden txs are atomic by default since they transition one account | +| Paymaster | N/A (client-side proving) | Miden has no equivalent; prover delegation is the closest | +| `APPROVE` opcode | Nonce increment in epilogue | Both gate transaction finality | +| Transaction signature hash | Transaction commitment (proven in ZK) | Miden's is a cryptographic commitment verified inside the proof | +| Account code | Account code (components) | Both are programmable smart accounts | +| N/A | Notes (UTXO-like) | EIP-8141 has no UTXO concept | +| N/A | Nullifiers | EIP-8141 has no private consumption tracking | +| N/A | ZK proof | EIP-8141 is pre-proof, standard EVM execution | + +## 12. Bottom Line + +EIP-8141 is an evolutionary improvement to Ethereum's transaction format, decomposing the monolithic tx into a sequence of typed frames to unlock account abstraction, post-quantum signatures, and gas sponsorship -- all while staying within the existing EVM and account model. + +Miden is a ground-up redesign built around ZK proofs, a hybrid UTXO/account model, and client-side execution. Its transaction model is fundamentally different: one-account-per-transaction, note-based inter-account communication, private state, and local proving. + +The two designs solve some of the same problems (signature agility, programmable validation, fee flexibility) but from radically different starting points. EIP-8141's frame abstraction is the closest Ethereum L1 has come to Miden's separation of concerns between validation and execution, but it does so without introducing UTXOs, privacy, or off-chain proving. diff --git a/docs/ger-injection-audit.md b/docs/ger-injection-audit.md new file mode 100644 index 0000000..e3b14b7 --- /dev/null +++ b/docs/ger-injection-audit.md @@ -0,0 +1,276 @@ +# GER Injection Flow -- Security Audit Document + +**System**: miden-agglayer proxy +**Repository**: https://github.com/gateway-fm/miden-agglayer (branch: `fix/remove-derive-account-id`) +**Date**: 2026-03-30 +**Audience**: Security auditors + +--- + +## 1. System Context + +The miden-agglayer proxy sits between the **zkevm-bridge-service** (an Ethereum-oriented bridge UI/backend) and the **Miden L2 network**. Its purpose is to present an EVM-compatible JSON-RPC interface so that existing Polygon CDK tooling (aggoracle, bridge-service, claim-sponsor) can interact with Miden without modification. + +The proxy translates a small subset of EVM JSON-RPC calls (`eth_sendRawTransaction`, `eth_getLogs`, `eth_getBlockByNumber`, etc.) into Miden-native operations (note creation, transaction submission, state queries). + +The Global Exit Root (GER) injection flow is one of the two primary write paths through the proxy (the other being claim processing). + +--- + +## 2. GER Injection Flow + +### 2.1 End-to-End Sequence + +``` +L1 GER Contract + | + v +aggkit aggoracle (polls L1, detects new GER) + | + | eth_sendRawTransaction(insertGlobalExitRoot(bytes32)) + v +miden-agglayer proxy + | + |--- 1. Decode signed TX envelope (EIP-1559 or Legacy) + |--- 2. Extract calldata: insertGlobalExitRoot(bytes32 ger) + |--- 3. Dedup check (has_seen_ger) + |--- 4. Sync miden-client state (sync_state) + |--- 5. Build UpdateGerNote (B2AggNote from miden-base-agglayer) + |--- 6. Submit note as TX from service account + |--- 7. Poll for commitment (up to 30s, retry up to 3x) + |--- 8. Write synthetic InsertGlobalExitRoot event to store + |--- 9. Advance latest_block_number + v +Miden Node (validates UpdateGerNote against bridge account GER set) +``` + +### 2.2 Detailed Step Breakdown + +**Step 1-2: Transaction Routing** (`service_send_raw_txn.rs`) + +The proxy receives a signed Ethereum transaction via `eth_sendRawTransaction`. It decodes the RLP-encoded transaction envelope (supporting both EIP-1559 and Legacy formats), extracts the calldata, and routes based on the function selector. The `insertGlobalExitRoot(bytes32)` selector triggers the `handle_ger_result` code path. + +**Step 3: Deduplication** (`ger.rs` -- `insert_ger()`) + +Before submitting to Miden, the proxy checks the PostgreSQL store for whether this GER hash has already been processed (`has_seen_ger`). If the GER is already present, the call returns early with success. + +**Step 4-7: Miden Submission** (`ger.rs` -- `submit_ger_to_miden()`) + +1. **State sync**: Calls `sync_state()` on the miden-client to ensure the local state matches the node's latest committed state. This is critical because the transaction commitment hash depends on the current account state. +2. **Note creation**: Constructs an `UpdateGerNote` using `miden-base-agglayer`'s `B2AggNote` types. The GER bytes are wrapped in an `ExitRoot` type. +3. **Transaction submission**: Submits the note as a transaction from the proxy's service account. +4. **Commitment polling**: Polls the miden-node for transaction commitment, waiting up to 30 seconds. +5. **Retry logic**: If the commitment fails due to stale state (commitment mismatch), the proxy retries up to 3 times, re-syncing state before each attempt. + +**Step 8-9: Synthetic Event and Block** (`ger.rs`, `block_state.rs`, `store/postgres.rs`) + +After the Miden transaction is committed: +1. A synthetic `InsertGlobalExitRoot` event log is written to PostgreSQL, encoded with the standard ABI event signature. +2. The `latest_block_number` is advanced so that the bridge-service discovers the new block on its next poll. + +--- + +## 3. Synthetic Block and Event System + +### 3.1 Purpose + +The bridge-service expects to interact with an EVM chain. It polls for new blocks via `eth_getBlockByNumber` and scans for events via `eth_getLogs`. The proxy fabricates deterministic EVM blocks and events to satisfy these expectations. + +### 3.2 Block Construction (`block_state.rs`) + +- Each significant event (GER injection, claim completion) triggers a new synthetic block. +- Block numbers are sequential integers, advanced only after the underlying Miden operation commits. +- Block hashes are computed as `keccak256(rlp(header))` where the header fields are deterministic (no miner randomness, no uncle hashes, etc.). +- This deterministic construction satisfies the bridge-service's `checkReorg` detection, which compares stored block hashes against queried block hashes. + +### 3.3 Event Encoding (`log_synthesis.rs`) + +- The `InsertGlobalExitRoot` event uses the standard Solidity event topic. +- Event data matches the ABI encoding that the bridge-service expects to decode. +- Events are stored in PostgreSQL and returned via `eth_getLogs` filtered by topic and block range. + +--- + +## 4. Trust Assumptions + +| # | Assumption | Implication | +|---|-----------|-------------| +| 1 | The aggoracle is honest and correctly relays L1 GERs. | The proxy validates the TX envelope (signature, RLP structure) but does **not** independently verify the GER value against L1. A compromised aggoracle could inject arbitrary GER values. | +| 2 | The proxy's service account private key is securely managed. | This account is the sole entity authorized to create `UpdateGerNote` transactions. Compromise of this key allows arbitrary GER injection into Miden. | +| 3 | The miden-node validates `UpdateGerNote` against the bridge account's registered GER set. | This provides a second layer of validation at the protocol level, but only if the bridge account's GER set is itself correctly maintained. | +| 4 | Synthetic events in PostgreSQL are trusted by the bridge-service. | The bridge-service has no way to independently verify that a synthetic event corresponds to an actual Miden transaction. It trusts the proxy's PostgreSQL store entirely. | +| 5 | The PostgreSQL instance is not accessible to unauthorized parties. | Direct modification of the store could inject phantom events or suppress real ones. | + +--- + +## 5. Security Considerations + +### 5.1 What the Proxy Controls + +- **Synthetic block numbers and hashes**: Purely deterministic from the event sequence. No manipulation is possible without also modifying the store. +- **`latest_block_number` advancement**: Controls what the bridge-service can observe. Delaying advancement delays bridge-service awareness; premature advancement (before event write) can cause the bridge to miss events. +- **GER-to-Miden mapping**: The proxy is the sole translator between L1 GER events and Miden `UpdateGerNote` transactions. + +### 5.2 Attack Vectors + +#### 5.2.1 Stale GER Injection + +**Vector**: If the proxy's miden-client state is stale (e.g., another transaction committed between sync and submit), the `UpdateGerNote` transaction will be rejected by the node due to commitment mismatch. + +**Mitigation**: `sync_state()` is called before each submission attempt. On commitment mismatch, the proxy retries up to 3 times with a fresh sync before each retry. + +**Residual risk**: If the node is under heavy load and state changes rapidly, all 3 retries may fail. The GER injection is then lost until the aggoracle retries (which introduces its own issues; see Known Issues). + +#### 5.2.2 Block Number Race Condition + +**Vector**: The synthetic block number was previously assigned **before** the Miden transaction committed. This caused the bridge-service to poll the new block before the event was written to the store, resulting in the bridge seeing an empty block and never re-scanning it. + +**Mitigation**: Fixed by assigning the block number **after** the Miden transaction commits and the synthetic event is written to the store. The ordering is now: Miden commit -> event write -> block number advance. + +**Residual risk**: A crash between event write and block number advance would leave the event orphaned (written but not discoverable). The `restore.rs` module can reconstruct state from consumed notes to recover. + +#### 5.2.3 Duplicate GER + +**Vector**: The aggoracle may retry a GER injection if it does not receive a timely response. + +**Mitigation**: Two layers of deduplication: +1. The proxy checks `has_seen_ger` in the PostgreSQL store before submission. +2. The miden-node validates against the bridge account's GER set and rejects duplicates. + +**Residual risk**: Minimal. Both layers must fail for a duplicate to be accepted. + +#### 5.2.4 Missing GER (Crash Recovery) + +**Vector**: If the proxy crashes after the Miden transaction commits but before the synthetic log is written to PostgreSQL, the GER exists on Miden but is invisible to the bridge-service. + +**Mitigation**: The `restore.rs` module can reconstruct proxy state by scanning consumed notes on the Miden network and replaying them as synthetic events. + +**Residual risk**: Recovery requires manual intervention (or a restart that triggers restore logic). During the gap, the bridge-service is unaware of the GER. + +#### 5.2.5 Concurrent Claims and GER Injections + +**Vector**: Claims and GER injections both use the proxy's service account. If both operations are in flight simultaneously, their state snapshots can conflict, causing commitment mismatches on one or both operations. + +**Mitigation**: `sync_state()` before each operation plus retry logic. The miden-client access is serialized (see `miden_client.rs`). + +**Residual risk**: Under high concurrency, retry storms can degrade throughput. No data corruption risk -- only liveness impact. + +#### 5.2.6 Unauthorized GER Injection + +**Vector**: An attacker sends a crafted `eth_sendRawTransaction` with the `insertGlobalExitRoot` selector. + +**Mitigation**: The proxy validates the TX envelope (RLP decode, signature extraction). However, the proxy does not enforce a sender allowlist for GER injections -- it processes any well-formed transaction with the correct selector. The miden-node provides the authorization check via the service account's permissions. + +**Residual risk**: If sender validation is desired at the proxy layer, it must be explicitly implemented. + +### 5.3 Data Flow Integrity + +``` +L1 GER (bytes32) --> aggoracle calldata --> proxy decodes --> ExitRoot type --> UpdateGerNote --> Miden +``` + +- The 32-byte GER value is passed through without modification at every stage. +- The proxy adds metadata (block number, timestamp, hash chain value) but does not alter the GER itself. +- The `UpdateGerNote` wraps the GER in an `ExitRoot` type from `miden-base-agglayer`, which is the type the Miden bridge account expects. + +--- + +## 6. Key Management and Account Authorization + +### 6.1 Account Hierarchy + +The proxy manages several Miden accounts, each with a specific role: + +| Account | Role | Created by | Has private key | +|---------|------|-----------|-----------------| +| **Service** | Signs GER injection and claim TXs | Proxy init (`init.rs:55`) | Yes (in proxy's keystore) | +| **Bridge** | Receives and consumes CLAIM/B2AGG notes | Proxy init (`init.rs:62`) | Yes | +| **ETH Faucet** | Mints bridged ETH tokens | Proxy init (`faucet_ops.rs:50`) | Yes | +| **AGG Faucet** | Mints bridged AGG tokens | Proxy init | Yes | +| **wallet_hardhat** | Receives P2ID notes from faucets | Proxy init | Yes | +| **Dynamic faucets** | Mints bridged ERC-20 tokens | On-demand (`claim.rs:111`) | Yes | + +All private keys are stored in the proxy's miden-client SQLite store at `/var/lib/miden-agglayer-service/store.sqlite3` and the `keystore/` directory. + +### 6.2 GER Injection Authorization + +The miden-node enforces authorization for GER injection through the following chain: + +1. **Note creation**: The proxy's **service account** creates an `UpdateGerNote` (source: `ger.rs:107`). The note targets the **bridge account**. + +2. **Note script validation**: The `UpdateGerNote` script (from `miden-base-agglayer` crate) verifies that the note was created by an authorized account. The bridge account's storage contains a list of authorized GER updaters. + +3. **Bridge account registration**: During proxy init, the service account is registered as an authorized GER updater on the bridge account (source: `init.rs`, faucet registration flow). + +4. **Miden node validation**: The node's block producer validates the transaction against the accounts' current state. If the service account is not in the bridge's authorized set, the TX is rejected. + +### 6.3 How the Node Allowlists the GER Injector + +The bridge account on Miden has an internal storage map that tracks: +- Registered faucets (which tokens can be minted) +- Authorized GER updaters (which accounts can inject GERs) +- The current GER set (which GERs have been injected) + +When the proxy registers faucets during init (`faucet_ops.rs:89-117`), it also registers the service account as authorized. This registration is itself a Miden transaction that modifies the bridge account's storage. + +**Key insight**: If the service account's private key is compromised, an attacker can inject arbitrary GERs into the bridge account. The miden-node will accept them because the service account is in the authorized set. There is no secondary validation of the GER value against L1. + +### 6.4 External Key: aggoracle Signing Key + +The aggoracle (part of aggkit) signs `eth_sendRawTransaction` calls with its own Ethereum private key (`aggoracle.keystore`). The proxy validates the EIP-1559/Legacy TX envelope but does **not** check the signer address against an allowlist. Any valid Ethereum signature is accepted. + +The proxy routes the transaction based on the **calldata selector** (`insertGlobalExitRoot`, `updateExitRoot`, `claimAsset`), not the sender. This means any account can send GER injection transactions to the proxy. + +**Recommendation**: Consider adding an optional sender allowlist in the proxy for GER injection transactions, configurable via environment variable or config file. + +--- + +## 7. Key Source Files + +| File | Responsibility | +|------|---------------| +| `src/ger.rs` | GER injection logic: `insert_ger()`, `submit_ger_to_miden()`, dedup, retry | +| `src/service_send_raw_txn.rs` | Transaction routing, calldata decoding, `handle_ger_result()` | +| `src/block_state.rs` | Synthetic block number and hash management | +| `src/store/postgres.rs` | Persistent storage for synthetic events and GER dedup state | +| `src/log_synthesis.rs` | Event encoding (`InsertGlobalExitRoot` topic and ABI data) | +| `src/miden_client.rs` | Miden node client wrapper with serialized access to `sync_state()` and TX submission | +| `src/restore.rs` | Crash recovery: reconstructs proxy state from consumed Miden notes | + +--- + +## 7. Known Issues + +### 7.1 aggoracle ethtxmanager Stuck State + +**Symptom**: After a GER injection fails (e.g., commitment mismatch returned as an RPC error), the aggoracle's ethtxmanager marks the transaction as evicted but does not clear it from its in-memory queue. All subsequent GER injections are blocked. + +**Impact**: New L1 GERs are not propagated to Miden until the aggkit is restarted. + +**Workaround**: Restart the aggkit process to clear in-memory ethtxmanager state. + +**Root cause**: The ethtxmanager was designed for Ethereum, where "already exists" means the TX is in the mempool. In the proxy context, an error means the TX was rejected and should be retried, but the etxtxmanager has no "rejected, please retry" state. + +### 7.2 bridge-service checkReorg Event Discovery Gap + +**Symptom**: The bridge-service's `checkReorg` mode only compares block hashes via `eth_getBlockByNumber` -- it does not call `eth_getLogs`. If the proxy writes a synthetic event to a block that the bridge has already passed during a `checkReorg` cycle, the event is never discovered. + +**Impact**: The bridge-service misses the GER event. Deposits gated on that GER cannot be claimed. + +**Workaround**: Restart the bridge-service to force a full resync from the last finalized block. + +**Root cause**: The bridge-service assumes that `checkReorg` only needs to verify block continuity, not re-scan for events. This assumption holds for real EVM chains but not for the proxy's synthetic block system where events can be written to blocks after the block number is published. + +--- + +## 8. Recommendations for Auditors + +1. **Verify GER passthrough integrity**: Confirm that the 32-byte GER value in the aggoracle's calldata is identical to the value in the `UpdateGerNote` submitted to Miden. Key code path: `service_send_raw_txn.rs` decode -> `ger.rs` -> `B2AggNote` construction. + +2. **Review service account authorization**: The service account is the trust anchor for all proxy-to-Miden operations. Verify how the account ID and credentials are provisioned, stored, and rotated. + +3. **Assess crash recovery completeness**: The `restore.rs` module is the primary recovery mechanism. Verify that it correctly handles all edge cases (partial writes, duplicate notes, reorged Miden blocks). + +4. **Evaluate sender validation**: The proxy does not enforce a sender allowlist for GER injection transactions. Assess whether this is acceptable given the deployment context or whether an allowlist should be added. + +5. **Test concurrent operations**: Submit GER injections and claims simultaneously to verify that the serialized miden-client access and retry logic correctly handle contention without data loss. diff --git a/kurtosis/miden-cdk/bridge_service.star b/kurtosis/miden-cdk/bridge_service.star index 3cde14e..cc93e47 100644 --- a/kurtosis/miden-cdk/bridge_service.star +++ b/kurtosis/miden-cdk/bridge_service.star @@ -174,13 +174,22 @@ def _deploy_aggkit(plan, deployment_suffix, cdk_args, contract_addresses, l1_rpc """ service_name = "aggkit" + deployment_suffix - # Get aggoracle keystore from contracts service + # Get aggoracle keystore from contracts service (for GER injection) aggoracle_keystore = plan.store_service_files( name="aggoracle-keystore" + deployment_suffix, service_name="contracts" + deployment_suffix, src="/opt/keystores/aggoracle.keystore", ) + # Get sequencer keystore from contracts service (for certificate signing) + # The sequencer address is registered as the proposer in the L1 validator committee. + # The aggsender must sign certificates with this key for the AggLayer to accept them. + sequencer_keystore = plan.store_service_files( + name="sequencer-keystore" + deployment_suffix, + service_name="contracts" + deployment_suffix, + src="/opt/keystores/sequencer.keystore", + ) + # Generate aggkit config config_template = _get_aggkit_config_template() config_artifact = plan.render_templates( @@ -212,7 +221,7 @@ def _deploy_aggkit(plan, deployment_suffix, cdk_args, contract_addresses, l1_rpc return plan.add_service( name=service_name, config=ServiceConfig( - image=cdk_args.get("aggkit_image", "ghcr.io/0xpolygon/aggkit:latest"), + image=cdk_args.get("aggkit_image", "ghcr.io/agglayer/aggkit:0.9.0-rc3"), ports={ "rpc": PortSpec( number=5576, @@ -221,11 +230,11 @@ def _deploy_aggkit(plan, deployment_suffix, cdk_args, contract_addresses, l1_rpc }, files={ "/etc/aggkit": Directory( - artifact_names=[config_artifact, aggoracle_keystore], + artifact_names=[config_artifact, aggoracle_keystore, sequencer_keystore], ), }, - # Only run aggoracle for now (aggsender needs more config) - cmd=["run", "--cfg=/etc/aggkit/config.toml", "--components=aggoracle"], + # Run aggoracle (GER injection) and aggsender (L2→L1 certificate submission) + cmd=["run", "--cfg=/etc/aggkit/config.toml", "--components=aggoracle,aggsender"], # Docker Desktop grouping label labels={ DOCKER_PROJECT_LABEL: BRIDGE_PROJECT_GROUP, @@ -321,7 +330,7 @@ L1URL = "{{.l1_rpc_url}}" L2URL = "{{.l2_rpc_url}}" AggLayerURL = "{{.agglayer_url}}" AggchainProofURL = "" -SequencerPrivateKeyPath = "/etc/aggkit/aggoracle.keystore" +SequencerPrivateKeyPath = "/etc/aggkit/sequencer.keystore" SequencerPrivateKeyPassword = "{{.l2_keystore_password}}" # Block numbers (top-level, resolved by aggkit config renderer) @@ -372,7 +381,54 @@ Password = "{{.l2_keystore_password}}" URL = "{{.l2_rpc_url}}" L1ChainID = {{.l2_chain_id}} -# AggSender +# AggSender — submits certificates to AggLayer for L2→L1 bridging [AggSender] -AggSenderPrivateKey = {Path = "/etc/aggkit/aggoracle.keystore", Password = "{{.l2_keystore_password}}"} +AggSenderPrivateKey = {Path = "/etc/aggkit/sequencer.keystore", Password = "{{.l2_keystore_password}}"} +Mode = "PessimisticProof" +CheckStatusCertificateInterval = "1s" +TriggerCertMode = "ASAP" + +[AggSender.StorageRetainCertificatesPolicy] +RetryCertAfterInError = true + +[AggSender.AggkitProverClient] +UseTLS = false + +[AggSender.AgglayerClient] + +[[AggSender.AgglayerClient.APIRateLimits]] +MethodName = "SendCertificate" + +[AggSender.AgglayerClient.APIRateLimits.RateLimit] +NumRequests = 0 + +[AggSender.AgglayerClient.GRPC] +URL = "{{.agglayer_url}}" +MinConnectTimeout = "5s" +RequestTimeout = "300s" +UseTLS = false + +[AggSender.AgglayerClient.GRPC.Retry] +InitialBackoff = "1s" +MaxBackoff = "10s" +BackoffMultiplier = 2.0 +MaxAttempts = 20 + +# BridgeL2Sync — syncs BridgeEvent logs from L2 proxy for AggSender deposit tree +[BridgeL2Sync] +BridgeAddr = "{{.l2_bridge_address}}" +BlockFinality = "LatestBlock" +# Disable debug_traceTransaction calls — Miden proxy synthetic txns don't have call traces +SyncFromInBridges = "false" + +[ReorgDetectorL2] +FinalizedBlock = "LatestBlock" + +# L1InfoTreeSync — syncs L1 info tree for certificate building +[L1InfoTreeSync] +InitialBlock = "{{.rollup_manager_block_number}}" + +# L2GERSync — syncs L2 GER for AggSender +[L2GERSync] +BlockFinality = "LatestBlock" """ diff --git a/kurtosis/miden-cdk/main.star b/kurtosis/miden-cdk/main.star index 85415ca..43d62e8 100644 --- a/kurtosis/miden-cdk/main.star +++ b/kurtosis/miden-cdk/main.star @@ -84,17 +84,14 @@ MIDEN_DEFAULTS = { "miden_chain_id": 2, # Miden node configuration - "miden_node_image": "miden-infra/miden-node:agglayer-v0.1", + "miden_node_image": "miden-infra/miden-node:v0.14.6", "miden_node_port": 57291, - # Miden proxy configuration - "miden_proxy_image": "miden-infra/miden-proxy:latest", + # Miden proxy configuration (miden-agglayer) + "miden_proxy_image": "miden-infra/miden-proxy:release-0.1", "miden_proxy_port": 8546, "miden_proxy_external_port": 8123, - # Bridge faucet ID for claim transactions - "bridge_faucet_id": "0x000000000000000000000000000001", - # Aggkit deployment (enabled - required for GER injection) # The aggoracle component injects Global Exit Root updates from L1 to L2 # This is required for deposits to become claimable @@ -130,6 +127,11 @@ def run(plan, args={}): # makes 4_createRollup.ts run during deploy_agglayer_contracts_on_l1 instead. miden_cdk_overrides = { "sequencer_type": "cdk-erigon", + # Point AggLayer's full-node-rpc to our Miden proxy (not non-existent cdk-erigon) + "l2_fullnode_rpc_url": "http://miden-proxy{}:{}".format( + miden_args.get("deployment_suffix", "-001"), + miden_args.get("miden_proxy_port", 8546), + ), } miden_cdk_overrides.update(args.get("args", {})) cdk_args = { diff --git a/kurtosis/miden-cdk/miden_services.star b/kurtosis/miden-cdk/miden_services.star index aa171e5..5227a70 100644 --- a/kurtosis/miden-cdk/miden_services.star +++ b/kurtosis/miden-cdk/miden_services.star @@ -1,7 +1,7 @@ """ Miden Services Module -Deploys the Miden node, RPC proxy, and nginx forwarder for bridge integration. +Deploys the Miden node, RPC proxy (miden-agglayer), and nginx forwarder for bridge integration. These services replace the OP-Geth L2 in the standard kurtosis-cdk deployment. """ @@ -10,15 +10,21 @@ MIDEN_NODE_PORT = 57291 MIDEN_PROXY_PORT = 8546 FORWARDER_PORT = 8545 # Bridge expects L2 on 8545 WEB_UI_PORT = 80 +AGGLAYER_POSTGRES_PORT = 5432 # Default images -DEFAULT_MIDEN_NODE_IMAGE = "miden-infra/miden-node:agglayer-v0.1" -DEFAULT_MIDEN_PROXY_IMAGE = "miden-infra/miden-proxy:latest" +DEFAULT_MIDEN_NODE_IMAGE = "miden-infra/miden-node:v0.14.6" +DEFAULT_MIDEN_PROXY_IMAGE = "miden-infra/miden-proxy:release-0.1" # Docker Desktop grouping label DOCKER_PROJECT_LABEL = "com.docker.compose.project" MIDEN_PROJECT_GROUP = "miden" +# Postgres credentials for miden-agglayer store +AGGLAYER_DB_USER = "agglayer" +AGGLAYER_DB_PASSWORD = "agglayer" +AGGLAYER_DB_NAME = "agglayer_store" + def deploy(plan, miden_args, contract_setup_addresses, cdk_args): """ @@ -39,13 +45,15 @@ def deploy(plan, miden_args, contract_setup_addresses, cdk_args): miden_node_image = miden_args.get("miden_node_image", DEFAULT_MIDEN_NODE_IMAGE) miden_proxy_image = miden_args.get("miden_proxy_image", DEFAULT_MIDEN_PROXY_IMAGE) miden_network_id = miden_args.get("miden_network_id", 2) - bridge_faucet_id = miden_args.get("bridge_faucet_id", "0x000000000000000000000000000001") # Get bridge address from contract deployment bridge_address = contract_setup_addresses.get("l1_bridge_address", "") if not bridge_address: bridge_address = contract_setup_addresses.get("polygon_bridge_address", "") + # L1 RPC URL (internal Kurtosis network) + l1_rpc_url = cdk_args.get("l1_rpc_url", "") + # 1. Deploy Miden node miden_node = _deploy_miden_node(plan, deployment_suffix, miden_node_image) @@ -59,14 +67,33 @@ def deploy(plan, miden_args, contract_setup_addresses, cdk_args): timeout="120s", ) - # 2. Deploy Miden proxy + # 2. Deploy PostgreSQL for miden-agglayer store + _deploy_agglayer_postgres(plan, deployment_suffix) + + plan.wait( + service_name="miden-agglayer-postgres" + deployment_suffix, + recipe=ExecRecipe(command=["pg_isready", "-U", AGGLAYER_DB_USER, "-d", AGGLAYER_DB_NAME]), + field="code", + assertion="==", + target_value=0, + timeout="60s", + ) + + # 3. Run database migration + _apply_agglayer_migration(plan, deployment_suffix) + + # 4. Deploy Miden proxy (miden-agglayer) + database_url = "host=miden-agglayer-postgres{} user={} password={} dbname={}".format( + deployment_suffix, AGGLAYER_DB_USER, AGGLAYER_DB_PASSWORD, AGGLAYER_DB_NAME, + ) miden_proxy = _deploy_miden_proxy( plan, deployment_suffix, miden_proxy_image, miden_network_id, - bridge_address, - bridge_faucet_id, + l1_rpc_url, + contract_setup_addresses, + database_url, ) # Wait for proxy to be ready @@ -84,17 +111,17 @@ def deploy(plan, miden_args, contract_setup_addresses, cdk_args): timeout="120s", ) - # 3. Deploy nginx forwarder + # 5. Deploy nginx forwarder # This routes traffic from port 8545 (where bridge expects L2) to proxy on 8546 forwarder = _deploy_l2_forwarder(plan, deployment_suffix) - # 4. Deploy web UI for deposits + # 6. Deploy web UI for deposits deploy_web_ui = miden_args.get("deploy_web_ui", True) web_ui = None if deploy_web_ui: l1_chain_id = cdk_args.get("l1_chain_id", 271828) - l1_rpc_url = miden_args.get("l1_rpc_url_external", "") - web_ui = _deploy_web_ui(plan, deployment_suffix, bridge_address, l1_chain_id, l1_rpc_url) + l1_rpc_url_ext = miden_args.get("l1_rpc_url_external", "") + web_ui = _deploy_web_ui(plan, deployment_suffix, bridge_address, l1_chain_id, l1_rpc_url_ext) # Build context proxy_url = "http://miden-proxy{}:{}".format(deployment_suffix, MIDEN_PROXY_PORT) @@ -133,12 +160,13 @@ def _deploy_miden_node(plan, deployment_suffix, image): ), }, # Miden node runs in devnet mode for testing + # AGGLAYER_GENESIS=0: proxy creates bridge/faucet accounts (not node genesis) env_vars={ - "RUST_LOG": "info", + "RUST_LOG": "info,miden_node_ntx_builder=debug,miden_tx=debug", + "AGGLAYER_GENESIS": "1", }, - # Node needs substantial memory for proving + concurrent syncs + # Memory: miden-node needs ~4GB for claims + bridge-out min_memory=4096, - min_cpu=1000, # Docker Desktop grouping label labels={ DOCKER_PROJECT_LABEL: MIDEN_PROJECT_GROUP, @@ -147,11 +175,184 @@ def _deploy_miden_node(plan, deployment_suffix, image): ) -def _deploy_miden_proxy(plan, deployment_suffix, image, network_id, bridge_address, faucet_id): - """Deploy Miden RPC proxy service.""" +def _deploy_agglayer_postgres(plan, deployment_suffix): + """Deploy PostgreSQL for miden-agglayer store.""" + service_name = "miden-agglayer-postgres" + deployment_suffix + + return plan.add_service( + name=service_name, + config=ServiceConfig( + image="postgres:16-alpine", + ports={ + "postgres": PortSpec( + number=AGGLAYER_POSTGRES_PORT, + transport_protocol="TCP", + ), + }, + env_vars={ + "POSTGRES_USER": AGGLAYER_DB_USER, + "POSTGRES_PASSWORD": AGGLAYER_DB_PASSWORD, + "POSTGRES_DB": AGGLAYER_DB_NAME, + }, + labels={ + DOCKER_PROJECT_LABEL: MIDEN_PROJECT_GROUP, + }, + ), + ) + + +def _apply_agglayer_migration(plan, deployment_suffix): + """Run the miden-agglayer database migration.""" + service_name = "miden-agglayer-postgres" + deployment_suffix + + # Migration SQL from miden-agglayer/migrations/001_initial.sql + migration_sql = """ +CREATE TABLE service_state ( + id INT PRIMARY KEY DEFAULT 1 CHECK (id = 1), + latest_block_number BIGINT NOT NULL DEFAULT 0, + log_counter BIGINT NOT NULL DEFAULT 0, + hash_chain_value BYTEA NOT NULL DEFAULT '\\x0000000000000000000000000000000000000000000000000000000000000000', + deposit_counter INT NOT NULL DEFAULT 0, + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() +); +INSERT INTO service_state (id) VALUES (1); + +CREATE TABLE synthetic_logs ( + id BIGSERIAL PRIMARY KEY, + log_index BIGINT NOT NULL, + address TEXT NOT NULL, + topics TEXT[] NOT NULL, + data TEXT NOT NULL, + block_number BIGINT NOT NULL, + block_hash BYTEA NOT NULL, + transaction_hash TEXT NOT NULL, + transaction_index BIGINT NOT NULL DEFAULT 0, + removed BOOLEAN NOT NULL DEFAULT FALSE, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); +CREATE INDEX idx_logs_block_range_address ON synthetic_logs (block_number, lower(address)); +CREATE INDEX idx_logs_tx_hash ON synthetic_logs (lower(transaction_hash)); + +CREATE TABLE ger_entries ( + ger_hash BYTEA PRIMARY KEY, + mainnet_exit_root BYTEA, + rollup_exit_root BYTEA, + block_number BIGINT NOT NULL, + timestamp BIGINT NOT NULL, + is_injected BOOLEAN NOT NULL DEFAULT FALSE, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TABLE transactions ( + tx_hash TEXT PRIMARY KEY, + miden_tx_id TEXT, + envelope_bytes BYTEA NOT NULL, + signer TEXT NOT NULL, + expires_at BIGINT, + status TEXT NOT NULL DEFAULT 'pending', + error_message TEXT, + block_number BIGINT NOT NULL DEFAULT 0, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() +); +CREATE INDEX idx_txns_status ON transactions (status); +CREATE INDEX idx_txns_miden_id ON transactions (miden_tx_id) WHERE miden_tx_id IS NOT NULL; + +CREATE TABLE transaction_logs ( + id BIGSERIAL PRIMARY KEY, + tx_hash TEXT NOT NULL REFERENCES transactions(tx_hash) ON DELETE CASCADE, + topics BYTEA[] NOT NULL, + data BYTEA NOT NULL +); +CREATE INDEX idx_txn_logs_tx_hash ON transaction_logs (tx_hash); + +CREATE TABLE nonces ( + address TEXT PRIMARY KEY, + nonce BIGINT NOT NULL DEFAULT 0 +); + +CREATE TABLE claimed_indices ( + global_index TEXT PRIMARY KEY, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TABLE address_mappings ( + eth_address TEXT PRIMARY KEY, + miden_account TEXT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TABLE bridge_out_processed ( + note_id TEXT PRIMARY KEY, + deposit_count INT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +-- 002_faucet_registry.sql — dynamic faucet registry for bridged tokens. +-- Maps each L1 origin token to its Miden faucet account. +CREATE TABLE IF NOT EXISTS faucet_registry ( + faucet_id TEXT PRIMARY KEY, + origin_address BYTEA NOT NULL, + origin_network INT NOT NULL, + symbol TEXT NOT NULL, + origin_decimals SMALLINT NOT NULL, + miden_decimals SMALLINT NOT NULL, + scale SMALLINT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); +CREATE UNIQUE INDEX IF NOT EXISTS idx_faucet_origin + ON faucet_registry (origin_address, origin_network); +""" + + plan.exec( + service_name=service_name, + recipe=ExecRecipe( + command=[ + "/bin/sh", "-c", + "cat << 'EOSQL' | psql -U {} -d {}\n{}\nEOSQL".format( + AGGLAYER_DB_USER, + AGGLAYER_DB_NAME, + migration_sql, + ), + ], + ), + description="Running miden-agglayer database migration", + ) + + +def _deploy_miden_proxy(plan, deployment_suffix, image, network_id, l1_rpc_url, contract_addresses, database_url): + """Deploy miden-agglayer RPC proxy service.""" service_name = "miden-proxy" + deployment_suffix miden_node_url = "http://miden-node{}:{}".format(deployment_suffix, MIDEN_NODE_PORT) + env_vars = { + # Rollup network ID from RollupManager (first rollup = 1) + # This is used by the bridge's networkID() call, NOT the same as chain ID + "NETWORK_ID": "1", + "RUST_LOG": "info", + "DATABASE_URL": database_url, + # claim submission path reads MIDEN_NODE_URL from env (not --miden-node CLI); + # main.rs:222 defaults it to "http://miden-node:57291" — wrong hostname in + # Kurtosis where the service is `miden-node{deployment_suffix}`. + "MIDEN_NODE_URL": miden_node_url, + } + + # Bridge and L1 contract addresses + bridge_address = contract_addresses.get("l1_bridge_address", "") + if not bridge_address: + bridge_address = contract_addresses.get("polygon_bridge_address", "") + if bridge_address: + env_vars["BRIDGE_ADDRESS"] = bridge_address + + if l1_rpc_url: + env_vars["L1_RPC_URL"] = l1_rpc_url + + # NOTE: env var name is GER_L1_ADDRESS (L1 after GER), not L1_GER_ADDRESS. + # The release/0.1 proxy CLI uses this exact name; prior versions had it reversed. + l1_ger_address = contract_addresses.get("l1_ger_address", "") + if l1_ger_address: + env_vars["GER_L1_ADDRESS"] = l1_ger_address + return plan.add_service( name=service_name, config=ServiceConfig( @@ -163,19 +364,13 @@ def _deploy_miden_proxy(plan, deployment_suffix, image, network_id, bridge_addre application_protocol="http", ), }, - env_vars={ - "CHAIN_ID": str(network_id), - "MIDEN_RPC_URL": miden_node_url, - "MIDEN_STORE_PATH": "/app/data/miden-client", - "BRIDGE_FAUCET_ID": faucet_id, - "BRIDGE_ADDRESS": bridge_address, - "LISTEN_PORT": str(MIDEN_PROXY_PORT), - "RUST_LOG": "info", - }, - # Proxy runs miden-client syncs — needs memory for stress tests - min_memory=1024, - max_memory=2048, - min_cpu=500, + cmd=[ + "--chain-id={}".format(network_id), + "--miden-node={}".format(miden_node_url), + "--miden-store-dir=/var/lib/miden-agglayer-service", + "--port={}".format(MIDEN_PROXY_PORT), + ], + env_vars=env_vars, # Docker Desktop grouping label labels={ DOCKER_PROJECT_LABEL: MIDEN_PROJECT_GROUP, diff --git a/kurtosis/miden-cdk/params.yaml b/kurtosis/miden-cdk/params.yaml index 154aacf..696ffdb 100644 --- a/kurtosis/miden-cdk/params.yaml +++ b/kurtosis/miden-cdk/params.yaml @@ -37,19 +37,15 @@ miden: miden_chain_id: 2 # Miden node image - # Build with: docker build -t miden-infra/miden-node:agglayer-v0.1 -f Dockerfile.miden-node . - miden_node_image: "miden-infra/miden-node:agglayer-v0.1" + # Build with: docker build -t miden-infra/miden-node:v0.14.6 -f Dockerfile.miden-node . + miden_node_image: "miden-infra/miden-node:v0.14.6" miden_node_port: 57291 - # Miden RPC proxy image - # Build with: docker build -t miden-infra/miden-proxy:latest . - miden_proxy_image: "miden-infra/miden-proxy:latest" + # Miden RPC proxy image (miden-agglayer release/0.1 — the target under test) + # Build: docker build https://github.com/gateway-fm/miden-agglayer.git#release/0.1 -t miden-infra/miden-proxy:release-0.1 + miden_proxy_image: "miden-infra/miden-proxy:release-0.1" miden_proxy_port: 8546 - # Bridge faucet ID for claim transactions - # This account ID is used by the proxy to mint tokens for claims - bridge_faucet_id: "0x000000000000000000000000000001" - # Deploy web UI for sending deposits (bridge ETH from L1 to Miden) deploy_web_ui: true @@ -75,13 +71,15 @@ args: # Deployment suffix deployment_suffix: "-001" - # Log level - log_level: "info" + # Log level (debug for aggoracle visibility) + log_level: "debug" log_format: "pretty" # Images (can be overridden) # zkevm_bridge_service_image: "hermeznetwork/zkevm-bridge-service:v0.6.0-RC1" - # aggkit_image: "ghcr.io/0xpolygon/aggkit:latest" + # Patched: fix ethtxmanager "already exists" stuck state (aggkit#1479) + # Build: cd /tmp/aggkit-patch && docker build -t ghcr.io/agglayer/aggkit:0.9.0-rc3-nugget1 . + aggkit_image: "ghcr.io/agglayer/aggkit:0.9.0-rc3-nugget1" # Agglayer configuration agglayer_prover_primary_prover: "mock-prover" diff --git a/out/TestToken.sol/TestToken.json b/out/TestToken.sol/TestToken.json new file mode 100644 index 0000000..22c930b --- /dev/null +++ b/out/TestToken.sol/TestToken.json @@ -0,0 +1 @@ +{"abi":[{"type":"constructor","inputs":[{"name":"_name","type":"string","internalType":"string"},{"name":"_symbol","type":"string","internalType":"string"},{"name":"_decimals","type":"uint8","internalType":"uint8"}],"stateMutability":"nonpayable"},{"type":"function","name":"allowance","inputs":[{"name":"","type":"address","internalType":"address"},{"name":"","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"approve","inputs":[{"name":"spender","type":"address","internalType":"address"},{"name":"amount","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"nonpayable"},{"type":"function","name":"balanceOf","inputs":[{"name":"","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"decimals","inputs":[],"outputs":[{"name":"","type":"uint8","internalType":"uint8"}],"stateMutability":"view"},{"type":"function","name":"mint","inputs":[{"name":"to","type":"address","internalType":"address"},{"name":"amount","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"name","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"type":"function","name":"symbol","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"type":"function","name":"totalSupply","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"transfer","inputs":[{"name":"to","type":"address","internalType":"address"},{"name":"amount","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"nonpayable"},{"type":"function","name":"transferFrom","inputs":[{"name":"from","type":"address","internalType":"address"},{"name":"to","type":"address","internalType":"address"},{"name":"amount","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"nonpayable"},{"type":"event","name":"Approval","inputs":[{"name":"owner","type":"address","indexed":true,"internalType":"address"},{"name":"spender","type":"address","indexed":true,"internalType":"address"},{"name":"value","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"Transfer","inputs":[{"name":"from","type":"address","indexed":true,"internalType":"address"},{"name":"to","type":"address","indexed":true,"internalType":"address"},{"name":"value","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false}],"bytecode":{"object":"0x608060405234801561000f575f5ffd5b506040516113c53803806113c5833981810160405281019061003191906101f5565b825f908161003f919061048d565b50816001908161004f919061048d565b508060025f6101000a81548160ff021916908360ff16021790555050505061055c565b5f604051905090565b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6100d18261008b565b810181811067ffffffffffffffff821117156100f0576100ef61009b565b5b80604052505050565b5f610102610072565b905061010e82826100c8565b919050565b5f67ffffffffffffffff82111561012d5761012c61009b565b5b6101368261008b565b9050602081019050919050565b8281835e5f83830152505050565b5f61016361015e84610113565b6100f9565b90508281526020810184848401111561017f5761017e610087565b5b61018a848285610143565b509392505050565b5f82601f8301126101a6576101a5610083565b5b81516101b6848260208601610151565b91505092915050565b5f60ff82169050919050565b6101d4816101bf565b81146101de575f5ffd5b50565b5f815190506101ef816101cb565b92915050565b5f5f5f6060848603121561020c5761020b61007b565b5b5f84015167ffffffffffffffff8111156102295761022861007f565b5b61023586828701610192565b935050602084015167ffffffffffffffff8111156102565761025561007f565b5b61026286828701610192565b9250506040610273868287016101e1565b9150509250925092565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806102cb57607f821691505b6020821081036102de576102dd610287565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f600883026103407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82610305565b61034a8683610305565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f61038e61038961038484610362565b61036b565b610362565b9050919050565b5f819050919050565b6103a783610374565b6103bb6103b382610395565b848454610311565b825550505050565b5f5f905090565b6103d26103c3565b6103dd81848461039e565b505050565b5b81811015610400576103f55f826103ca565b6001810190506103e3565b5050565b601f82111561044557610416816102e4565b61041f846102f6565b8101602085101561042e578190505b61044261043a856102f6565b8301826103e2565b50505b505050565b5f82821c905092915050565b5f6104655f198460080261044a565b1980831691505092915050565b5f61047d8383610456565b9150826002028217905092915050565b6104968261027d565b67ffffffffffffffff8111156104af576104ae61009b565b5b6104b982546102b4565b6104c4828285610404565b5f60209050601f8311600181146104f5575f84156104e3578287015190505b6104ed8582610472565b865550610554565b601f198416610503866102e4565b5f5b8281101561052a57848901518255600182019150602085019450602081019050610505565b868310156105475784890151610543601f891682610456565b8355505b6001600288020188555050505b505050505050565b610e5c806105695f395ff3fe608060405234801561000f575f5ffd5b506004361061009c575f3560e01c806340c10f191161006457806340c10f191461015a57806370a082311461017657806395d89b41146101a6578063a9059cbb146101c4578063dd62ed3e146101f45761009c565b806306fdde03146100a0578063095ea7b3146100be57806318160ddd146100ee57806323b872dd1461010c578063313ce5671461013c575b5f5ffd5b6100a8610224565b6040516100b59190610a2f565b60405180910390f35b6100d860048036038101906100d39190610ae0565b6102af565b6040516100e59190610b38565b60405180910390f35b6100f661039c565b6040516101039190610b60565b60405180910390f35b61012660048036038101906101219190610b79565b6103a2565b6040516101339190610b38565b60405180910390f35b610144610682565b6040516101519190610be4565b60405180910390f35b610174600480360381019061016f9190610ae0565b610694565b005b610190600480360381019061018b9190610bfd565b610768565b60405161019d9190610b60565b60405180910390f35b6101ae61077d565b6040516101bb9190610a2f565b60405180910390f35b6101de60048036038101906101d99190610ae0565b610809565b6040516101eb9190610b38565b60405180910390f35b61020e60048036038101906102099190610c28565b61099f565b60405161021b9190610b60565b60405180910390f35b5f805461023090610c93565b80601f016020809104026020016040519081016040528092919081815260200182805461025c90610c93565b80156102a75780601f1061027e576101008083540402835291602001916102a7565b820191905f5260205f20905b81548152906001019060200180831161028a57829003601f168201915b505050505081565b5f8160055f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258460405161038a9190610b60565b60405180910390a36001905092915050565b60035481565b5f8160055f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054101561045e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161045590610d0d565b60405180910390fd5b8160045f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205410156104de576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d590610d75565b60405180910390fd5b8160055f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546105659190610dc0565b925050819055508160045f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546105b89190610dc0565b925050819055508160045f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825461060b9190610df3565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161066f9190610b60565b60405180910390a3600190509392505050565b60025f9054906101000a900460ff1681565b8060035f8282546106a59190610df3565b925050819055508060045f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546106f89190610df3565b925050819055508173ffffffffffffffffffffffffffffffffffffffff165f73ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8360405161075c9190610b60565b60405180910390a35050565b6004602052805f5260405f205f915090505481565b6001805461078a90610c93565b80601f01602080910402602001604051908101604052809291908181526020018280546107b690610c93565b80156108015780601f106107d857610100808354040283529160200191610801565b820191905f5260205f20905b8154815290600101906020018083116107e457829003601f168201915b505050505081565b5f8160045f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054101561088a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161088190610d75565b60405180910390fd5b8160045f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546108d69190610dc0565b925050819055508160045f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546109299190610df3565b925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161098d9190610b60565b60405180910390a36001905092915050565b6005602052815f5260405f20602052805f5260405f205f91509150505481565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f610a01826109bf565b610a0b81856109c9565b9350610a1b8185602086016109d9565b610a24816109e7565b840191505092915050565b5f6020820190508181035f830152610a4781846109f7565b905092915050565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610a7c82610a53565b9050919050565b610a8c81610a72565b8114610a96575f5ffd5b50565b5f81359050610aa781610a83565b92915050565b5f819050919050565b610abf81610aad565b8114610ac9575f5ffd5b50565b5f81359050610ada81610ab6565b92915050565b5f5f60408385031215610af657610af5610a4f565b5b5f610b0385828601610a99565b9250506020610b1485828601610acc565b9150509250929050565b5f8115159050919050565b610b3281610b1e565b82525050565b5f602082019050610b4b5f830184610b29565b92915050565b610b5a81610aad565b82525050565b5f602082019050610b735f830184610b51565b92915050565b5f5f5f60608486031215610b9057610b8f610a4f565b5b5f610b9d86828701610a99565b9350506020610bae86828701610a99565b9250506040610bbf86828701610acc565b9150509250925092565b5f60ff82169050919050565b610bde81610bc9565b82525050565b5f602082019050610bf75f830184610bd5565b92915050565b5f60208284031215610c1257610c11610a4f565b5b5f610c1f84828501610a99565b91505092915050565b5f5f60408385031215610c3e57610c3d610a4f565b5b5f610c4b85828601610a99565b9250506020610c5c85828601610a99565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680610caa57607f821691505b602082108103610cbd57610cbc610c66565b5b50919050565b7f6e6f7420617070726f76656400000000000000000000000000000000000000005f82015250565b5f610cf7600c836109c9565b9150610d0282610cc3565b602082019050919050565b5f6020820190508181035f830152610d2481610ceb565b9050919050565b7f696e73756666696369656e7400000000000000000000000000000000000000005f82015250565b5f610d5f600c836109c9565b9150610d6a82610d2b565b602082019050919050565b5f6020820190508181035f830152610d8c81610d53565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610dca82610aad565b9150610dd583610aad565b9250828203905081811115610ded57610dec610d93565b5b92915050565b5f610dfd82610aad565b9150610e0883610aad565b9250828201905080821115610e2057610e1f610d93565b5b9291505056fea264697066735822122078fde2500bf71b936ecde3d8c26d1ad70c9c7b4b8d6e8ea02be1cda0f9eaaa3164736f6c634300081c0033","sourceMap":"59:1661:0:-:0;;;475:158;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;565:5;558:4;:12;;;;;;:::i;:::-;;589:7;580:6;:16;;;;;;:::i;:::-;;617:9;606:8;;:20;;;;;;;;;;;;;;;;;;475:158;;;59:1661;;7:75:1;40:6;73:2;67:9;57:19;;7:75;:::o;88:117::-;197:1;194;187:12;211:117;320:1;317;310:12;334:117;443:1;440;433:12;457:117;566:1;563;556:12;580:102;621:6;672:2;668:7;663:2;656:5;652:14;648:28;638:38;;580:102;;;:::o;688:180::-;736:77;733:1;726:88;833:4;830:1;823:15;857:4;854:1;847:15;874:281;957:27;979:4;957:27;:::i;:::-;949:6;945:40;1087:6;1075:10;1072:22;1051:18;1039:10;1036:34;1033:62;1030:88;;;1098:18;;:::i;:::-;1030:88;1138:10;1134:2;1127:22;917:238;874:281;;:::o;1161:129::-;1195:6;1222:20;;:::i;:::-;1212:30;;1251:33;1279:4;1271:6;1251:33;:::i;:::-;1161:129;;;:::o;1296:308::-;1358:4;1448:18;1440:6;1437:30;1434:56;;;1470:18;;:::i;:::-;1434:56;1508:29;1530:6;1508:29;:::i;:::-;1500:37;;1592:4;1586;1582:15;1574:23;;1296:308;;;:::o;1610:139::-;1699:6;1694:3;1689;1683:23;1740:1;1731:6;1726:3;1722:16;1715:27;1610:139;;;:::o;1755:434::-;1844:5;1869:66;1885:49;1927:6;1885:49;:::i;:::-;1869:66;:::i;:::-;1860:75;;1958:6;1951:5;1944:21;1996:4;1989:5;1985:16;2034:3;2025:6;2020:3;2016:16;2013:25;2010:112;;;2041:79;;:::i;:::-;2010:112;2131:52;2176:6;2171:3;2166;2131:52;:::i;:::-;1850:339;1755:434;;;;;:::o;2209:355::-;2276:5;2325:3;2318:4;2310:6;2306:17;2302:27;2292:122;;2333:79;;:::i;:::-;2292:122;2443:6;2437:13;2468:90;2554:3;2546:6;2539:4;2531:6;2527:17;2468:90;:::i;:::-;2459:99;;2282:282;2209:355;;;;:::o;2570:86::-;2605:7;2645:4;2638:5;2634:16;2623:27;;2570:86;;;:::o;2662:118::-;2733:22;2749:5;2733:22;:::i;:::-;2726:5;2723:33;2713:61;;2770:1;2767;2760:12;2713:61;2662:118;:::o;2786:139::-;2841:5;2872:6;2866:13;2857:22;;2888:31;2913:5;2888:31;:::i;:::-;2786:139;;;;:::o;2931:1005::-;3037:6;3045;3053;3102:2;3090:9;3081:7;3077:23;3073:32;3070:119;;;3108:79;;:::i;:::-;3070:119;3249:1;3238:9;3234:17;3228:24;3279:18;3271:6;3268:30;3265:117;;;3301:79;;:::i;:::-;3265:117;3406:74;3472:7;3463:6;3452:9;3448:22;3406:74;:::i;:::-;3396:84;;3199:291;3550:2;3539:9;3535:18;3529:25;3581:18;3573:6;3570:30;3567:117;;;3603:79;;:::i;:::-;3567:117;3708:74;3774:7;3765:6;3754:9;3750:22;3708:74;:::i;:::-;3698:84;;3500:292;3831:2;3857:62;3911:7;3902:6;3891:9;3887:22;3857:62;:::i;:::-;3847:72;;3802:127;2931:1005;;;;;:::o;3942:99::-;3994:6;4028:5;4022:12;4012:22;;3942:99;;;:::o;4047:180::-;4095:77;4092:1;4085:88;4192:4;4189:1;4182:15;4216:4;4213:1;4206:15;4233:320;4277:6;4314:1;4308:4;4304:12;4294:22;;4361:1;4355:4;4351:12;4382:18;4372:81;;4438:4;4430:6;4426:17;4416:27;;4372:81;4500:2;4492:6;4489:14;4469:18;4466:38;4463:84;;4519:18;;:::i;:::-;4463:84;4284:269;4233:320;;;:::o;4559:141::-;4608:4;4631:3;4623:11;;4654:3;4651:1;4644:14;4688:4;4685:1;4675:18;4667:26;;4559:141;;;:::o;4706:93::-;4743:6;4790:2;4785;4778:5;4774:14;4770:23;4760:33;;4706:93;;;:::o;4805:107::-;4849:8;4899:5;4893:4;4889:16;4868:37;;4805:107;;;;:::o;4918:393::-;4987:6;5037:1;5025:10;5021:18;5060:97;5090:66;5079:9;5060:97;:::i;:::-;5178:39;5208:8;5197:9;5178:39;:::i;:::-;5166:51;;5250:4;5246:9;5239:5;5235:21;5226:30;;5299:4;5289:8;5285:19;5278:5;5275:30;5265:40;;4994:317;;4918:393;;;;;:::o;5317:77::-;5354:7;5383:5;5372:16;;5317:77;;;:::o;5400:60::-;5428:3;5449:5;5442:12;;5400:60;;;:::o;5466:142::-;5516:9;5549:53;5567:34;5576:24;5594:5;5576:24;:::i;:::-;5567:34;:::i;:::-;5549:53;:::i;:::-;5536:66;;5466:142;;;:::o;5614:75::-;5657:3;5678:5;5671:12;;5614:75;;;:::o;5695:269::-;5805:39;5836:7;5805:39;:::i;:::-;5866:91;5915:41;5939:16;5915:41;:::i;:::-;5907:6;5900:4;5894:11;5866:91;:::i;:::-;5860:4;5853:105;5771:193;5695:269;;;:::o;5970:73::-;6015:3;6036:1;6029:8;;5970:73;:::o;6049:189::-;6126:32;;:::i;:::-;6167:65;6225:6;6217;6211:4;6167:65;:::i;:::-;6102:136;6049:189;;:::o;6244:186::-;6304:120;6321:3;6314:5;6311:14;6304:120;;;6375:39;6412:1;6405:5;6375:39;:::i;:::-;6348:1;6341:5;6337:13;6328:22;;6304:120;;;6244:186;;:::o;6436:543::-;6537:2;6532:3;6529:11;6526:446;;;6571:38;6603:5;6571:38;:::i;:::-;6655:29;6673:10;6655:29;:::i;:::-;6645:8;6641:44;6838:2;6826:10;6823:18;6820:49;;;6859:8;6844:23;;6820:49;6882:80;6938:22;6956:3;6938:22;:::i;:::-;6928:8;6924:37;6911:11;6882:80;:::i;:::-;6541:431;;6526:446;6436:543;;;:::o;6985:117::-;7039:8;7089:5;7083:4;7079:16;7058:37;;6985:117;;;;:::o;7108:169::-;7152:6;7185:51;7233:1;7229:6;7221:5;7218:1;7214:13;7185:51;:::i;:::-;7181:56;7266:4;7260;7256:15;7246:25;;7159:118;7108:169;;;;:::o;7282:295::-;7358:4;7504:29;7529:3;7523:4;7504:29;:::i;:::-;7496:37;;7566:3;7563:1;7559:11;7553:4;7550:21;7542:29;;7282:295;;;;:::o;7582:1395::-;7699:37;7732:3;7699:37;:::i;:::-;7801:18;7793:6;7790:30;7787:56;;;7823:18;;:::i;:::-;7787:56;7867:38;7899:4;7893:11;7867:38;:::i;:::-;7952:67;8012:6;8004;7998:4;7952:67;:::i;:::-;8046:1;8070:4;8057:17;;8102:2;8094:6;8091:14;8119:1;8114:618;;;;8776:1;8793:6;8790:77;;;8842:9;8837:3;8833:19;8827:26;8818:35;;8790:77;8893:67;8953:6;8946:5;8893:67;:::i;:::-;8887:4;8880:81;8749:222;8084:887;;8114:618;8166:4;8162:9;8154:6;8150:22;8200:37;8232:4;8200:37;:::i;:::-;8259:1;8273:208;8287:7;8284:1;8281:14;8273:208;;;8366:9;8361:3;8357:19;8351:26;8343:6;8336:42;8417:1;8409:6;8405:14;8395:24;;8464:2;8453:9;8449:18;8436:31;;8310:4;8307:1;8303:12;8298:17;;8273:208;;;8509:6;8500:7;8497:19;8494:179;;;8567:9;8562:3;8558:19;8552:26;8610:48;8652:4;8644:6;8640:17;8629:9;8610:48;:::i;:::-;8602:6;8595:64;8517:156;8494:179;8719:1;8715;8707:6;8703:14;8699:22;8693:4;8686:36;8121:611;;;8084:887;;7674:1303;;;7582:1395;;:::o;59:1661:0:-;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x608060405234801561000f575f5ffd5b506004361061009c575f3560e01c806340c10f191161006457806340c10f191461015a57806370a082311461017657806395d89b41146101a6578063a9059cbb146101c4578063dd62ed3e146101f45761009c565b806306fdde03146100a0578063095ea7b3146100be57806318160ddd146100ee57806323b872dd1461010c578063313ce5671461013c575b5f5ffd5b6100a8610224565b6040516100b59190610a2f565b60405180910390f35b6100d860048036038101906100d39190610ae0565b6102af565b6040516100e59190610b38565b60405180910390f35b6100f661039c565b6040516101039190610b60565b60405180910390f35b61012660048036038101906101219190610b79565b6103a2565b6040516101339190610b38565b60405180910390f35b610144610682565b6040516101519190610be4565b60405180910390f35b610174600480360381019061016f9190610ae0565b610694565b005b610190600480360381019061018b9190610bfd565b610768565b60405161019d9190610b60565b60405180910390f35b6101ae61077d565b6040516101bb9190610a2f565b60405180910390f35b6101de60048036038101906101d99190610ae0565b610809565b6040516101eb9190610b38565b60405180910390f35b61020e60048036038101906102099190610c28565b61099f565b60405161021b9190610b60565b60405180910390f35b5f805461023090610c93565b80601f016020809104026020016040519081016040528092919081815260200182805461025c90610c93565b80156102a75780601f1061027e576101008083540402835291602001916102a7565b820191905f5260205f20905b81548152906001019060200180831161028a57829003601f168201915b505050505081565b5f8160055f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258460405161038a9190610b60565b60405180910390a36001905092915050565b60035481565b5f8160055f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054101561045e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161045590610d0d565b60405180910390fd5b8160045f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205410156104de576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d590610d75565b60405180910390fd5b8160055f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546105659190610dc0565b925050819055508160045f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546105b89190610dc0565b925050819055508160045f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825461060b9190610df3565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161066f9190610b60565b60405180910390a3600190509392505050565b60025f9054906101000a900460ff1681565b8060035f8282546106a59190610df3565b925050819055508060045f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546106f89190610df3565b925050819055508173ffffffffffffffffffffffffffffffffffffffff165f73ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8360405161075c9190610b60565b60405180910390a35050565b6004602052805f5260405f205f915090505481565b6001805461078a90610c93565b80601f01602080910402602001604051908101604052809291908181526020018280546107b690610c93565b80156108015780601f106107d857610100808354040283529160200191610801565b820191905f5260205f20905b8154815290600101906020018083116107e457829003601f168201915b505050505081565b5f8160045f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054101561088a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161088190610d75565b60405180910390fd5b8160045f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546108d69190610dc0565b925050819055508160045f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546109299190610df3565b925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161098d9190610b60565b60405180910390a36001905092915050565b6005602052815f5260405f20602052805f5260405f205f91509150505481565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f610a01826109bf565b610a0b81856109c9565b9350610a1b8185602086016109d9565b610a24816109e7565b840191505092915050565b5f6020820190508181035f830152610a4781846109f7565b905092915050565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610a7c82610a53565b9050919050565b610a8c81610a72565b8114610a96575f5ffd5b50565b5f81359050610aa781610a83565b92915050565b5f819050919050565b610abf81610aad565b8114610ac9575f5ffd5b50565b5f81359050610ada81610ab6565b92915050565b5f5f60408385031215610af657610af5610a4f565b5b5f610b0385828601610a99565b9250506020610b1485828601610acc565b9150509250929050565b5f8115159050919050565b610b3281610b1e565b82525050565b5f602082019050610b4b5f830184610b29565b92915050565b610b5a81610aad565b82525050565b5f602082019050610b735f830184610b51565b92915050565b5f5f5f60608486031215610b9057610b8f610a4f565b5b5f610b9d86828701610a99565b9350506020610bae86828701610a99565b9250506040610bbf86828701610acc565b9150509250925092565b5f60ff82169050919050565b610bde81610bc9565b82525050565b5f602082019050610bf75f830184610bd5565b92915050565b5f60208284031215610c1257610c11610a4f565b5b5f610c1f84828501610a99565b91505092915050565b5f5f60408385031215610c3e57610c3d610a4f565b5b5f610c4b85828601610a99565b9250506020610c5c85828601610a99565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680610caa57607f821691505b602082108103610cbd57610cbc610c66565b5b50919050565b7f6e6f7420617070726f76656400000000000000000000000000000000000000005f82015250565b5f610cf7600c836109c9565b9150610d0282610cc3565b602082019050919050565b5f6020820190508181035f830152610d2481610ceb565b9050919050565b7f696e73756666696369656e7400000000000000000000000000000000000000005f82015250565b5f610d5f600c836109c9565b9150610d6a82610d2b565b602082019050919050565b5f6020820190508181035f830152610d8c81610d53565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610dca82610aad565b9150610dd583610aad565b9250828203905081811115610ded57610dec610d93565b5b92915050565b5f610dfd82610aad565b9150610e0883610aad565b9250828201905080821115610e2057610e1f610d93565b5b9291505056fea264697066735822122078fde2500bf71b936ecde3d8c26d1ad70c9c7b4b8d6e8ea02be1cda0f9eaaa3164736f6c634300081c0033","sourceMap":"59:1661:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;84:18;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;814:203;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;161:26;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;1314:404;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;134:21;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;639:169;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;193:44;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;108:20;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;1023:285;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;243:64;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;84:18;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;814:203::-;882:4;931:6;898:9;:21;908:10;898:21;;;;;;;;;;;;;;;:30;920:7;898:30;;;;;;;;;;;;;;;:39;;;;973:7;952:37;;961:10;952:37;;;982:6;952:37;;;;;;:::i;:::-;;;;;;;;1006:4;999:11;;814:203;;;;:::o;161:26::-;;;;:::o;1314:404::-;1396:4;1451:6;1420:9;:15;1430:4;1420:15;;;;;;;;;;;;;;;:27;1436:10;1420:27;;;;;;;;;;;;;;;;:37;;1412:62;;;;;;;;;;;;:::i;:::-;;;;;;;;;1511:6;1492:9;:15;1502:4;1492:15;;;;;;;;;;;;;;;;:25;;1484:50;;;;;;;;;;;;:::i;:::-;;;;;;;;;1575:6;1544:9;:15;1554:4;1544:15;;;;;;;;;;;;;;;:27;1560:10;1544:27;;;;;;;;;;;;;;;;:37;;;;;;;:::i;:::-;;;;;;;;1610:6;1591:9;:15;1601:4;1591:15;;;;;;;;;;;;;;;;:25;;;;;;;:::i;:::-;;;;;;;;1643:6;1626:9;:13;1636:2;1626:13;;;;;;;;;;;;;;;;:23;;;;;;;:::i;:::-;;;;;;;;1679:2;1664:26;;1673:4;1664:26;;;1683:6;1664:26;;;;;;:::i;:::-;;;;;;;;1707:4;1700:11;;1314:404;;;;;:::o;134:21::-;;;;;;;;;;;;;:::o;639:169::-;715:6;700:11;;:21;;;;;;;:::i;:::-;;;;;;;;748:6;731:9;:13;741:2;731:13;;;;;;;;;;;;;;;;:23;;;;;;;:::i;:::-;;;;;;;;790:2;769:32;;786:1;769:32;;;794:6;769:32;;;;;;:::i;:::-;;;;;;;;639:169;;:::o;193:44::-;;;;;;;;;;;;;;;;;:::o;108:20::-;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;1023:285::-;1087:4;1136:6;1111:9;:21;1121:10;1111:21;;;;;;;;;;;;;;;;:31;;1103:56;;;;;;;;;;;;:::i;:::-;;;;;;;;;1194:6;1169:9;:21;1179:10;1169:21;;;;;;;;;;;;;;;;:31;;;;;;;:::i;:::-;;;;;;;;1227:6;1210:9;:13;1220:2;1210:13;;;;;;;;;;;;;;;;:23;;;;;;;:::i;:::-;;;;;;;;1269:2;1248:32;;1257:10;1248:32;;;1273:6;1248:32;;;;;;:::i;:::-;;;;;;;;1297:4;1290:11;;1023:285;;;;:::o;243:64::-;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;7:99:1:-;59:6;93:5;87:12;77:22;;7:99;;;:::o;112:169::-;196:11;230:6;225:3;218:19;270:4;265:3;261:14;246:29;;112:169;;;;:::o;287:139::-;376:6;371:3;366;360:23;417:1;408:6;403:3;399:16;392:27;287:139;;;:::o;432:102::-;473:6;524:2;520:7;515:2;508:5;504:14;500:28;490:38;;432:102;;;:::o;540:377::-;628:3;656:39;689:5;656:39;:::i;:::-;711:71;775:6;770:3;711:71;:::i;:::-;704:78;;791:65;849:6;844:3;837:4;830:5;826:16;791:65;:::i;:::-;881:29;903:6;881:29;:::i;:::-;876:3;872:39;865:46;;632:285;540:377;;;;:::o;923:313::-;1036:4;1074:2;1063:9;1059:18;1051:26;;1123:9;1117:4;1113:20;1109:1;1098:9;1094:17;1087:47;1151:78;1224:4;1215:6;1151:78;:::i;:::-;1143:86;;923:313;;;;:::o;1323:117::-;1432:1;1429;1422:12;1569:126;1606:7;1646:42;1639:5;1635:54;1624:65;;1569:126;;;:::o;1701:96::-;1738:7;1767:24;1785:5;1767:24;:::i;:::-;1756:35;;1701:96;;;:::o;1803:122::-;1876:24;1894:5;1876:24;:::i;:::-;1869:5;1866:35;1856:63;;1915:1;1912;1905:12;1856:63;1803:122;:::o;1931:139::-;1977:5;2015:6;2002:20;1993:29;;2031:33;2058:5;2031:33;:::i;:::-;1931:139;;;;:::o;2076:77::-;2113:7;2142:5;2131:16;;2076:77;;;:::o;2159:122::-;2232:24;2250:5;2232:24;:::i;:::-;2225:5;2222:35;2212:63;;2271:1;2268;2261:12;2212:63;2159:122;:::o;2287:139::-;2333:5;2371:6;2358:20;2349:29;;2387:33;2414:5;2387:33;:::i;:::-;2287:139;;;;:::o;2432:474::-;2500:6;2508;2557:2;2545:9;2536:7;2532:23;2528:32;2525:119;;;2563:79;;:::i;:::-;2525:119;2683:1;2708:53;2753:7;2744:6;2733:9;2729:22;2708:53;:::i;:::-;2698:63;;2654:117;2810:2;2836:53;2881:7;2872:6;2861:9;2857:22;2836:53;:::i;:::-;2826:63;;2781:118;2432:474;;;;;:::o;2912:90::-;2946:7;2989:5;2982:13;2975:21;2964:32;;2912:90;;;:::o;3008:109::-;3089:21;3104:5;3089:21;:::i;:::-;3084:3;3077:34;3008:109;;:::o;3123:210::-;3210:4;3248:2;3237:9;3233:18;3225:26;;3261:65;3323:1;3312:9;3308:17;3299:6;3261:65;:::i;:::-;3123:210;;;;:::o;3339:118::-;3426:24;3444:5;3426:24;:::i;:::-;3421:3;3414:37;3339:118;;:::o;3463:222::-;3556:4;3594:2;3583:9;3579:18;3571:26;;3607:71;3675:1;3664:9;3660:17;3651:6;3607:71;:::i;:::-;3463:222;;;;:::o;3691:619::-;3768:6;3776;3784;3833:2;3821:9;3812:7;3808:23;3804:32;3801:119;;;3839:79;;:::i;:::-;3801:119;3959:1;3984:53;4029:7;4020:6;4009:9;4005:22;3984:53;:::i;:::-;3974:63;;3930:117;4086:2;4112:53;4157:7;4148:6;4137:9;4133:22;4112:53;:::i;:::-;4102:63;;4057:118;4214:2;4240:53;4285:7;4276:6;4265:9;4261:22;4240:53;:::i;:::-;4230:63;;4185:118;3691:619;;;;;:::o;4316:86::-;4351:7;4391:4;4384:5;4380:16;4369:27;;4316:86;;;:::o;4408:112::-;4491:22;4507:5;4491:22;:::i;:::-;4486:3;4479:35;4408:112;;:::o;4526:214::-;4615:4;4653:2;4642:9;4638:18;4630:26;;4666:67;4730:1;4719:9;4715:17;4706:6;4666:67;:::i;:::-;4526:214;;;;:::o;4746:329::-;4805:6;4854:2;4842:9;4833:7;4829:23;4825:32;4822:119;;;4860:79;;:::i;:::-;4822:119;4980:1;5005:53;5050:7;5041:6;5030:9;5026:22;5005:53;:::i;:::-;4995:63;;4951:117;4746:329;;;;:::o;5081:474::-;5149:6;5157;5206:2;5194:9;5185:7;5181:23;5177:32;5174:119;;;5212:79;;:::i;:::-;5174:119;5332:1;5357:53;5402:7;5393:6;5382:9;5378:22;5357:53;:::i;:::-;5347:63;;5303:117;5459:2;5485:53;5530:7;5521:6;5510:9;5506:22;5485:53;:::i;:::-;5475:63;;5430:118;5081:474;;;;;:::o;5561:180::-;5609:77;5606:1;5599:88;5706:4;5703:1;5696:15;5730:4;5727:1;5720:15;5747:320;5791:6;5828:1;5822:4;5818:12;5808:22;;5875:1;5869:4;5865:12;5896:18;5886:81;;5952:4;5944:6;5940:17;5930:27;;5886:81;6014:2;6006:6;6003:14;5983:18;5980:38;5977:84;;6033:18;;:::i;:::-;5977:84;5798:269;5747:320;;;:::o;6073:162::-;6213:14;6209:1;6201:6;6197:14;6190:38;6073:162;:::o;6241:366::-;6383:3;6404:67;6468:2;6463:3;6404:67;:::i;:::-;6397:74;;6480:93;6569:3;6480:93;:::i;:::-;6598:2;6593:3;6589:12;6582:19;;6241:366;;;:::o;6613:419::-;6779:4;6817:2;6806:9;6802:18;6794:26;;6866:9;6860:4;6856:20;6852:1;6841:9;6837:17;6830:47;6894:131;7020:4;6894:131;:::i;:::-;6886:139;;6613:419;;;:::o;7038:162::-;7178:14;7174:1;7166:6;7162:14;7155:38;7038:162;:::o;7206:366::-;7348:3;7369:67;7433:2;7428:3;7369:67;:::i;:::-;7362:74;;7445:93;7534:3;7445:93;:::i;:::-;7563:2;7558:3;7554:12;7547:19;;7206:366;;;:::o;7578:419::-;7744:4;7782:2;7771:9;7767:18;7759:26;;7831:9;7825:4;7821:20;7817:1;7806:9;7802:17;7795:47;7859:131;7985:4;7859:131;:::i;:::-;7851:139;;7578:419;;;:::o;8003:180::-;8051:77;8048:1;8041:88;8148:4;8145:1;8138:15;8172:4;8169:1;8162:15;8189:194;8229:4;8249:20;8267:1;8249:20;:::i;:::-;8244:25;;8283:20;8301:1;8283:20;:::i;:::-;8278:25;;8327:1;8324;8320:9;8312:17;;8351:1;8345:4;8342:11;8339:37;;;8356:18;;:::i;:::-;8339:37;8189:194;;;;:::o;8389:191::-;8429:3;8448:20;8466:1;8448:20;:::i;:::-;8443:25;;8482:20;8500:1;8482:20;:::i;:::-;8477:25;;8525:1;8522;8518:9;8511:16;;8546:3;8543:1;8540:10;8537:36;;;8553:18;;:::i;:::-;8537:36;8389:191;;;;:::o","linkReferences":{}},"methodIdentifiers":{"allowance(address,address)":"dd62ed3e","approve(address,uint256)":"095ea7b3","balanceOf(address)":"70a08231","decimals()":"313ce567","mint(address,uint256)":"40c10f19","name()":"06fdde03","symbol()":"95d89b41","totalSupply()":"18160ddd","transfer(address,uint256)":"a9059cbb","transferFrom(address,address,uint256)":"23b872dd"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.28+commit.7893614a\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_name\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"_symbol\",\"type\":\"string\"},{\"internalType\":\"uint8\",\"name\":\"_decimals\",\"type\":\"uint8\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"mint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"/private/var/folders/7n/tm66gd7150xgn977n2g_c9yr0000gn/T/tmp.1VUVHm6ogJ/src/TestToken.sol\":\"TestToken\"},\"evmVersion\":\"prague\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[]},\"sources\":{\"/private/var/folders/7n/tm66gd7150xgn977n2g_c9yr0000gn/T/tmp.1VUVHm6ogJ/src/TestToken.sol\":{\"keccak256\":\"0x5851c1afe5c7cf16abc4e5b939a737eb190136936de71e0a10718d2de5197047\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://0dc4d4e5c299175809d017b991ba8566c32c51e60e4bb0b82d6de2dfde3ed77c\",\"dweb:/ipfs/Qmc3gMT2Nxvfx1Eu2XNtAKStMBFEmLK7KaprRCscdVVDfZ\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.28+commit.7893614a"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"string","name":"_symbol","type":"string"},{"internalType":"uint8","name":"_decimals","type":"uint8"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"owner","type":"address","indexed":true},{"internalType":"address","name":"spender","type":"address","indexed":true},{"internalType":"uint256","name":"value","type":"uint256","indexed":false}],"type":"event","name":"Approval","anonymous":false},{"inputs":[{"internalType":"address","name":"from","type":"address","indexed":true},{"internalType":"address","name":"to","type":"address","indexed":true},{"internalType":"uint256","name":"value","type":"uint256","indexed":false}],"type":"event","name":"Transfer","anonymous":false},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function","name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"nonpayable","type":"function","name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}]},{"inputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function","name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}]},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"nonpayable","type":"function","name":"mint"},{"inputs":[],"stateMutability":"view","type":"function","name":"name","outputs":[{"internalType":"string","name":"","type":"string"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"nonpayable","type":"function","name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}]},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"nonpayable","type":"function","name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}]}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":[],"optimizer":{"enabled":false,"runs":200},"metadata":{"bytecodeHash":"ipfs"},"compilationTarget":{"/private/var/folders/7n/tm66gd7150xgn977n2g_c9yr0000gn/T/tmp.1VUVHm6ogJ/src/TestToken.sol":"TestToken"},"evmVersion":"prague","libraries":{}},"sources":{"/private/var/folders/7n/tm66gd7150xgn977n2g_c9yr0000gn/T/tmp.1VUVHm6ogJ/src/TestToken.sol":{"keccak256":"0x5851c1afe5c7cf16abc4e5b939a737eb190136936de71e0a10718d2de5197047","urls":["bzz-raw://0dc4d4e5c299175809d017b991ba8566c32c51e60e4bb0b82d6de2dfde3ed77c","dweb:/ipfs/Qmc3gMT2Nxvfx1Eu2XNtAKStMBFEmLK7KaprRCscdVVDfZ"],"license":"MIT"}},"version":1},"id":0} \ No newline at end of file diff --git a/out/build-info/72664b54bf1f6a85.json b/out/build-info/72664b54bf1f6a85.json new file mode 100644 index 0000000..af362b5 --- /dev/null +++ b/out/build-info/72664b54bf1f6a85.json @@ -0,0 +1 @@ +{"id":"72664b54bf1f6a85","source_id_to_path":{"0":"/private/var/folders/7n/tm66gd7150xgn977n2g_c9yr0000gn/T/tmp.1VUVHm6ogJ/src/TestToken.sol"},"language":"Solidity"} \ No newline at end of file diff --git a/out/build-info/796149271a07a4e7.json b/out/build-info/796149271a07a4e7.json new file mode 100644 index 0000000..26e7786 --- /dev/null +++ b/out/build-info/796149271a07a4e7.json @@ -0,0 +1 @@ +{"id":"796149271a07a4e7","source_id_to_path":{"0":"/private/var/folders/7n/tm66gd7150xgn977n2g_c9yr0000gn/T/tmp.1hxrgibVb0/src/TestToken.sol"},"language":"Solidity"} \ No newline at end of file diff --git a/out/src/TestToken.sol/TestToken.json b/out/src/TestToken.sol/TestToken.json new file mode 100644 index 0000000..fc64d5e --- /dev/null +++ b/out/src/TestToken.sol/TestToken.json @@ -0,0 +1 @@ +{"abi":[{"type":"constructor","inputs":[{"name":"_name","type":"string","internalType":"string"},{"name":"_symbol","type":"string","internalType":"string"},{"name":"_decimals","type":"uint8","internalType":"uint8"}],"stateMutability":"nonpayable"},{"type":"function","name":"allowance","inputs":[{"name":"","type":"address","internalType":"address"},{"name":"","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"approve","inputs":[{"name":"spender","type":"address","internalType":"address"},{"name":"amount","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"nonpayable"},{"type":"function","name":"balanceOf","inputs":[{"name":"","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"decimals","inputs":[],"outputs":[{"name":"","type":"uint8","internalType":"uint8"}],"stateMutability":"view"},{"type":"function","name":"mint","inputs":[{"name":"to","type":"address","internalType":"address"},{"name":"amount","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"name","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"type":"function","name":"symbol","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"type":"function","name":"totalSupply","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"transfer","inputs":[{"name":"to","type":"address","internalType":"address"},{"name":"amount","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"nonpayable"},{"type":"function","name":"transferFrom","inputs":[{"name":"from","type":"address","internalType":"address"},{"name":"to","type":"address","internalType":"address"},{"name":"amount","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"nonpayable"},{"type":"event","name":"Approval","inputs":[{"name":"owner","type":"address","indexed":true,"internalType":"address"},{"name":"spender","type":"address","indexed":true,"internalType":"address"},{"name":"value","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"Transfer","inputs":[{"name":"from","type":"address","indexed":true,"internalType":"address"},{"name":"to","type":"address","indexed":true,"internalType":"address"},{"name":"value","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false}],"bytecode":{"object":"0x608060405234801561000f575f5ffd5b506040516113c53803806113c5833981810160405281019061003191906101f5565b825f908161003f919061048d565b50816001908161004f919061048d565b508060025f6101000a81548160ff021916908360ff16021790555050505061055c565b5f604051905090565b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6100d18261008b565b810181811067ffffffffffffffff821117156100f0576100ef61009b565b5b80604052505050565b5f610102610072565b905061010e82826100c8565b919050565b5f67ffffffffffffffff82111561012d5761012c61009b565b5b6101368261008b565b9050602081019050919050565b8281835e5f83830152505050565b5f61016361015e84610113565b6100f9565b90508281526020810184848401111561017f5761017e610087565b5b61018a848285610143565b509392505050565b5f82601f8301126101a6576101a5610083565b5b81516101b6848260208601610151565b91505092915050565b5f60ff82169050919050565b6101d4816101bf565b81146101de575f5ffd5b50565b5f815190506101ef816101cb565b92915050565b5f5f5f6060848603121561020c5761020b61007b565b5b5f84015167ffffffffffffffff8111156102295761022861007f565b5b61023586828701610192565b935050602084015167ffffffffffffffff8111156102565761025561007f565b5b61026286828701610192565b9250506040610273868287016101e1565b9150509250925092565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806102cb57607f821691505b6020821081036102de576102dd610287565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f600883026103407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82610305565b61034a8683610305565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f61038e61038961038484610362565b61036b565b610362565b9050919050565b5f819050919050565b6103a783610374565b6103bb6103b382610395565b848454610311565b825550505050565b5f5f905090565b6103d26103c3565b6103dd81848461039e565b505050565b5b81811015610400576103f55f826103ca565b6001810190506103e3565b5050565b601f82111561044557610416816102e4565b61041f846102f6565b8101602085101561042e578190505b61044261043a856102f6565b8301826103e2565b50505b505050565b5f82821c905092915050565b5f6104655f198460080261044a565b1980831691505092915050565b5f61047d8383610456565b9150826002028217905092915050565b6104968261027d565b67ffffffffffffffff8111156104af576104ae61009b565b5b6104b982546102b4565b6104c4828285610404565b5f60209050601f8311600181146104f5575f84156104e3578287015190505b6104ed8582610472565b865550610554565b601f198416610503866102e4565b5f5b8281101561052a57848901518255600182019150602085019450602081019050610505565b868310156105475784890151610543601f891682610456565b8355505b6001600288020188555050505b505050505050565b610e5c806105695f395ff3fe608060405234801561000f575f5ffd5b506004361061009c575f3560e01c806340c10f191161006457806340c10f191461015a57806370a082311461017657806395d89b41146101a6578063a9059cbb146101c4578063dd62ed3e146101f45761009c565b806306fdde03146100a0578063095ea7b3146100be57806318160ddd146100ee57806323b872dd1461010c578063313ce5671461013c575b5f5ffd5b6100a8610224565b6040516100b59190610a2f565b60405180910390f35b6100d860048036038101906100d39190610ae0565b6102af565b6040516100e59190610b38565b60405180910390f35b6100f661039c565b6040516101039190610b60565b60405180910390f35b61012660048036038101906101219190610b79565b6103a2565b6040516101339190610b38565b60405180910390f35b610144610682565b6040516101519190610be4565b60405180910390f35b610174600480360381019061016f9190610ae0565b610694565b005b610190600480360381019061018b9190610bfd565b610768565b60405161019d9190610b60565b60405180910390f35b6101ae61077d565b6040516101bb9190610a2f565b60405180910390f35b6101de60048036038101906101d99190610ae0565b610809565b6040516101eb9190610b38565b60405180910390f35b61020e60048036038101906102099190610c28565b61099f565b60405161021b9190610b60565b60405180910390f35b5f805461023090610c93565b80601f016020809104026020016040519081016040528092919081815260200182805461025c90610c93565b80156102a75780601f1061027e576101008083540402835291602001916102a7565b820191905f5260205f20905b81548152906001019060200180831161028a57829003601f168201915b505050505081565b5f8160055f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258460405161038a9190610b60565b60405180910390a36001905092915050565b60035481565b5f8160055f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054101561045e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161045590610d0d565b60405180910390fd5b8160045f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205410156104de576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d590610d75565b60405180910390fd5b8160055f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546105659190610dc0565b925050819055508160045f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546105b89190610dc0565b925050819055508160045f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825461060b9190610df3565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161066f9190610b60565b60405180910390a3600190509392505050565b60025f9054906101000a900460ff1681565b8060035f8282546106a59190610df3565b925050819055508060045f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546106f89190610df3565b925050819055508173ffffffffffffffffffffffffffffffffffffffff165f73ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8360405161075c9190610b60565b60405180910390a35050565b6004602052805f5260405f205f915090505481565b6001805461078a90610c93565b80601f01602080910402602001604051908101604052809291908181526020018280546107b690610c93565b80156108015780601f106107d857610100808354040283529160200191610801565b820191905f5260205f20905b8154815290600101906020018083116107e457829003601f168201915b505050505081565b5f8160045f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054101561088a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161088190610d75565b60405180910390fd5b8160045f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546108d69190610dc0565b925050819055508160045f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546109299190610df3565b925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161098d9190610b60565b60405180910390a36001905092915050565b6005602052815f5260405f20602052805f5260405f205f91509150505481565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f610a01826109bf565b610a0b81856109c9565b9350610a1b8185602086016109d9565b610a24816109e7565b840191505092915050565b5f6020820190508181035f830152610a4781846109f7565b905092915050565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610a7c82610a53565b9050919050565b610a8c81610a72565b8114610a96575f5ffd5b50565b5f81359050610aa781610a83565b92915050565b5f819050919050565b610abf81610aad565b8114610ac9575f5ffd5b50565b5f81359050610ada81610ab6565b92915050565b5f5f60408385031215610af657610af5610a4f565b5b5f610b0385828601610a99565b9250506020610b1485828601610acc565b9150509250929050565b5f8115159050919050565b610b3281610b1e565b82525050565b5f602082019050610b4b5f830184610b29565b92915050565b610b5a81610aad565b82525050565b5f602082019050610b735f830184610b51565b92915050565b5f5f5f60608486031215610b9057610b8f610a4f565b5b5f610b9d86828701610a99565b9350506020610bae86828701610a99565b9250506040610bbf86828701610acc565b9150509250925092565b5f60ff82169050919050565b610bde81610bc9565b82525050565b5f602082019050610bf75f830184610bd5565b92915050565b5f60208284031215610c1257610c11610a4f565b5b5f610c1f84828501610a99565b91505092915050565b5f5f60408385031215610c3e57610c3d610a4f565b5b5f610c4b85828601610a99565b9250506020610c5c85828601610a99565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680610caa57607f821691505b602082108103610cbd57610cbc610c66565b5b50919050565b7f6e6f7420617070726f76656400000000000000000000000000000000000000005f82015250565b5f610cf7600c836109c9565b9150610d0282610cc3565b602082019050919050565b5f6020820190508181035f830152610d2481610ceb565b9050919050565b7f696e73756666696369656e7400000000000000000000000000000000000000005f82015250565b5f610d5f600c836109c9565b9150610d6a82610d2b565b602082019050919050565b5f6020820190508181035f830152610d8c81610d53565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610dca82610aad565b9150610dd583610aad565b9250828203905081811115610ded57610dec610d93565b5b92915050565b5f610dfd82610aad565b9150610e0883610aad565b9250828201905080821115610e2057610e1f610d93565b5b9291505056fea2646970667358221220632f63e84dc375233b712c45ce409d2e92177e58fb172a7d9678db215d42d66f64736f6c634300081c0033","sourceMap":"59:1661:0:-:0;;;475:158;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;565:5;558:4;:12;;;;;;:::i;:::-;;589:7;580:6;:16;;;;;;:::i;:::-;;617:9;606:8;;:20;;;;;;;;;;;;;;;;;;475:158;;;59:1661;;7:75:1;40:6;73:2;67:9;57:19;;7:75;:::o;88:117::-;197:1;194;187:12;211:117;320:1;317;310:12;334:117;443:1;440;433:12;457:117;566:1;563;556:12;580:102;621:6;672:2;668:7;663:2;656:5;652:14;648:28;638:38;;580:102;;;:::o;688:180::-;736:77;733:1;726:88;833:4;830:1;823:15;857:4;854:1;847:15;874:281;957:27;979:4;957:27;:::i;:::-;949:6;945:40;1087:6;1075:10;1072:22;1051:18;1039:10;1036:34;1033:62;1030:88;;;1098:18;;:::i;:::-;1030:88;1138:10;1134:2;1127:22;917:238;874:281;;:::o;1161:129::-;1195:6;1222:20;;:::i;:::-;1212:30;;1251:33;1279:4;1271:6;1251:33;:::i;:::-;1161:129;;;:::o;1296:308::-;1358:4;1448:18;1440:6;1437:30;1434:56;;;1470:18;;:::i;:::-;1434:56;1508:29;1530:6;1508:29;:::i;:::-;1500:37;;1592:4;1586;1582:15;1574:23;;1296:308;;;:::o;1610:139::-;1699:6;1694:3;1689;1683:23;1740:1;1731:6;1726:3;1722:16;1715:27;1610:139;;;:::o;1755:434::-;1844:5;1869:66;1885:49;1927:6;1885:49;:::i;:::-;1869:66;:::i;:::-;1860:75;;1958:6;1951:5;1944:21;1996:4;1989:5;1985:16;2034:3;2025:6;2020:3;2016:16;2013:25;2010:112;;;2041:79;;:::i;:::-;2010:112;2131:52;2176:6;2171:3;2166;2131:52;:::i;:::-;1850:339;1755:434;;;;;:::o;2209:355::-;2276:5;2325:3;2318:4;2310:6;2306:17;2302:27;2292:122;;2333:79;;:::i;:::-;2292:122;2443:6;2437:13;2468:90;2554:3;2546:6;2539:4;2531:6;2527:17;2468:90;:::i;:::-;2459:99;;2282:282;2209:355;;;;:::o;2570:86::-;2605:7;2645:4;2638:5;2634:16;2623:27;;2570:86;;;:::o;2662:118::-;2733:22;2749:5;2733:22;:::i;:::-;2726:5;2723:33;2713:61;;2770:1;2767;2760:12;2713:61;2662:118;:::o;2786:139::-;2841:5;2872:6;2866:13;2857:22;;2888:31;2913:5;2888:31;:::i;:::-;2786:139;;;;:::o;2931:1005::-;3037:6;3045;3053;3102:2;3090:9;3081:7;3077:23;3073:32;3070:119;;;3108:79;;:::i;:::-;3070:119;3249:1;3238:9;3234:17;3228:24;3279:18;3271:6;3268:30;3265:117;;;3301:79;;:::i;:::-;3265:117;3406:74;3472:7;3463:6;3452:9;3448:22;3406:74;:::i;:::-;3396:84;;3199:291;3550:2;3539:9;3535:18;3529:25;3581:18;3573:6;3570:30;3567:117;;;3603:79;;:::i;:::-;3567:117;3708:74;3774:7;3765:6;3754:9;3750:22;3708:74;:::i;:::-;3698:84;;3500:292;3831:2;3857:62;3911:7;3902:6;3891:9;3887:22;3857:62;:::i;:::-;3847:72;;3802:127;2931:1005;;;;;:::o;3942:99::-;3994:6;4028:5;4022:12;4012:22;;3942:99;;;:::o;4047:180::-;4095:77;4092:1;4085:88;4192:4;4189:1;4182:15;4216:4;4213:1;4206:15;4233:320;4277:6;4314:1;4308:4;4304:12;4294:22;;4361:1;4355:4;4351:12;4382:18;4372:81;;4438:4;4430:6;4426:17;4416:27;;4372:81;4500:2;4492:6;4489:14;4469:18;4466:38;4463:84;;4519:18;;:::i;:::-;4463:84;4284:269;4233:320;;;:::o;4559:141::-;4608:4;4631:3;4623:11;;4654:3;4651:1;4644:14;4688:4;4685:1;4675:18;4667:26;;4559:141;;;:::o;4706:93::-;4743:6;4790:2;4785;4778:5;4774:14;4770:23;4760:33;;4706:93;;;:::o;4805:107::-;4849:8;4899:5;4893:4;4889:16;4868:37;;4805:107;;;;:::o;4918:393::-;4987:6;5037:1;5025:10;5021:18;5060:97;5090:66;5079:9;5060:97;:::i;:::-;5178:39;5208:8;5197:9;5178:39;:::i;:::-;5166:51;;5250:4;5246:9;5239:5;5235:21;5226:30;;5299:4;5289:8;5285:19;5278:5;5275:30;5265:40;;4994:317;;4918:393;;;;;:::o;5317:77::-;5354:7;5383:5;5372:16;;5317:77;;;:::o;5400:60::-;5428:3;5449:5;5442:12;;5400:60;;;:::o;5466:142::-;5516:9;5549:53;5567:34;5576:24;5594:5;5576:24;:::i;:::-;5567:34;:::i;:::-;5549:53;:::i;:::-;5536:66;;5466:142;;;:::o;5614:75::-;5657:3;5678:5;5671:12;;5614:75;;;:::o;5695:269::-;5805:39;5836:7;5805:39;:::i;:::-;5866:91;5915:41;5939:16;5915:41;:::i;:::-;5907:6;5900:4;5894:11;5866:91;:::i;:::-;5860:4;5853:105;5771:193;5695:269;;;:::o;5970:73::-;6015:3;6036:1;6029:8;;5970:73;:::o;6049:189::-;6126:32;;:::i;:::-;6167:65;6225:6;6217;6211:4;6167:65;:::i;:::-;6102:136;6049:189;;:::o;6244:186::-;6304:120;6321:3;6314:5;6311:14;6304:120;;;6375:39;6412:1;6405:5;6375:39;:::i;:::-;6348:1;6341:5;6337:13;6328:22;;6304:120;;;6244:186;;:::o;6436:543::-;6537:2;6532:3;6529:11;6526:446;;;6571:38;6603:5;6571:38;:::i;:::-;6655:29;6673:10;6655:29;:::i;:::-;6645:8;6641:44;6838:2;6826:10;6823:18;6820:49;;;6859:8;6844:23;;6820:49;6882:80;6938:22;6956:3;6938:22;:::i;:::-;6928:8;6924:37;6911:11;6882:80;:::i;:::-;6541:431;;6526:446;6436:543;;;:::o;6985:117::-;7039:8;7089:5;7083:4;7079:16;7058:37;;6985:117;;;;:::o;7108:169::-;7152:6;7185:51;7233:1;7229:6;7221:5;7218:1;7214:13;7185:51;:::i;:::-;7181:56;7266:4;7260;7256:15;7246:25;;7159:118;7108:169;;;;:::o;7282:295::-;7358:4;7504:29;7529:3;7523:4;7504:29;:::i;:::-;7496:37;;7566:3;7563:1;7559:11;7553:4;7550:21;7542:29;;7282:295;;;;:::o;7582:1395::-;7699:37;7732:3;7699:37;:::i;:::-;7801:18;7793:6;7790:30;7787:56;;;7823:18;;:::i;:::-;7787:56;7867:38;7899:4;7893:11;7867:38;:::i;:::-;7952:67;8012:6;8004;7998:4;7952:67;:::i;:::-;8046:1;8070:4;8057:17;;8102:2;8094:6;8091:14;8119:1;8114:618;;;;8776:1;8793:6;8790:77;;;8842:9;8837:3;8833:19;8827:26;8818:35;;8790:77;8893:67;8953:6;8946:5;8893:67;:::i;:::-;8887:4;8880:81;8749:222;8084:887;;8114:618;8166:4;8162:9;8154:6;8150:22;8200:37;8232:4;8200:37;:::i;:::-;8259:1;8273:208;8287:7;8284:1;8281:14;8273:208;;;8366:9;8361:3;8357:19;8351:26;8343:6;8336:42;8417:1;8409:6;8405:14;8395:24;;8464:2;8453:9;8449:18;8436:31;;8310:4;8307:1;8303:12;8298:17;;8273:208;;;8509:6;8500:7;8497:19;8494:179;;;8567:9;8562:3;8558:19;8552:26;8610:48;8652:4;8644:6;8640:17;8629:9;8610:48;:::i;:::-;8602:6;8595:64;8517:156;8494:179;8719:1;8715;8707:6;8703:14;8699:22;8693:4;8686:36;8121:611;;;8084:887;;7674:1303;;;7582:1395;;:::o;59:1661:0:-;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x608060405234801561000f575f5ffd5b506004361061009c575f3560e01c806340c10f191161006457806340c10f191461015a57806370a082311461017657806395d89b41146101a6578063a9059cbb146101c4578063dd62ed3e146101f45761009c565b806306fdde03146100a0578063095ea7b3146100be57806318160ddd146100ee57806323b872dd1461010c578063313ce5671461013c575b5f5ffd5b6100a8610224565b6040516100b59190610a2f565b60405180910390f35b6100d860048036038101906100d39190610ae0565b6102af565b6040516100e59190610b38565b60405180910390f35b6100f661039c565b6040516101039190610b60565b60405180910390f35b61012660048036038101906101219190610b79565b6103a2565b6040516101339190610b38565b60405180910390f35b610144610682565b6040516101519190610be4565b60405180910390f35b610174600480360381019061016f9190610ae0565b610694565b005b610190600480360381019061018b9190610bfd565b610768565b60405161019d9190610b60565b60405180910390f35b6101ae61077d565b6040516101bb9190610a2f565b60405180910390f35b6101de60048036038101906101d99190610ae0565b610809565b6040516101eb9190610b38565b60405180910390f35b61020e60048036038101906102099190610c28565b61099f565b60405161021b9190610b60565b60405180910390f35b5f805461023090610c93565b80601f016020809104026020016040519081016040528092919081815260200182805461025c90610c93565b80156102a75780601f1061027e576101008083540402835291602001916102a7565b820191905f5260205f20905b81548152906001019060200180831161028a57829003601f168201915b505050505081565b5f8160055f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258460405161038a9190610b60565b60405180910390a36001905092915050565b60035481565b5f8160055f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054101561045e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161045590610d0d565b60405180910390fd5b8160045f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205410156104de576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d590610d75565b60405180910390fd5b8160055f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546105659190610dc0565b925050819055508160045f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546105b89190610dc0565b925050819055508160045f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825461060b9190610df3565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161066f9190610b60565b60405180910390a3600190509392505050565b60025f9054906101000a900460ff1681565b8060035f8282546106a59190610df3565b925050819055508060045f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546106f89190610df3565b925050819055508173ffffffffffffffffffffffffffffffffffffffff165f73ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8360405161075c9190610b60565b60405180910390a35050565b6004602052805f5260405f205f915090505481565b6001805461078a90610c93565b80601f01602080910402602001604051908101604052809291908181526020018280546107b690610c93565b80156108015780601f106107d857610100808354040283529160200191610801565b820191905f5260205f20905b8154815290600101906020018083116107e457829003601f168201915b505050505081565b5f8160045f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054101561088a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161088190610d75565b60405180910390fd5b8160045f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546108d69190610dc0565b925050819055508160045f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546109299190610df3565b925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161098d9190610b60565b60405180910390a36001905092915050565b6005602052815f5260405f20602052805f5260405f205f91509150505481565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f610a01826109bf565b610a0b81856109c9565b9350610a1b8185602086016109d9565b610a24816109e7565b840191505092915050565b5f6020820190508181035f830152610a4781846109f7565b905092915050565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610a7c82610a53565b9050919050565b610a8c81610a72565b8114610a96575f5ffd5b50565b5f81359050610aa781610a83565b92915050565b5f819050919050565b610abf81610aad565b8114610ac9575f5ffd5b50565b5f81359050610ada81610ab6565b92915050565b5f5f60408385031215610af657610af5610a4f565b5b5f610b0385828601610a99565b9250506020610b1485828601610acc565b9150509250929050565b5f8115159050919050565b610b3281610b1e565b82525050565b5f602082019050610b4b5f830184610b29565b92915050565b610b5a81610aad565b82525050565b5f602082019050610b735f830184610b51565b92915050565b5f5f5f60608486031215610b9057610b8f610a4f565b5b5f610b9d86828701610a99565b9350506020610bae86828701610a99565b9250506040610bbf86828701610acc565b9150509250925092565b5f60ff82169050919050565b610bde81610bc9565b82525050565b5f602082019050610bf75f830184610bd5565b92915050565b5f60208284031215610c1257610c11610a4f565b5b5f610c1f84828501610a99565b91505092915050565b5f5f60408385031215610c3e57610c3d610a4f565b5b5f610c4b85828601610a99565b9250506020610c5c85828601610a99565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680610caa57607f821691505b602082108103610cbd57610cbc610c66565b5b50919050565b7f6e6f7420617070726f76656400000000000000000000000000000000000000005f82015250565b5f610cf7600c836109c9565b9150610d0282610cc3565b602082019050919050565b5f6020820190508181035f830152610d2481610ceb565b9050919050565b7f696e73756666696369656e7400000000000000000000000000000000000000005f82015250565b5f610d5f600c836109c9565b9150610d6a82610d2b565b602082019050919050565b5f6020820190508181035f830152610d8c81610d53565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610dca82610aad565b9150610dd583610aad565b9250828203905081811115610ded57610dec610d93565b5b92915050565b5f610dfd82610aad565b9150610e0883610aad565b9250828201905080821115610e2057610e1f610d93565b5b9291505056fea2646970667358221220632f63e84dc375233b712c45ce409d2e92177e58fb172a7d9678db215d42d66f64736f6c634300081c0033","sourceMap":"59:1661:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;84:18;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;814:203;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;161:26;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;1314:404;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;134:21;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;639:169;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;193:44;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;108:20;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;1023:285;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;243:64;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;84:18;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;814:203::-;882:4;931:6;898:9;:21;908:10;898:21;;;;;;;;;;;;;;;:30;920:7;898:30;;;;;;;;;;;;;;;:39;;;;973:7;952:37;;961:10;952:37;;;982:6;952:37;;;;;;:::i;:::-;;;;;;;;1006:4;999:11;;814:203;;;;:::o;161:26::-;;;;:::o;1314:404::-;1396:4;1451:6;1420:9;:15;1430:4;1420:15;;;;;;;;;;;;;;;:27;1436:10;1420:27;;;;;;;;;;;;;;;;:37;;1412:62;;;;;;;;;;;;:::i;:::-;;;;;;;;;1511:6;1492:9;:15;1502:4;1492:15;;;;;;;;;;;;;;;;:25;;1484:50;;;;;;;;;;;;:::i;:::-;;;;;;;;;1575:6;1544:9;:15;1554:4;1544:15;;;;;;;;;;;;;;;:27;1560:10;1544:27;;;;;;;;;;;;;;;;:37;;;;;;;:::i;:::-;;;;;;;;1610:6;1591:9;:15;1601:4;1591:15;;;;;;;;;;;;;;;;:25;;;;;;;:::i;:::-;;;;;;;;1643:6;1626:9;:13;1636:2;1626:13;;;;;;;;;;;;;;;;:23;;;;;;;:::i;:::-;;;;;;;;1679:2;1664:26;;1673:4;1664:26;;;1683:6;1664:26;;;;;;:::i;:::-;;;;;;;;1707:4;1700:11;;1314:404;;;;;:::o;134:21::-;;;;;;;;;;;;;:::o;639:169::-;715:6;700:11;;:21;;;;;;;:::i;:::-;;;;;;;;748:6;731:9;:13;741:2;731:13;;;;;;;;;;;;;;;;:23;;;;;;;:::i;:::-;;;;;;;;790:2;769:32;;786:1;769:32;;;794:6;769:32;;;;;;:::i;:::-;;;;;;;;639:169;;:::o;193:44::-;;;;;;;;;;;;;;;;;:::o;108:20::-;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;1023:285::-;1087:4;1136:6;1111:9;:21;1121:10;1111:21;;;;;;;;;;;;;;;;:31;;1103:56;;;;;;;;;;;;:::i;:::-;;;;;;;;;1194:6;1169:9;:21;1179:10;1169:21;;;;;;;;;;;;;;;;:31;;;;;;;:::i;:::-;;;;;;;;1227:6;1210:9;:13;1220:2;1210:13;;;;;;;;;;;;;;;;:23;;;;;;;:::i;:::-;;;;;;;;1269:2;1248:32;;1257:10;1248:32;;;1273:6;1248:32;;;;;;:::i;:::-;;;;;;;;1297:4;1290:11;;1023:285;;;;:::o;243:64::-;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;7:99:1:-;59:6;93:5;87:12;77:22;;7:99;;;:::o;112:169::-;196:11;230:6;225:3;218:19;270:4;265:3;261:14;246:29;;112:169;;;;:::o;287:139::-;376:6;371:3;366;360:23;417:1;408:6;403:3;399:16;392:27;287:139;;;:::o;432:102::-;473:6;524:2;520:7;515:2;508:5;504:14;500:28;490:38;;432:102;;;:::o;540:377::-;628:3;656:39;689:5;656:39;:::i;:::-;711:71;775:6;770:3;711:71;:::i;:::-;704:78;;791:65;849:6;844:3;837:4;830:5;826:16;791:65;:::i;:::-;881:29;903:6;881:29;:::i;:::-;876:3;872:39;865:46;;632:285;540:377;;;;:::o;923:313::-;1036:4;1074:2;1063:9;1059:18;1051:26;;1123:9;1117:4;1113:20;1109:1;1098:9;1094:17;1087:47;1151:78;1224:4;1215:6;1151:78;:::i;:::-;1143:86;;923:313;;;;:::o;1323:117::-;1432:1;1429;1422:12;1569:126;1606:7;1646:42;1639:5;1635:54;1624:65;;1569:126;;;:::o;1701:96::-;1738:7;1767:24;1785:5;1767:24;:::i;:::-;1756:35;;1701:96;;;:::o;1803:122::-;1876:24;1894:5;1876:24;:::i;:::-;1869:5;1866:35;1856:63;;1915:1;1912;1905:12;1856:63;1803:122;:::o;1931:139::-;1977:5;2015:6;2002:20;1993:29;;2031:33;2058:5;2031:33;:::i;:::-;1931:139;;;;:::o;2076:77::-;2113:7;2142:5;2131:16;;2076:77;;;:::o;2159:122::-;2232:24;2250:5;2232:24;:::i;:::-;2225:5;2222:35;2212:63;;2271:1;2268;2261:12;2212:63;2159:122;:::o;2287:139::-;2333:5;2371:6;2358:20;2349:29;;2387:33;2414:5;2387:33;:::i;:::-;2287:139;;;;:::o;2432:474::-;2500:6;2508;2557:2;2545:9;2536:7;2532:23;2528:32;2525:119;;;2563:79;;:::i;:::-;2525:119;2683:1;2708:53;2753:7;2744:6;2733:9;2729:22;2708:53;:::i;:::-;2698:63;;2654:117;2810:2;2836:53;2881:7;2872:6;2861:9;2857:22;2836:53;:::i;:::-;2826:63;;2781:118;2432:474;;;;;:::o;2912:90::-;2946:7;2989:5;2982:13;2975:21;2964:32;;2912:90;;;:::o;3008:109::-;3089:21;3104:5;3089:21;:::i;:::-;3084:3;3077:34;3008:109;;:::o;3123:210::-;3210:4;3248:2;3237:9;3233:18;3225:26;;3261:65;3323:1;3312:9;3308:17;3299:6;3261:65;:::i;:::-;3123:210;;;;:::o;3339:118::-;3426:24;3444:5;3426:24;:::i;:::-;3421:3;3414:37;3339:118;;:::o;3463:222::-;3556:4;3594:2;3583:9;3579:18;3571:26;;3607:71;3675:1;3664:9;3660:17;3651:6;3607:71;:::i;:::-;3463:222;;;;:::o;3691:619::-;3768:6;3776;3784;3833:2;3821:9;3812:7;3808:23;3804:32;3801:119;;;3839:79;;:::i;:::-;3801:119;3959:1;3984:53;4029:7;4020:6;4009:9;4005:22;3984:53;:::i;:::-;3974:63;;3930:117;4086:2;4112:53;4157:7;4148:6;4137:9;4133:22;4112:53;:::i;:::-;4102:63;;4057:118;4214:2;4240:53;4285:7;4276:6;4265:9;4261:22;4240:53;:::i;:::-;4230:63;;4185:118;3691:619;;;;;:::o;4316:86::-;4351:7;4391:4;4384:5;4380:16;4369:27;;4316:86;;;:::o;4408:112::-;4491:22;4507:5;4491:22;:::i;:::-;4486:3;4479:35;4408:112;;:::o;4526:214::-;4615:4;4653:2;4642:9;4638:18;4630:26;;4666:67;4730:1;4719:9;4715:17;4706:6;4666:67;:::i;:::-;4526:214;;;;:::o;4746:329::-;4805:6;4854:2;4842:9;4833:7;4829:23;4825:32;4822:119;;;4860:79;;:::i;:::-;4822:119;4980:1;5005:53;5050:7;5041:6;5030:9;5026:22;5005:53;:::i;:::-;4995:63;;4951:117;4746:329;;;;:::o;5081:474::-;5149:6;5157;5206:2;5194:9;5185:7;5181:23;5177:32;5174:119;;;5212:79;;:::i;:::-;5174:119;5332:1;5357:53;5402:7;5393:6;5382:9;5378:22;5357:53;:::i;:::-;5347:63;;5303:117;5459:2;5485:53;5530:7;5521:6;5510:9;5506:22;5485:53;:::i;:::-;5475:63;;5430:118;5081:474;;;;;:::o;5561:180::-;5609:77;5606:1;5599:88;5706:4;5703:1;5696:15;5730:4;5727:1;5720:15;5747:320;5791:6;5828:1;5822:4;5818:12;5808:22;;5875:1;5869:4;5865:12;5896:18;5886:81;;5952:4;5944:6;5940:17;5930:27;;5886:81;6014:2;6006:6;6003:14;5983:18;5980:38;5977:84;;6033:18;;:::i;:::-;5977:84;5798:269;5747:320;;;:::o;6073:162::-;6213:14;6209:1;6201:6;6197:14;6190:38;6073:162;:::o;6241:366::-;6383:3;6404:67;6468:2;6463:3;6404:67;:::i;:::-;6397:74;;6480:93;6569:3;6480:93;:::i;:::-;6598:2;6593:3;6589:12;6582:19;;6241:366;;;:::o;6613:419::-;6779:4;6817:2;6806:9;6802:18;6794:26;;6866:9;6860:4;6856:20;6852:1;6841:9;6837:17;6830:47;6894:131;7020:4;6894:131;:::i;:::-;6886:139;;6613:419;;;:::o;7038:162::-;7178:14;7174:1;7166:6;7162:14;7155:38;7038:162;:::o;7206:366::-;7348:3;7369:67;7433:2;7428:3;7369:67;:::i;:::-;7362:74;;7445:93;7534:3;7445:93;:::i;:::-;7563:2;7558:3;7554:12;7547:19;;7206:366;;;:::o;7578:419::-;7744:4;7782:2;7771:9;7767:18;7759:26;;7831:9;7825:4;7821:20;7817:1;7806:9;7802:17;7795:47;7859:131;7985:4;7859:131;:::i;:::-;7851:139;;7578:419;;;:::o;8003:180::-;8051:77;8048:1;8041:88;8148:4;8145:1;8138:15;8172:4;8169:1;8162:15;8189:194;8229:4;8249:20;8267:1;8249:20;:::i;:::-;8244:25;;8283:20;8301:1;8283:20;:::i;:::-;8278:25;;8327:1;8324;8320:9;8312:17;;8351:1;8345:4;8342:11;8339:37;;;8356:18;;:::i;:::-;8339:37;8189:194;;;;:::o;8389:191::-;8429:3;8448:20;8466:1;8448:20;:::i;:::-;8443:25;;8482:20;8500:1;8482:20;:::i;:::-;8477:25;;8525:1;8522;8518:9;8511:16;;8546:3;8543:1;8540:10;8537:36;;;8553:18;;:::i;:::-;8537:36;8389:191;;;;:::o","linkReferences":{}},"methodIdentifiers":{"allowance(address,address)":"dd62ed3e","approve(address,uint256)":"095ea7b3","balanceOf(address)":"70a08231","decimals()":"313ce567","mint(address,uint256)":"40c10f19","name()":"06fdde03","symbol()":"95d89b41","totalSupply()":"18160ddd","transfer(address,uint256)":"a9059cbb","transferFrom(address,address,uint256)":"23b872dd"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.28+commit.7893614a\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_name\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"_symbol\",\"type\":\"string\"},{\"internalType\":\"uint8\",\"name\":\"_decimals\",\"type\":\"uint8\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"mint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"/private/var/folders/7n/tm66gd7150xgn977n2g_c9yr0000gn/T/tmp.1hxrgibVb0/src/TestToken.sol\":\"TestToken\"},\"evmVersion\":\"prague\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[]},\"sources\":{\"/private/var/folders/7n/tm66gd7150xgn977n2g_c9yr0000gn/T/tmp.1hxrgibVb0/src/TestToken.sol\":{\"keccak256\":\"0x5851c1afe5c7cf16abc4e5b939a737eb190136936de71e0a10718d2de5197047\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://0dc4d4e5c299175809d017b991ba8566c32c51e60e4bb0b82d6de2dfde3ed77c\",\"dweb:/ipfs/Qmc3gMT2Nxvfx1Eu2XNtAKStMBFEmLK7KaprRCscdVVDfZ\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.28+commit.7893614a"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"string","name":"_symbol","type":"string"},{"internalType":"uint8","name":"_decimals","type":"uint8"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"owner","type":"address","indexed":true},{"internalType":"address","name":"spender","type":"address","indexed":true},{"internalType":"uint256","name":"value","type":"uint256","indexed":false}],"type":"event","name":"Approval","anonymous":false},{"inputs":[{"internalType":"address","name":"from","type":"address","indexed":true},{"internalType":"address","name":"to","type":"address","indexed":true},{"internalType":"uint256","name":"value","type":"uint256","indexed":false}],"type":"event","name":"Transfer","anonymous":false},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function","name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"nonpayable","type":"function","name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}]},{"inputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function","name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}]},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"nonpayable","type":"function","name":"mint"},{"inputs":[],"stateMutability":"view","type":"function","name":"name","outputs":[{"internalType":"string","name":"","type":"string"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"nonpayable","type":"function","name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}]},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"nonpayable","type":"function","name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}]}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":[],"optimizer":{"enabled":false,"runs":200},"metadata":{"bytecodeHash":"ipfs"},"compilationTarget":{"/private/var/folders/7n/tm66gd7150xgn977n2g_c9yr0000gn/T/tmp.1hxrgibVb0/src/TestToken.sol":"TestToken"},"evmVersion":"prague","libraries":{}},"sources":{"/private/var/folders/7n/tm66gd7150xgn977n2g_c9yr0000gn/T/tmp.1hxrgibVb0/src/TestToken.sol":{"keccak256":"0x5851c1afe5c7cf16abc4e5b939a737eb190136936de71e0a10718d2de5197047","urls":["bzz-raw://0dc4d4e5c299175809d017b991ba8566c32c51e60e4bb0b82d6de2dfde3ed77c","dweb:/ipfs/Qmc3gMT2Nxvfx1Eu2XNtAKStMBFEmLK7KaprRCscdVVDfZ"],"license":"MIT"}},"version":1},"id":0} \ No newline at end of file diff --git a/patches/001-ger-sync-retry.patch b/patches/001-ger-sync-retry.patch new file mode 100644 index 0000000..0e9158b --- /dev/null +++ b/patches/001-ger-sync-retry.patch @@ -0,0 +1,132 @@ +diff --git a/src/ger.rs b/src/ger.rs +index cabcd3e..dde40ea 100644 +--- a/src/ger.rs ++++ b/src/ger.rs +@@ -100,60 +100,85 @@ async fn submit_ger_to_miden( + ger_bytes: [u8; 32], + accounts: &AccountsConfig, + ) -> anyhow::Result<()> { +- let ger = ExitRoot::new(ger_bytes); + let service_id = accounts.service.0; + let bridge_id = accounts.bridge.0; + +- let note = UpdateGerNote::create(ger, service_id, bridge_id, client.rng())?; +- tracing::info!(note_id = %note.id(), "UpdateGerNote created"); ++ // Retry up to 3 times — concurrent claims can change the service account's ++ // state commitment between sync and TX submission. ++ for attempt in 0..3u32 { ++ // Always re-sync before building the TX so we have the latest account state. ++ client.sync_state().await?; + +- let tx_request = TransactionRequestBuilder::new() +- .own_output_notes(vec![OutputNote::Full(note)]) +- .build()?; ++ let ger = ExitRoot::new(ger_bytes); ++ let note = UpdateGerNote::create(ger, service_id, bridge_id, client.rng())?; ++ if attempt == 0 { ++ tracing::info!(note_id = %note.id(), "UpdateGerNote created"); ++ } + +- let tx_id = client +- .submit_new_transaction(service_id, tx_request) +- .await?; +- tracing::info!( +- tx_id = %tx_id, +- ger = %hex::encode(ger_bytes), +- "UpdateGerNote submitted to Miden node, waiting for commit..." +- ); ++ let tx_request = TransactionRequestBuilder::new() ++ .own_output_notes(vec![OutputNote::Full(note)]) ++ .build()?; ++ ++ let tx_id = match client.submit_new_transaction(service_id, tx_request).await { ++ Ok(id) => id, ++ Err(e) => { let e = anyhow::anyhow!("{e:#}"); ++ let msg = format!("{e:#}"); ++ if msg.contains("initial state commitment") && attempt < 2 { ++ tracing::warn!( ++ attempt, ++ "GER TX rejected (stale commitment), retrying after sync..." ++ ); ++ tokio::time::sleep(std::time::Duration::from_secs(2)).await; ++ continue; ++ } ++ return Err(anyhow::anyhow!("{e:#}")); ++ } ++ }; + +- // Poll for transaction commitment +- let timeout_secs: u64 = std::env::var("GER_COMMIT_TIMEOUT_SECS") +- .ok() +- .and_then(|v| v.parse().ok()) +- .unwrap_or(30) +- .clamp(5, 120); +- let mut committed = false; +- for _ in 0..timeout_secs { +- // We can check if txn is in the store and has a block number +- let txns = client +- .get_transactions(miden_client::store::TransactionFilter::All) +- .await?; +- if txns.iter().any(|t| { +- t.id == tx_id +- && matches!( +- t.status, +- miden_client::transaction::TransactionStatus::Committed { .. } +- ) +- }) { +- committed = true; +- break; ++ tracing::info!( ++ tx_id = %tx_id, ++ ger = %hex::encode(ger_bytes), ++ attempt, ++ "UpdateGerNote submitted to Miden node, waiting for commit..." ++ ); ++ ++ // Poll for transaction commitment ++ let timeout_secs: u64 = std::env::var("GER_COMMIT_TIMEOUT_SECS") ++ .ok() ++ .and_then(|v| v.parse().ok()) ++ .unwrap_or(30) ++ .clamp(5, 120); ++ let mut committed = false; ++ for _ in 0..timeout_secs { ++ let txns = client ++ .get_transactions(miden_client::store::TransactionFilter::All) ++ .await?; ++ if txns.iter().any(|t| { ++ t.id == tx_id ++ && matches!( ++ t.status, ++ miden_client::transaction::TransactionStatus::Committed { .. } ++ ) ++ }) { ++ committed = true; ++ break; ++ } ++ tokio::time::sleep(std::time::Duration::from_secs(1)).await; ++ client.sync_state().await?; + } +- tokio::time::sleep(std::time::Duration::from_secs(1)).await; +- client.sync_state().await?; // Sync to get latest updates +- } + +- if !committed { +- anyhow::bail!("UpdateGerNote transaction {tx_id} not committed after {timeout_secs}s"); ++ if !committed { ++ anyhow::bail!("UpdateGerNote transaction {tx_id} not committed after {timeout_secs}s"); ++ } ++ ++ tracing::info!(tx_id = %tx_id, "UpdateGerNote transaction committed"); ++ return Ok(()); + } + +- tracing::info!(tx_id = %tx_id, "UpdateGerNote transaction committed"); +- Ok(()) ++ anyhow::bail!("GER injection failed after 3 attempts due to stale commitment") + } + ++ + #[allow(clippy::too_many_arguments)] + pub async fn insert_ger( + ger_bytes: [u8; 32], diff --git a/patches/002-claim-debug-logging.patch b/patches/002-claim-debug-logging.patch new file mode 100644 index 0000000..b2fe543 --- /dev/null +++ b/patches/002-claim-debug-logging.patch @@ -0,0 +1,130 @@ +diff --git a/src/service_send_raw_txn.rs b/src/service_send_raw_txn.rs +index 2a2f77a..9c90ac0 100644 +--- a/src/service_send_raw_txn.rs ++++ b/src/service_send_raw_txn.rs +@@ -151,13 +151,31 @@ async fn handle_claim_asset( + } + + // Lock the claim index. All error paths after this MUST unclaim. ++ tracing::info!( ++ global_index = %params.globalIndex, ++ origin_token = %params.originTokenAddress, ++ amount = %params.amount, ++ "handle_claim_asset: try_claim lock acquired, calling publish_and_record_claim" ++ ); + service.store.try_claim(params.globalIndex).await?; + let result = + publish_and_record_claim(service, params.clone(), txn_hash, txn_envelope, signer).await; +- if let Err(err) = result { +- let _ = service.store.unclaim(¶ms.globalIndex).await; +- tracing::error!("claim failed after lock: {err:#?}"); +- return Err(err); ++ match &result { ++ Ok(()) => { ++ tracing::info!( ++ global_index = %params.globalIndex, ++ "handle_claim_asset: publish_and_record_claim SUCCEEDED" ++ ); ++ } ++ Err(err) => { ++ tracing::error!( ++ global_index = %params.globalIndex, ++ error = %err, ++ "handle_claim_asset: publish_and_record_claim FAILED, unclaiming" ++ ); ++ let _ = service.store.unclaim(¶ms.globalIndex).await; ++ return Err(anyhow::anyhow!("{err:#}")); ++ } + } + + service +@@ -178,14 +196,37 @@ async fn publish_and_record_claim( + txn_envelope: TxEnvelope, + signer: Address, + ) -> anyhow::Result<()> { +- let claim_result = claim::publish_claim( +- params, ++ tracing::info!( ++ global_index = %params.globalIndex, ++ origin_token = %params.originTokenAddress, ++ "publish_and_record_claim: calling publish_claim" ++ ); ++ let latest_block = service.store.get_latest_block_number().await?; ++ let claim_result = match claim::publish_claim( ++ params.clone(), + &service.miden_client, + service.accounts.clone(), + service.store.clone(), +- service.store.get_latest_block_number().await?, ++ latest_block, + ) +- .await?; ++ .await { ++ Ok(r) => { ++ tracing::info!( ++ global_index = %params.globalIndex, ++ miden_tx = %r.txn_id, ++ "publish_and_record_claim: publish_claim SUCCEEDED" ++ ); ++ r ++ } ++ Err(e) => { ++ tracing::error!( ++ global_index = %params.globalIndex, ++ error = %e, ++ "publish_and_record_claim: publish_claim FAILED" ++ ); ++ return Err(e); ++ } ++ }; + + // Note: bridge-service will see this ClaimEvent from both ClaimTxManager and + // L2 sync, causing a duplicate key error. See fixtures/bridge-db-patch.sql. +@@ -193,7 +234,20 @@ async fn publish_and_record_claim( + tracing::info!("published claim with eth txn: {txn_hash}; miden txn: {txn_id}"); + let block_num = service.store.advance_block_number().await?; + let block_hash = service.block_state.get_block_hash(block_num); +- service ++ ++ let log_topics: Vec = claim_result.log.topics().iter().map(|t| format!("{t}")).collect(); ++ let log_data_hex = format!("{:x}", claim_result.log.data); ++ tracing::info!( ++ eth_tx = %txn_hash, ++ miden_tx = %txn_id, ++ block_num, ++ log_topics = ?log_topics, ++ log_data_len = log_data_hex.len(), ++ log_data_prefix = %&log_data_hex[..std::cmp::min(120, log_data_hex.len())], ++ "recording ClaimEvent in store" ++ ); ++ ++ if let Err(e) = service + .store + .txn_begin( + txn_hash, +@@ -205,11 +259,22 @@ async fn publish_and_record_claim( + logs: vec![claim_result.log], + }, + ) +- .await?; +- service ++ .await ++ { ++ tracing::error!(eth_tx = %txn_hash, error = %e, "txn_begin FAILED for ClaimEvent"); ++ return Err(e.into()); ++ } ++ ++ if let Err(e) = service + .store + .txn_commit(txn_hash, Ok(()), block_num, block_hash) +- .await?; ++ .await ++ { ++ tracing::error!(eth_tx = %txn_hash, error = %e, "txn_commit FAILED for ClaimEvent"); ++ return Err(e.into()); ++ } ++ ++ tracing::info!(eth_tx = %txn_hash, block_num, "ClaimEvent recorded successfully"); + Ok(()) + } + diff --git a/patches/003-claim-cancellation-debug.patch b/patches/003-claim-cancellation-debug.patch new file mode 100644 index 0000000..c2398b8 --- /dev/null +++ b/patches/003-claim-cancellation-debug.patch @@ -0,0 +1,551 @@ +diff --git a/src/claim.rs b/src/claim.rs +index 5104c2f..195cad9 100644 +--- a/src/claim.rs ++++ b/src/claim.rs +@@ -212,6 +212,14 @@ async fn publish_claim_internal( + store: &dyn Store, + latest_block_num: BlockNumber, + ) -> anyhow::Result { ++ let gi = format!("{}", params.globalIndex); ++ tracing::info!( ++ global_index = %gi, ++ "[publish_claim_internal] ENTER — inside MidenClient closure (lock held)" ++ ); ++ ++ tracing::info!(global_index = %gi, "[publish_claim_internal] step 1/8: find_or_create_faucet"); ++ let faucet_start = std::time::Instant::now(); + let faucet = find_or_create_faucet( + params.originTokenAddress, + params.originNetwork, +@@ -221,9 +229,15 @@ async fn publish_claim_internal( + accounts, + ) + .await?; ++ tracing::info!( ++ global_index = %gi, ++ elapsed_ms = faucet_start.elapsed().as_millis(), ++ faucet_id = %crate::accounts_config::AccountIdBech32(faucet.id), ++ "[publish_claim_internal] step 1/8: find_or_create_faucet DONE" ++ ); + + tracing::info!( +- global_index = %params.globalIndex, ++ global_index = %gi, + origin_network = %params.originNetwork, + dest_address = %params.destinationAddress, + amount = %params.amount, +@@ -233,34 +247,38 @@ async fn publish_claim_internal( + "creating CLAIM note" + ); + ++ tracing::info!(global_index = %gi, "[publish_claim_internal] step 2/8: create_claim"); + let claim_note = create_claim(params.clone(), faucet, accounts, store, client.rng()).await?; + let claim_note_id = claim_note.id().to_string(); ++ tracing::info!(global_index = %gi, claim_note_id = %claim_note_id, "[publish_claim_internal] step 2/8: create_claim DONE"); + + const EXPIRATION_DELTA: u16 = 10; + let expires_at = latest_block_num + EXPIRATION_DELTA as u64; + + // Wait for the NTX builder to consume the UpdateGerNote on the bridge account. +- // The CLAIM note's FPI calls assert_valid_ger which checks the bridge account's +- // GER storage. If we submit the CLAIM before the GER is stored, it will fail. +- // Typically the GER note is consumed within ~5s (2-3 blocks). We wait 5 cycles +- // of 3s (15s total) which gives the NTX builder plenty of time while keeping +- // the overall claim latency reasonable. +- tracing::info!("waiting for GER to propagate to bridge account before submitting CLAIM..."); ++ tracing::info!(global_index = %gi, "[publish_claim_internal] step 3/8: GER propagation wait (5 x 3s = 15s)"); + for i in 0..5 { + tokio::time::sleep(std::time::Duration::from_secs(3)).await; + client.sync_state().await?; +- tracing::debug!(cycle = i, "GER propagation sync cycle"); ++ tracing::info!(global_index = %gi, cycle = i, "[publish_claim_internal] GER propagation sync cycle done"); + } +- tracing::info!("GER propagation wait complete, submitting CLAIM note"); ++ tracing::info!(global_index = %gi, "[publish_claim_internal] step 3/8: GER propagation wait DONE"); + ++ tracing::info!(global_index = %gi, "[publish_claim_internal] step 4/8: execute_transaction"); ++ let exec_start = std::time::Instant::now(); + let txn_request = TransactionRequestBuilder::new() + .own_output_notes([OutputNote::Full(claim_note); 1]) + .build()?; + +- // Execute and check the output notes before submission + let tx_result = client + .execute_transaction(accounts.service.0, txn_request) + .await?; ++ tracing::info!( ++ global_index = %gi, ++ elapsed_ms = exec_start.elapsed().as_millis(), ++ "[publish_claim_internal] step 4/8: execute_transaction DONE" ++ ); ++ + let exec_tx = tx_result.executed_transaction(); + for (i, note) in exec_tx.output_notes().iter().enumerate() { + let variant = match note { +@@ -275,7 +293,15 @@ async fn publish_claim_internal( + tracing::info!(note_idx = i, variant = %variant, "executed tx output note"); + } + ++ tracing::info!(global_index = %gi, "[publish_claim_internal] step 5/8: prove_transaction"); ++ let prove_start = std::time::Instant::now(); + let proven_tx = client.prove_transaction(&tx_result).await?; ++ tracing::info!( ++ global_index = %gi, ++ elapsed_ms = prove_start.elapsed().as_millis(), ++ "[publish_claim_internal] step 5/8: prove_transaction DONE" ++ ); ++ + for (i, note) in proven_tx.output_notes().iter().enumerate() { + let variant = match note { + miden_protocol::transaction::OutputNote::Full(n) => { +@@ -289,6 +315,8 @@ async fn publish_claim_internal( + tracing::info!(note_idx = i, variant = %variant, "proven tx output note"); + } + ++ tracing::info!(global_index = %gi, "[publish_claim_internal] step 6/8: submit_proven_transaction"); ++ let submit_start = std::time::Instant::now(); + let txn_id = tx_result.executed_transaction().id(); + let _submission_height = client + .submit_proven_transaction(proven_tx, &tx_result) +@@ -296,8 +324,15 @@ async fn publish_claim_internal( + client + .apply_transaction(&tx_result, _submission_height) + .await?; +- tracing::info!("submitted claim note txn: {txn_id}, claim_note_id: {claim_note_id}"); ++ tracing::info!( ++ global_index = %gi, ++ elapsed_ms = submit_start.elapsed().as_millis(), ++ txn_id = %txn_id, ++ claim_note_id = %claim_note_id, ++ "[publish_claim_internal] step 6/8: submit_proven_transaction DONE" ++ ); + ++ tracing::info!(global_index = %gi, "[publish_claim_internal] step 7/8: wait_for_transaction_commit (up to 20s)"); + let committed = crate::miden_client::wait_for_transaction_commit( + client, + txn_id, +@@ -306,14 +341,17 @@ async fn publish_claim_internal( + ) + .await?; + if committed { +- tracing::info!("claim tx {txn_id} committed to block"); ++ tracing::info!(global_index = %gi, txn_id = %txn_id, "[publish_claim_internal] step 7/8: transaction COMMITTED"); + } else { ++ tracing::error!(global_index = %gi, txn_id = %txn_id, "[publish_claim_internal] step 7/8: transaction NOT committed in 20s"); + anyhow::bail!("claim tx {txn_id} was submitted but not committed within 20s"); + } + ++ tracing::info!(global_index = %gi, "[publish_claim_internal] step 8/8: encoding ClaimEvent and returning"); + let event = ClaimEvent::from(params); + let log = event.encode_log_data(); + ++ tracing::info!(global_index = %gi, "[publish_claim_internal] EXIT — returning PublishClaimTxn to caller"); + Ok(PublishClaimTxn { + txn_id, + expires_at, +@@ -329,21 +367,44 @@ pub async fn publish_claim( + store: Arc, + latest_block_num: BlockNumber, + ) -> anyhow::Result { ++ let gi = format!("{}", params.globalIndex); ++ tracing::info!( ++ global_index = %gi, ++ "[publish_claim] ENTER — about to call MidenClient::with (will queue on channel)" ++ ); ++ let overall_start = std::time::Instant::now(); ++ + let result = Arc::new(OnceLock::::new()); + let result_inner = result.clone(); ++ let gi_inner = gi.clone(); + + client + .with(move |client| { + Box::new(async move { ++ tracing::info!( ++ global_index = %gi_inner, ++ "[publish_claim] MidenClient closure START — lock acquired" ++ ); + let value = + publish_claim_internal(params, client, &accounts.0, &*store, latest_block_num) + .await?; ++ tracing::info!( ++ global_index = %gi_inner, ++ txn_id = %value.txn_id, ++ "[publish_claim] MidenClient closure END — setting result" ++ ); + let _ = result_inner.set(value); + Ok(()) + }) + }) + .await?; + ++ tracing::info!( ++ global_index = %gi, ++ elapsed_ms = overall_start.elapsed().as_millis(), ++ "[publish_claim] EXIT — MidenClient::with returned" ++ ); ++ + result + .get() + .cloned() +diff --git a/src/miden_client.rs b/src/miden_client.rs +index f9d2888..4845f9d 100644 +--- a/src/miden_client.rs ++++ b/src/miden_client.rs +@@ -283,8 +283,25 @@ impl MidenClient { + tokio::select! { + receiver_result = receiver.recv() => { + let Some(request) = receiver_result else { break }; ++ let closure_start = std::time::Instant::now(); + let result = (request.closure)(&mut client).await; +- request.response_sender.send(result).unwrap_or(()); ++ let closure_elapsed = closure_start.elapsed(); ++ let is_ok = result.is_ok(); ++ if request.response_sender.send(result).is_err() { ++ tracing::error!( ++ elapsed_ms = closure_elapsed.as_millis(), ++ success = is_ok, ++ "!!!! MidenClient: closure completed but CALLER IS GONE (response_sender dropped). \ ++ The caller's future was cancelled while the closure was running. \ ++ Any post-closure work (like recording ClaimEvent) will NOT happen." ++ ); ++ } else { ++ tracing::debug!( ++ elapsed_ms = closure_elapsed.as_millis(), ++ success = is_ok, ++ "MidenClient: closure completed and result delivered to caller" ++ ); ++ } + }, + _ = sync_interval.tick() => { + tokio::select! { +@@ -310,6 +327,8 @@ impl MidenClient { + { + let (response_sender, response_receiver) = oneshot::channel::>(); + ++ tracing::debug!("MidenClient::with: about to send request to channel (capacity=1, may block if another op is running)"); ++ let send_start = std::time::Instant::now(); + let request = Request { + response_sender, + closure: Box::new(|client| Box::into_pin(closure(client))), +@@ -317,10 +336,30 @@ impl MidenClient { + if self.sender.send(request).await.is_err() { + anyhow::bail!("MidenClient::with: failed to queue a request - receiver is closed"); + } ++ let send_elapsed = send_start.elapsed(); ++ if send_elapsed > Duration::from_millis(100) { ++ tracing::warn!( ++ elapsed_ms = send_elapsed.as_millis(), ++ "MidenClient::with: channel send took {send_elapsed:?} (was blocked waiting for previous op to complete)" ++ ); ++ } else { ++ tracing::debug!( ++ elapsed_ms = send_elapsed.as_millis(), ++ "MidenClient::with: channel send completed in {send_elapsed:?}" ++ ); ++ } + ++ tracing::debug!("MidenClient::with: waiting for closure execution result..."); ++ let wait_start = std::time::Instant::now(); + let Ok(result) = response_receiver.await else { + anyhow::bail!("MidenClient::with: failed to get a response - receiver is closed"); + }; ++ let wait_elapsed = wait_start.elapsed(); ++ tracing::info!( ++ elapsed_ms = wait_elapsed.as_millis(), ++ success = result.is_ok(), ++ "MidenClient::with: closure completed in {wait_elapsed:?}" ++ ); + result + } + } +diff --git a/src/service_send_raw_txn.rs b/src/service_send_raw_txn.rs +index 2a2f77a..8c54ee9 100644 +--- a/src/service_send_raw_txn.rs ++++ b/src/service_send_raw_txn.rs +@@ -9,6 +9,47 @@ use alloy::consensus::transaction::SignerRecoverable; + use alloy::eips::Decodable2718; + use alloy::primitives::{Address, LogData, TxHash}; + use alloy_core::sol_types::SolCall; ++use std::sync::atomic::{AtomicU64, Ordering}; ++ ++/// Monotonic request counter for correlating logs across the claim lifecycle. ++static REQUEST_COUNTER: AtomicU64 = AtomicU64::new(1); ++ ++/// Drop guard that logs when a claim handler future is dropped (cancelled). ++/// This is the KEY diagnostic: if we see "DROPPED" without a preceding ++/// "SUCCEEDED" or "FAILED", the HTTP client disconnected mid-flight. ++struct ClaimDropGuard { ++ req_id: u64, ++ global_index: String, ++ completed: bool, ++} ++ ++impl ClaimDropGuard { ++ fn new(req_id: u64, global_index: &str) -> Self { ++ Self { ++ req_id, ++ global_index: global_index.to_string(), ++ completed: false, ++ } ++ } ++ ++ fn mark_completed(&mut self) { ++ self.completed = true; ++ } ++} ++ ++impl Drop for ClaimDropGuard { ++ fn drop(&mut self) { ++ if !self.completed { ++ tracing::error!( ++ req_id = self.req_id, ++ global_index = %self.global_index, ++ "!!!! CLAIM HANDLER DROPPED (CANCELLED) — HTTP client likely disconnected. \ ++ publish_and_record_claim was still in-flight when the future was cancelled. \ ++ ClaimEvent will NOT be recorded in the store." ++ ); ++ } ++ } ++} + + struct TransactionData { + pub hash: TxHash, +@@ -129,6 +170,18 @@ async fn handle_claim_asset( + txn_envelope: TxEnvelope, + signer: Address, + ) -> anyhow::Result { ++ let req_id = REQUEST_COUNTER.fetch_add(1, Ordering::Relaxed); ++ ++ tracing::info!( ++ req_id, ++ global_index = %params.globalIndex, ++ origin_token = %params.originTokenAddress, ++ amount = %params.amount, ++ dest_address = %params.destinationAddress, ++ tx_hash = %txn_hash, ++ ">> handle_claim_asset ENTER" ++ ); ++ + // Only claims where destinationNetwork matches our network_id are processed. + if params.destinationNetwork != service.network_id as u32 { + anyhow::bail!( +@@ -141,7 +194,7 @@ async fn handle_claim_asset( + // Skip zero-amount claims (e.g., genesis batch deposit). These create + // CLAIM notes that crash the NTX builder's faucet actor. + if params.amount.is_zero() { +- tracing::info!("skipping zero-amount claim (genesis batch)"); ++ tracing::info!(req_id, "skipping zero-amount claim (genesis batch)"); + record_local_immediate_success(service, txn_hash, txn_envelope, signer, vec![]).await?; + service + .store +@@ -150,20 +203,57 @@ async fn handle_claim_asset( + return Ok(txn_hash); + } + ++ // Install drop guard BEFORE try_claim — if this future is cancelled at any ++ // point after this, we'll see the DROPPED log. ++ let mut drop_guard = ClaimDropGuard::new(req_id, &format!("{}", params.globalIndex)); ++ + // Lock the claim index. All error paths after this MUST unclaim. ++ tracing::info!( ++ req_id, ++ global_index = %params.globalIndex, ++ "handle_claim_asset: calling try_claim..." ++ ); + service.store.try_claim(params.globalIndex).await?; ++ tracing::info!( ++ req_id, ++ global_index = %params.globalIndex, ++ "handle_claim_asset: try_claim lock acquired, calling publish_and_record_claim" ++ ); ++ + let result = +- publish_and_record_claim(service, params.clone(), txn_hash, txn_envelope, signer).await; +- if let Err(err) = result { +- let _ = service.store.unclaim(¶ms.globalIndex).await; +- tracing::error!("claim failed after lock: {err:#?}"); +- return Err(err); ++ publish_and_record_claim(service, params.clone(), txn_hash, txn_envelope, signer, req_id).await; ++ match &result { ++ Ok(()) => { ++ tracing::info!( ++ req_id, ++ global_index = %params.globalIndex, ++ "handle_claim_asset: publish_and_record_claim SUCCEEDED" ++ ); ++ drop_guard.mark_completed(); ++ } ++ Err(err) => { ++ tracing::error!( ++ req_id, ++ global_index = %params.globalIndex, ++ error = %err, ++ "handle_claim_asset: publish_and_record_claim FAILED, unclaiming" ++ ); ++ drop_guard.mark_completed(); // intentional failure, not a cancellation ++ let _ = service.store.unclaim(¶ms.globalIndex).await; ++ return Err(anyhow::anyhow!("{err:#}")); ++ } + } + + service + .store + .nonce_increment(&format!("{signer:#x}")) + .await?; ++ ++ tracing::info!( ++ req_id, ++ global_index = %params.globalIndex, ++ "<< handle_claim_asset EXIT OK" ++ ); + Ok(txn_hash) + } + +@@ -177,23 +267,82 @@ async fn publish_and_record_claim( + txn_hash: TxHash, + txn_envelope: TxEnvelope, + signer: Address, ++ req_id: u64, + ) -> anyhow::Result<()> { +- let claim_result = claim::publish_claim( +- params, ++ tracing::info!( ++ req_id, ++ global_index = %params.globalIndex, ++ origin_token = %params.originTokenAddress, ++ "publish_and_record_claim ENTER: about to get latest_block" ++ ); ++ let latest_block = service.store.get_latest_block_number().await?; ++ tracing::info!( ++ req_id, ++ global_index = %params.globalIndex, ++ latest_block, ++ "publish_and_record_claim: got latest_block, about to call publish_claim (this acquires MidenClient lock)" ++ ); ++ ++ let claim_result = match claim::publish_claim( ++ params.clone(), + &service.miden_client, + service.accounts.clone(), + service.store.clone(), +- service.store.get_latest_block_number().await?, ++ latest_block, + ) +- .await?; ++ .await { ++ Ok(r) => { ++ tracing::info!( ++ req_id, ++ global_index = %params.globalIndex, ++ miden_tx = %r.txn_id, ++ "publish_and_record_claim: publish_claim SUCCEEDED" ++ ); ++ r ++ } ++ Err(e) => { ++ tracing::error!( ++ req_id, ++ global_index = %params.globalIndex, ++ error = %e, ++ "publish_and_record_claim: publish_claim FAILED" ++ ); ++ return Err(e); ++ } ++ }; ++ ++ // ---- CRITICAL SECTION: Everything below this line is what goes missing ---- ++ // If the HTTP client disconnected while publish_claim was running, this code ++ // never executes. The MidenClient closure may have completed successfully on ++ // its background thread, but the caller's future was already dropped. ++ tracing::info!( ++ req_id, ++ global_index = %params.globalIndex, ++ "publish_and_record_claim: REACHED RECORDING SECTION (if you see this, cancellation is NOT the cause)" ++ ); + +- // Note: bridge-service will see this ClaimEvent from both ClaimTxManager and +- // L2 sync, causing a duplicate key error. See fixtures/bridge-db-patch.sql. + let txn_id = claim_result.txn_id; +- tracing::info!("published claim with eth txn: {txn_hash}; miden txn: {txn_id}"); ++ tracing::info!( ++ req_id, ++ "published claim with eth txn: {txn_hash}; miden txn: {txn_id}" ++ ); + let block_num = service.store.advance_block_number().await?; + let block_hash = service.block_state.get_block_hash(block_num); +- service ++ ++ let log_topics: Vec = claim_result.log.topics().iter().map(|t| format!("{t}")).collect(); ++ let log_data_hex = format!("{:x}", claim_result.log.data); ++ tracing::info!( ++ req_id, ++ eth_tx = %txn_hash, ++ miden_tx = %txn_id, ++ block_num, ++ log_topics = ?log_topics, ++ log_data_len = log_data_hex.len(), ++ log_data_prefix = %&log_data_hex[..std::cmp::min(120, log_data_hex.len())], ++ "recording ClaimEvent in store" ++ ); ++ ++ if let Err(e) = service + .store + .txn_begin( + txn_hash, +@@ -205,11 +354,23 @@ async fn publish_and_record_claim( + logs: vec![claim_result.log], + }, + ) +- .await?; +- service ++ .await ++ { ++ tracing::error!(req_id, eth_tx = %txn_hash, error = %e, "txn_begin FAILED for ClaimEvent"); ++ return Err(e.into()); ++ } ++ tracing::info!(req_id, eth_tx = %txn_hash, "txn_begin OK"); ++ ++ if let Err(e) = service + .store + .txn_commit(txn_hash, Ok(()), block_num, block_hash) +- .await?; ++ .await ++ { ++ tracing::error!(req_id, eth_tx = %txn_hash, error = %e, "txn_commit FAILED for ClaimEvent"); ++ return Err(e.into()); ++ } ++ ++ tracing::info!(req_id, eth_tx = %txn_hash, block_num, "ClaimEvent recorded successfully"); + Ok(()) + } + +@@ -234,11 +395,26 @@ pub async fn service_send_raw_txn(service: ServiceState, input: String) -> anyho + let txn = unwrap_txn_envelope(txn_envelope.clone())?; + let txn_hash = txn.hash; + let signer = txn_envelope.recover_signer()?; +- tracing::debug!(target: concat!(module_path!(), "::debug"), "raw transaction hash: {txn_hash}"); ++ ++ // Identify the method for the entry log ++ let method_name = if txn.input.starts_with(&claimAssetCall::SELECTOR) { ++ "claimAsset" ++ } else if txn.input.starts_with(&insertGlobalExitRootCall::SELECTOR) { ++ "insertGlobalExitRoot" ++ } else if txn.input.starts_with(&updateExitRootCall::SELECTOR) { ++ "updateExitRoot" ++ } else { ++ "unknown" ++ }; ++ tracing::info!( ++ method = method_name, ++ tx_hash = %txn_hash, ++ signer = %signer, ++ ">> service_send_raw_txn ENTER" ++ ); + + let params_encoded = &txn.input; + if params_encoded.starts_with(&claimAssetCall::SELECTOR) { +- tracing::debug!("claimAsset call"); + let params = claimAssetCall::abi_decode(params_encoded)?; + tracing::debug!(target: concat!(module_path!(), "::debug"), "claimAsset call params: {params:?}"); + return handle_claim_asset(&service, params, txn_hash, txn_envelope, signer).await; diff --git a/patches/004-aggkit-ethtxmanager-already-exists.patch b/patches/004-aggkit-ethtxmanager-already-exists.patch new file mode 100644 index 0000000..6e054e1 --- /dev/null +++ b/patches/004-aggkit-ethtxmanager-already-exists.patch @@ -0,0 +1,58 @@ +diff --git a/aggoracle/chaingersender/evm.go b/aggoracle/chaingersender/evm.go +index 7c84ef7..6b500fa 100644 +--- a/aggoracle/chaingersender/evm.go ++++ b/aggoracle/chaingersender/evm.go +@@ -3,6 +3,7 @@ package chaingersender + import ( + "context" + "fmt" ++ "strings" + "time" + + "github.com/0xPolygon/cdk-contracts-tooling/contracts/aggchain-multisig/agglayergerl2" +@@ -17,6 +18,7 @@ import ( + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" ++ ethTypes "github.com/ethereum/go-ethereum/core/types" + ) + + const ( +@@ -226,13 +228,33 @@ func (c *EVMChainGERSender) submitTransaction( + return fmt.Errorf("failed to pack %s call: %w", funcName, err) + } + +- // Add the transaction to the transaction manager ++ // Add the transaction to the transaction manager. ++ // The tx ID is deterministic: hash(To, Value, Data). If the same GER is ++ // already queued (still in-flight from a previous attempt), Add returns ++ // ErrAlreadyExists. In that case we fall through to the monitoring loop ++ // so we wait for the existing tx instead of returning an error. ++ // Compute the expected ID ourselves so we can monitor even when Add ++ // returns a zero hash on ErrAlreadyExists (zkevm-ethtx-manager <= v0.2.17). + id, err := c.ethTxMan.Add(ctx, targetAddr, common.Big0, txInput, c.gasOffset, nil) + if err != nil { +- return fmt.Errorf("failed to add %s GER transaction: %w", action, err) ++ if strings.Contains(err.Error(), "already exists") { ++ c.logger.Infof("%s GER tx already in-flight, monitoring existing tx. GER: %s", action, ger.Hex()) ++ // Use the deterministic ID to monitor the existing tx. ++ // On v0.2.17 Add returns zero hash on error, so we recompute ++ // the same way ethtxmanager does: hash of LegacyTx{To, Data}. ++ if id == (common.Hash{}) { ++ legacyTx := ethTypes.NewTx(ðTypes.LegacyTx{ ++ To: targetAddr, ++ Data: txInput, ++ }) ++ id = legacyTx.Hash() ++ } ++ } else { ++ return fmt.Errorf("failed to add %s GER transaction: %w", action, err) ++ } ++ } else { ++ c.logger.Infof("%s GER transaction submitted with ID: %s. GER: %s", action, id.Hex(), ger.Hex()) + } +- +- c.logger.Infof("%s GER transaction submitted with ID: %s. GER: %s", action, id.Hex(), ger.Hex()) + c.logger.Debugf("monitoring every %s", c.waitPeriodMonitorTx) + + // Monitor the transaction status diff --git a/patches/005-bridge-out-false-block-advance.patch b/patches/005-bridge-out-false-block-advance.patch new file mode 100644 index 0000000..3e2703c --- /dev/null +++ b/patches/005-bridge-out-false-block-advance.patch @@ -0,0 +1,180 @@ +diff --git a/src/bridge_out.rs b/src/bridge_out.rs +index 8d7f2e1..a23481a 100644 +--- a/src/bridge_out.rs ++++ b/src/bridge_out.rs +@@ -240,6 +240,11 @@ impl SyncListener for BridgeOutScanner { + + let mut emitted = false; + for note in &consumed_notes { ++ // Only process B2AGG notes — other consumed notes (CLAIM, UpdateGerNote) ++ // must not trigger block advancement or they race with GER event writes. ++ if !is_b2agg_note(note.details()) { ++ continue; ++ } + let was_processed = self + .store + .is_note_processed(¬e.id().to_string()) +diff --git a/src/ger.rs b/src/ger.rs +index cabcd3e..483f7fc 100644 +--- a/src/ger.rs ++++ b/src/ger.rs +@@ -100,60 +100,85 @@ async fn submit_ger_to_miden( + ger_bytes: [u8; 32], + accounts: &AccountsConfig, + ) -> anyhow::Result<()> { +- let ger = ExitRoot::new(ger_bytes); + let service_id = accounts.service.0; + let bridge_id = accounts.bridge.0; + +- let note = UpdateGerNote::create(ger, service_id, bridge_id, client.rng())?; +- tracing::info!(note_id = %note.id(), "UpdateGerNote created"); ++ // Retry up to 3 times — concurrent claims can change the service account's ++ // state commitment between sync and TX submission. ++ for attempt in 0..3u32 { ++ // Always re-sync before building the TX so we have the latest account state. ++ client.sync_state().await?; + +- let tx_request = TransactionRequestBuilder::new() +- .own_output_notes(vec![OutputNote::Full(note)]) +- .build()?; ++ let ger = ExitRoot::new(ger_bytes); ++ let note = UpdateGerNote::create(ger, service_id, bridge_id, client.rng())?; ++ if attempt == 0 { ++ tracing::info!(note_id = %note.id(), "UpdateGerNote created"); ++ } + +- let tx_id = client +- .submit_new_transaction(service_id, tx_request) +- .await?; +- tracing::info!( +- tx_id = %tx_id, +- ger = %hex::encode(ger_bytes), +- "UpdateGerNote submitted to Miden node, waiting for commit..." +- ); ++ let tx_request = TransactionRequestBuilder::new() ++ .own_output_notes(vec![OutputNote::Full(note)]) ++ .build()?; ++ ++ let tx_id = match client.submit_new_transaction(service_id, tx_request).await { ++ Ok(id) => id, ++ Err(e) => { let e = anyhow::anyhow!("{e:#}"); ++ let msg = format!("{e:#}"); ++ if msg.contains("initial state commitment") && attempt < 2 { ++ tracing::warn!( ++ attempt, ++ "GER TX rejected (stale commitment), retrying after sync..." ++ ); ++ tokio::time::sleep(std::time::Duration::from_secs(2)).await; ++ continue; ++ } ++ return Err(anyhow::anyhow!("{e:#}")); ++ } ++ }; + +- // Poll for transaction commitment +- let timeout_secs: u64 = std::env::var("GER_COMMIT_TIMEOUT_SECS") +- .ok() +- .and_then(|v| v.parse().ok()) +- .unwrap_or(30) +- .clamp(5, 120); +- let mut committed = false; +- for _ in 0..timeout_secs { +- // We can check if txn is in the store and has a block number +- let txns = client +- .get_transactions(miden_client::store::TransactionFilter::All) +- .await?; +- if txns.iter().any(|t| { +- t.id == tx_id +- && matches!( +- t.status, +- miden_client::transaction::TransactionStatus::Committed { .. } +- ) +- }) { +- committed = true; +- break; ++ tracing::info!( ++ tx_id = %tx_id, ++ ger = %hex::encode(ger_bytes), ++ attempt, ++ "UpdateGerNote submitted to Miden node, waiting for commit..." ++ ); ++ ++ // Poll for transaction commitment ++ let timeout_secs: u64 = std::env::var("GER_COMMIT_TIMEOUT_SECS") ++ .ok() ++ .and_then(|v| v.parse().ok()) ++ .unwrap_or(30) ++ .clamp(5, 120); ++ let mut committed = false; ++ for _ in 0..timeout_secs { ++ let txns = client ++ .get_transactions(miden_client::store::TransactionFilter::All) ++ .await?; ++ if txns.iter().any(|t| { ++ t.id == tx_id ++ && matches!( ++ t.status, ++ miden_client::transaction::TransactionStatus::Committed { .. } ++ ) ++ }) { ++ committed = true; ++ break; ++ } ++ tokio::time::sleep(std::time::Duration::from_secs(1)).await; ++ client.sync_state().await?; ++ } ++ ++ if !committed { ++ anyhow::bail!("UpdateGerNote transaction {tx_id} not committed after {timeout_secs}s"); + } +- tokio::time::sleep(std::time::Duration::from_secs(1)).await; +- client.sync_state().await?; // Sync to get latest updates +- } + +- if !committed { +- anyhow::bail!("UpdateGerNote transaction {tx_id} not committed after {timeout_secs}s"); ++ tracing::info!(tx_id = %tx_id, "UpdateGerNote transaction committed"); ++ return Ok(()); + } + +- tracing::info!(tx_id = %tx_id, "UpdateGerNote transaction committed"); +- Ok(()) ++ anyhow::bail!("GER injection failed after 3 attempts due to stale commitment") + } + ++ + #[allow(clippy::too_many_arguments)] + pub async fn insert_ger( + ger_bytes: [u8; 32], +@@ -186,9 +211,15 @@ pub async fn insert_ger( + }) + .await?; + +- // Re-assign block number after the Miden TX has committed so +- // current_block reflects the latest sync and +1 is genuinely ahead. +- block_number = block_state.current_block_number() + 1; ++ // Use the store's sequential block counter so GER events and claim ++ // events never collide on the same block number. ++ block_number = store.advance_block_number().await?; ++ tracing::info!( ++ ger = %hex::encode(ger_bytes), ++ block_number, ++ miden_block = block_state.current_block_number(), ++ "GER commit done — writing event to block (from store.advance)" ++ ); + let block_hash = block_state.get_block_hash(block_number); + let timestamp = block_state.get_block_timestamp(block_number); + +@@ -206,11 +237,7 @@ pub async fn insert_ger( + ) + .await?; + +- // Advance latest_block_number so bridge-service queries the new block +- let current_latest = store.get_latest_block_number().await.unwrap_or(0); +- if block_number > current_latest { +- store.set_latest_block_number(block_number).await?; +- } ++ // latest_block_number already advanced by advance_block_number() above + } else { + tracing::debug!( + ger = %hex::encode(ger_bytes), diff --git a/patches/005-ger-block-assignment-logging.patch b/patches/005-ger-block-assignment-logging.patch new file mode 100644 index 0000000..34c545d --- /dev/null +++ b/patches/005-ger-block-assignment-logging.patch @@ -0,0 +1,147 @@ +diff --git a/src/ger.rs b/src/ger.rs +index cabcd3e..6c4ed8e 100644 +--- a/src/ger.rs ++++ b/src/ger.rs +@@ -100,60 +100,85 @@ async fn submit_ger_to_miden( + ger_bytes: [u8; 32], + accounts: &AccountsConfig, + ) -> anyhow::Result<()> { +- let ger = ExitRoot::new(ger_bytes); + let service_id = accounts.service.0; + let bridge_id = accounts.bridge.0; + +- let note = UpdateGerNote::create(ger, service_id, bridge_id, client.rng())?; +- tracing::info!(note_id = %note.id(), "UpdateGerNote created"); ++ // Retry up to 3 times — concurrent claims can change the service account's ++ // state commitment between sync and TX submission. ++ for attempt in 0..3u32 { ++ // Always re-sync before building the TX so we have the latest account state. ++ client.sync_state().await?; + +- let tx_request = TransactionRequestBuilder::new() +- .own_output_notes(vec![OutputNote::Full(note)]) +- .build()?; ++ let ger = ExitRoot::new(ger_bytes); ++ let note = UpdateGerNote::create(ger, service_id, bridge_id, client.rng())?; ++ if attempt == 0 { ++ tracing::info!(note_id = %note.id(), "UpdateGerNote created"); ++ } + +- let tx_id = client +- .submit_new_transaction(service_id, tx_request) +- .await?; +- tracing::info!( +- tx_id = %tx_id, +- ger = %hex::encode(ger_bytes), +- "UpdateGerNote submitted to Miden node, waiting for commit..." +- ); ++ let tx_request = TransactionRequestBuilder::new() ++ .own_output_notes(vec![OutputNote::Full(note)]) ++ .build()?; ++ ++ let tx_id = match client.submit_new_transaction(service_id, tx_request).await { ++ Ok(id) => id, ++ Err(e) => { let e = anyhow::anyhow!("{e:#}"); ++ let msg = format!("{e:#}"); ++ if msg.contains("initial state commitment") && attempt < 2 { ++ tracing::warn!( ++ attempt, ++ "GER TX rejected (stale commitment), retrying after sync..." ++ ); ++ tokio::time::sleep(std::time::Duration::from_secs(2)).await; ++ continue; ++ } ++ return Err(anyhow::anyhow!("{e:#}")); ++ } ++ }; + +- // Poll for transaction commitment +- let timeout_secs: u64 = std::env::var("GER_COMMIT_TIMEOUT_SECS") +- .ok() +- .and_then(|v| v.parse().ok()) +- .unwrap_or(30) +- .clamp(5, 120); +- let mut committed = false; +- for _ in 0..timeout_secs { +- // We can check if txn is in the store and has a block number +- let txns = client +- .get_transactions(miden_client::store::TransactionFilter::All) +- .await?; +- if txns.iter().any(|t| { +- t.id == tx_id +- && matches!( +- t.status, +- miden_client::transaction::TransactionStatus::Committed { .. } +- ) +- }) { +- committed = true; +- break; ++ tracing::info!( ++ tx_id = %tx_id, ++ ger = %hex::encode(ger_bytes), ++ attempt, ++ "UpdateGerNote submitted to Miden node, waiting for commit..." ++ ); ++ ++ // Poll for transaction commitment ++ let timeout_secs: u64 = std::env::var("GER_COMMIT_TIMEOUT_SECS") ++ .ok() ++ .and_then(|v| v.parse().ok()) ++ .unwrap_or(30) ++ .clamp(5, 120); ++ let mut committed = false; ++ for _ in 0..timeout_secs { ++ let txns = client ++ .get_transactions(miden_client::store::TransactionFilter::All) ++ .await?; ++ if txns.iter().any(|t| { ++ t.id == tx_id ++ && matches!( ++ t.status, ++ miden_client::transaction::TransactionStatus::Committed { .. } ++ ) ++ }) { ++ committed = true; ++ break; ++ } ++ tokio::time::sleep(std::time::Duration::from_secs(1)).await; ++ client.sync_state().await?; ++ } ++ ++ if !committed { ++ anyhow::bail!("UpdateGerNote transaction {tx_id} not committed after {timeout_secs}s"); + } +- tokio::time::sleep(std::time::Duration::from_secs(1)).await; +- client.sync_state().await?; // Sync to get latest updates +- } + +- if !committed { +- anyhow::bail!("UpdateGerNote transaction {tx_id} not committed after {timeout_secs}s"); ++ tracing::info!(tx_id = %tx_id, "UpdateGerNote transaction committed"); ++ return Ok(()); + } + +- tracing::info!(tx_id = %tx_id, "UpdateGerNote transaction committed"); +- Ok(()) ++ anyhow::bail!("GER injection failed after 3 attempts due to stale commitment") + } + ++ + #[allow(clippy::too_many_arguments)] + pub async fn insert_ger( + ger_bytes: [u8; 32], +@@ -189,6 +214,14 @@ pub async fn insert_ger( + // Re-assign block number after the Miden TX has committed so + // current_block reflects the latest sync and +1 is genuinely ahead. + block_number = block_state.current_block_number() + 1; ++ let store_latest = store.get_latest_block_number().await.unwrap_or(0); ++ tracing::info!( ++ ger = %hex::encode(ger_bytes), ++ block_number, ++ miden_block = block_state.current_block_number(), ++ store_latest, ++ "GER commit done — writing event to block_number (miden+1), store latest_block for comparison" ++ ); + let block_hash = block_state.get_block_hash(block_number); + let timestamp = block_state.get_block_timestamp(block_number); + diff --git a/patches/006-bridge-out-tool-retry.patch b/patches/006-bridge-out-tool-retry.patch new file mode 100644 index 0000000..cba8dd2 --- /dev/null +++ b/patches/006-bridge-out-tool-retry.patch @@ -0,0 +1,299 @@ +diff --git a/src/bin/bridge_out_tool.rs b/src/bin/bridge_out_tool.rs +index 6ffd990..0993360 100644 +--- a/src/bin/bridge_out_tool.rs ++++ b/src/bin/bridge_out_tool.rs +@@ -11,7 +11,7 @@ + use std::path::PathBuf; + use std::sync::Arc; + +-use anyhow::{Context, anyhow}; ++use anyhow::{anyhow, Context}; + use clap::Parser; + use miden_base_agglayer::{B2AggNote, EthAddressFormat}; + use miden_client::DebugMode; +@@ -89,90 +89,61 @@ async fn main() -> anyhow::Result<()> { + args.dest_address, args.dest_network + ); + +- let store_path = args.store_dir.join("store.sqlite3"); + let keystore_path = args.store_dir.join("keystore"); +- +- if !store_path.exists() { +- return Err(anyhow!("store not found at {}", store_path.display())); +- } + if !keystore_path.exists() { + return Err(anyhow!("keystore not found at {}", keystore_path.display())); + } + ++ // Create a fresh temp directory for each launch. This avoids stale account ++ // state from previous runs (different nonce, corrupted vault). Only the ++ // keystore (private keys) is carried over from the source store-dir. ++ let work_dir = std::env::temp_dir().join(format!("miden-bridge-out-{}", std::process::id())); ++ std::fs::create_dir_all(&work_dir)?; ++ let work_keystore = work_dir.join("keystore"); ++ std::fs::create_dir_all(&work_keystore)?; ++ // Copy all key files ++ for entry in std::fs::read_dir(&keystore_path)? { ++ let entry = entry?; ++ std::fs::copy(entry.path(), work_keystore.join(entry.file_name()))?; ++ } ++ let work_store = work_dir.join("store.sqlite3"); ++ println!("[bridge-out] using fresh store: {}", work_dir.display()); ++ + // Parse node endpoint + let node_endpoint = + Endpoint::try_from(args.node_url.as_str()).map_err(|e| anyhow!("invalid node URL: {e}"))?; + +- // Build miden client from existing store +- let keystore = FilesystemKeyStore::new(keystore_path)?; ++ // Build miden client with fresh store + existing keystore ++ let keystore = FilesystemKeyStore::new(work_keystore)?; + let mut client = ClientBuilder::new() + .grpc_client(&node_endpoint, Some(10_000)) +- .sqlite_store(store_path) ++ .sqlite_store(work_store) + .authenticator(Arc::new(keystore)) + .in_debug_mode(DebugMode::Enabled) + .build() + .await + .map_err(|e| anyhow!("failed to build miden client: {e}"))?; + +- // Sync state ++ // Sync — fetches all account state fresh from the node. ++ // The wallet is a public account so sync_state will import it. + println!("[bridge-out] syncing state..."); + client + .sync_state() + .await + .map_err(|e| anyhow!("sync failed: {e}"))?; ++ // Import the wallet account explicitly (public account, fetched from node) ++ client.import_account_by_id(wallet_id).await ++ .map_err(|e| anyhow!("failed to import wallet from node: {e}"))?; ++ client.sync_state().await ++ .map_err(|e| anyhow!("post-import sync failed: {e}"))?; + println!("[bridge-out] sync complete"); + +- // Try to consume any Expected/Committed notes for the wallet +- { +- use miden_client::store::NoteFilter; +- // Check for Expected notes (synced but not yet consumed) +- let expected = client +- .get_input_notes(NoteFilter::Expected) +- .await +- .unwrap_or_default(); +- let committed = client +- .get_input_notes(NoteFilter::Committed) +- .await +- .unwrap_or_default(); +- println!( +- "[bridge-out] notes: {} expected, {} committed", +- expected.len(), +- committed.len() +- ); ++ // Note: we do NOT try to consume committed notes here. ++ // claim-note.sh handles P2ID note consumption separately. ++ // Attempting to consume here corrupts local state when notes ++ // target a different wallet or were already consumed elsewhere. + +- // Try consuming committed notes first (standard path) +- let consumable = client +- .get_consumable_notes(Some(wallet_id)) +- .await +- .unwrap_or_default(); +- if !consumable.is_empty() { +- println!("[bridge-out] consuming {} notes...", consumable.len()); +- let notes: Vec = consumable +- .into_iter() +- .filter_map(|(rec, _)| rec.try_into().ok()) +- .collect(); +- if !notes.is_empty() { +- match TransactionRequestBuilder::new().build_consume_notes(notes) { +- Ok(req) => { +- match client.submit_new_transaction(wallet_id, req).await { +- Ok(tx) => { +- println!("[bridge-out] consumed notes: {tx}"); +- // Wait for commit +- for _ in 0..10 { +- tokio::time::sleep(std::time::Duration::from_secs(3)).await; +- client.sync_state().await.ok(); +- } +- } +- Err(e) => println!("[bridge-out] consume failed: {e}"), +- } +- } +- Err(e) => println!("[bridge-out] build consume req failed: {e}"), +- } +- } +- } +- } +- +- // Check wallet balance ++ // Check wallet balance (fresh store — should always work) + let balance = client + .account_reader(wallet_id) + .get_balance(faucet_id) +@@ -191,12 +162,21 @@ async fn main() -> anyhow::Result<()> { + let l1_dest = EthAddressFormat::from_hex(&args.dest_address) + .map_err(|e| anyhow!("invalid dest address: {e}"))?; + +- // Create B2AGG note ++ println!("[bridge-out] B2AGG note ready to create"); ++ ++ // Submit once. No retries — each submit_new_transaction call bumps the ++ // local nonce via apply_transaction. Retrying creates nonce N and N+1 TXs ++ // in the mempool, deadlocking both (N+1 can't commit until N does). ++ // If submit fails at RPC level, the nonce was NOT bumped (safe to retry ++ // from a fresh tool invocation). ++ println!("[bridge-out] submitting transaction..."); ++ client.sync_state().await.ok(); ++ client.import_account_by_id(bridge_id).await.ok(); ++ + let asset: Asset = FungibleAsset::new(faucet_id, args.amount) + .map_err(|e| anyhow!("invalid asset: {e}"))? + .into(); + let note_assets = NoteAssets::new(vec![asset]).map_err(|e| anyhow!("note assets: {e}"))?; +- + let b2agg = B2AggNote::create( + args.dest_network, + l1_dest, +@@ -207,22 +187,11 @@ async fn main() -> anyhow::Result<()> { + ) + .map_err(|e| anyhow!("B2AGG creation failed: {e}"))?; + +- println!("[bridge-out] B2AGG note created"); +- +- // Re-import bridge account so the NoteScreener has the latest asset tree. +- // Without this, submit_new_transaction fails with FetchAssetWitnessFailed +- // after CLAIM modified the bridge account. +- if let Err(e) = client.import_account_by_id(bridge_id).await { +- eprintln!("[bridge-out] bridge re-import: {e} (may already be tracked)"); +- } +- +- // Submit transaction + let tx_request = TransactionRequestBuilder::new() + .own_output_notes(vec![OutputNote::Full(b2agg)]) + .build() + .map_err(|e| anyhow!("tx request build failed: {e}"))?; + +- println!("[bridge-out] submitting transaction..."); + let tx_id = client + .submit_new_transaction(wallet_id, tx_request) + .await +@@ -230,12 +199,39 @@ async fn main() -> anyhow::Result<()> { + + println!("[bridge-out] transaction submitted: {tx_id}"); + +- // Wait a couple of sync cycles for the note to propagate ++ // Wait for transaction commitment. The TX is now in the node's mempool. ++ // The Miden node's block producer may fail a block due to NTX builder ++ // "asset vault error", but the TX remains in the mempool and will be ++ // included in a future block once the node recovers. ++ // We wait up to 3 minutes (60 × 3s) to account for multiple block failures. + println!("[bridge-out] waiting for confirmation..."); +- for i in 1..=5 { +- tokio::time::sleep(std::time::Duration::from_secs(5)).await; ++ let mut committed = false; ++ for i in 1..=60u32 { ++ tokio::time::sleep(std::time::Duration::from_secs(3)).await; + client.sync_state().await.ok(); +- println!("[bridge-out] sync cycle {i}/5"); ++ let txns = client ++ .get_transactions(miden_client::store::TransactionFilter::All) ++ .await ++ .unwrap_or_default(); ++ if txns.iter().any(|t| { ++ t.id == tx_id ++ && matches!( ++ t.status, ++ miden_client::transaction::TransactionStatus::Committed { .. } ++ ) ++ }) { ++ committed = true; ++ println!("[bridge-out] transaction committed (cycle {i})"); ++ break; ++ } ++ if i % 10 == 0 { ++ println!("[bridge-out] waiting... cycle {i}/60"); ++ } ++ } ++ if !committed { ++ return Err(anyhow!( ++ "transaction {tx_id} not committed after 3 min — TX may be stuck in mempool" ++ )); + } + + let new_balance = client +@@ -244,7 +240,72 @@ async fn main() -> anyhow::Result<()> { + .await + .unwrap_or(0); + println!("[bridge-out] wallet balance after: {new_balance}"); +- println!("[bridge-out] done"); + ++ // Wait for the NTX builder to consume our B2AGG note before returning. ++ // The NTX builder picks up B2AGG notes (NetworkAccountTarget::Always) and ++ // creates consumption TXs on the bridge account. If we return immediately, ++ // the next bridge-out may land in a block poisoned by the NTX builder's ++ // consumption TX ("asset vault error"). ++ // ++ // We check if any of our output notes transitioned to consumed state. ++ // The B2AGG note is the only non-consumed output note we just created. ++ println!("[bridge-out] waiting for B2AGG note consumption..."); ++ let mut consumed = false; ++ for i in 1..=30u32 { ++ tokio::time::sleep(std::time::Duration::from_secs(2)).await; ++ client.sync_state().await.ok(); ++ // Check if all our output notes are consumed ++ let pending = client ++ .get_output_notes(miden_client::store::NoteFilter::Committed) ++ .await ++ .unwrap_or_default(); ++ if pending.is_empty() { ++ consumed = true; ++ println!("[bridge-out] B2AGG note consumed by bridge (cycle {i})"); ++ break; ++ } ++ if i % 10 == 0 { ++ println!("[bridge-out] B2AGG note still pending, cycle {i}/30"); ++ } ++ } ++ if !consumed { ++ println!("[bridge-out] note consumption timeout (60s), proceeding anyway"); ++ } ++ ++ // Wait 2 real Miden blocks after consumption so the bridge account's ++ // vault state fully settles before the next bridge-out TX is built. ++ // The NTX builder's consumption TX commits in block N; we wait until ++ // block N+2 to ensure no stale vault deltas remain in the pipeline. ++ if consumed { ++ let base_block = client ++ .sync_state() ++ .await ++ .map(|s| s.block_num) ++ .unwrap_or_default(); ++ let target = base_block + 2; ++ println!( ++ "[bridge-out] waiting for 2 blocks to settle (current: {}, target: {})...", ++ base_block, target ++ ); ++ for poll in 1..=30u32 { ++ tokio::time::sleep(std::time::Duration::from_secs(2)).await; ++ match client.sync_state().await { ++ Ok(s) if s.block_num >= target => { ++ println!( ++ "[bridge-out] block {} reached (target was {}), state settled", ++ s.block_num, target ++ ); ++ break; ++ } ++ _ => { ++ if poll % 10 == 0 { ++ println!("[bridge-out] still waiting for block {target}..."); ++ } ++ } ++ } ++ } ++ } ++ ++ println!("[bridge-out] done"); + Ok(()) + } diff --git a/scripts/bridge-erc20-test.sh b/scripts/bridge-erc20-test.sh new file mode 100755 index 0000000..ff516a5 --- /dev/null +++ b/scripts/bridge-erc20-test.sh @@ -0,0 +1,228 @@ +#!/usr/bin/env bash +# +# Bridge ERC20 Test - Deploy ERC20 tokens on L1, bridge them to Miden +# +# Usage: ./scripts/bridge-erc20-test.sh [num_tokens] +# +# Deploys N ERC20 contracts, mints tokens, approves bridge, and bridges them. +# Default: 10 tokens. + +set -euo pipefail + +NUM_TOKENS="${1:-10}" +DEST="0x00000000a417929a101b89100dda63bf4f692800" +DEST_NET=1 + +# Kurtosis funded account +PRIVATE_KEY="0x12d7de8621a77640c9241b2595ba78ce443d05e94090365ab3bb5e19df82c625" +FROM_ADDRESS="0xE34aaF64b29273B7D567FCFc40544c014EEe9970" + +# Auto-detect L1 RPC +L1_RPC="${L1_RPC:-}" +if [[ -z "$L1_RPC" ]]; then + L1_CONTAINER=$(docker ps --filter "name=el-1-geth" --format "{{.Names}}" | head -1) + if [[ -n "$L1_CONTAINER" ]]; then + L1_PORT=$(docker port "$L1_CONTAINER" 8545 2>/dev/null | cut -d: -f2 || true) + [[ -n "$L1_PORT" ]] && L1_RPC="http://localhost:$L1_PORT" + fi +fi +[[ -z "$L1_RPC" ]] && L1_RPC=$(kurtosis port print miden-cdk el-1-geth-lighthouse rpc 2>/dev/null) || true +[[ -z "$L1_RPC" ]] && { echo "ERROR: Cannot find L1 RPC"; exit 1; } + +# Auto-detect bridge address +BRIDGE_ADDRESS="${BRIDGE_ADDRESS:-}" +if [[ -z "$BRIDGE_ADDRESS" ]]; then + BRIDGE_ADDRESS=$(kurtosis service exec miden-cdk contracts-001 \ + "cat /opt/output/combined.json 2>/dev/null" 2>/dev/null | jq -r '.polygonZkEVMBridgeAddress // empty' || true) +fi +[[ -z "$BRIDGE_ADDRESS" ]] && BRIDGE_ADDRESS="0xC8cbEBf950B9Df44d987c8619f092beA980fF038" + +echo "=== ERC20 Bridge Test ===" +echo "L1 RPC: $L1_RPC" +echo "Bridge: $BRIDGE_ADDRESS" +echo "From: $FROM_ADDRESS" +echo "Dest: $DEST" +echo "Tokens: $NUM_TOKENS" +echo "" + +# Minimal ERC20 bytecode — constructor(name, symbol, decimals) +# Using a simple inline Solidity contract deployed via forge +ERC20_SOL=' +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +contract TestToken { + string public name; + string public symbol; + uint8 public decimals; + uint256 public totalSupply; + mapping(address => uint256) public balanceOf; + mapping(address => mapping(address => uint256)) public allowance; + + event Transfer(address indexed from, address indexed to, uint256 value); + event Approval(address indexed owner, address indexed spender, uint256 value); + + constructor(string memory _name, string memory _symbol, uint8 _decimals) { + name = _name; + symbol = _symbol; + decimals = _decimals; + } + + function mint(address to, uint256 amount) external { + totalSupply += amount; + balanceOf[to] += amount; + emit Transfer(address(0), to, amount); + } + + function approve(address spender, uint256 amount) external returns (bool) { + allowance[msg.sender][spender] = amount; + emit Approval(msg.sender, spender, amount); + return true; + } + + function transfer(address to, uint256 amount) external returns (bool) { + require(balanceOf[msg.sender] >= amount, "insufficient"); + balanceOf[msg.sender] -= amount; + balanceOf[to] += amount; + emit Transfer(msg.sender, to, amount); + return true; + } + + function transferFrom(address from, address to, uint256 amount) external returns (bool) { + require(allowance[from][msg.sender] >= amount, "not approved"); + require(balanceOf[from] >= amount, "insufficient"); + allowance[from][msg.sender] -= amount; + balanceOf[from] -= amount; + balanceOf[to] += amount; + emit Transfer(from, to, amount); + return true; + } +} +' + +# Write the contract to a temp file +TMPDIR=$(mktemp -d) +mkdir -p "$TMPDIR/src" +echo "$ERC20_SOL" > "$TMPDIR/src/TestToken.sol" +cat > "$TMPDIR/foundry.toml" << 'TOML' +[profile.default] +src = "src" +out = "out" +solc_version = "0.8.28" +TOML + +echo "--- Step 1: Compiling ERC20 contract ---" +(cd "$TMPDIR" && forge build --quiet 2>&1) || { echo "Forge build failed"; exit 1; } +echo "✓ Compiled" +echo "" + +# Get current nonce +NONCE=$(cast nonce "$FROM_ADDRESS" --rpc-url "$L1_RPC") + +TOKENS=() +NAMES=("ALPHA" "BETA" "GAMMA" "DELTA" "EPSILON" "ZETA" "ETA" "THETA" "IOTA" "KAPPA" + "LAMBDA" "MU" "NU" "XI" "OMICRON" "PI" "RHO" "SIGMA" "TAU" "UPSILON") +DECIMALS=(18 18 8 8 18 10 8 18 12 18 18 8 10 18 18 9 8 18 18 12) + +BYTECODE=$(jq -r '.bytecode.object' "$TMPDIR/out/TestToken.sol/TestToken.json") + +echo "--- Step 2: Deploying $NUM_TOKENS ERC20 tokens ---" +for i in $(seq 0 $((NUM_TOKENS - 1))); do + NAME="${NAMES[$i]}" + SYM="${NAMES[$i]:0:3}" + DEC="${DECIMALS[$i]}" + + ARGS=$(cast abi-encode "constructor(string,string,uint8)" "$NAME Token" "$SYM" "$DEC") + RESULT=$(cast send --rpc-url "$L1_RPC" --private-key "$PRIVATE_KEY" \ + --gas-limit 1000000 --nonce "$NONCE" --json \ + --create "${BYTECODE}${ARGS#0x}" 2>&1) + + ADDR=$(echo "$RESULT" | jq -r '.contractAddress // empty') + if [[ -z "$ADDR" ]]; then + echo " [FAIL] $NAME — $(echo "$RESULT" | head -1)" + NONCE=$((NONCE + 1)) + continue + fi + + TOKENS+=("$ADDR:$NAME:$SYM:$DEC") + echo " [$((i+1))/$NUM_TOKENS] $NAME ($SYM, ${DEC}dec) → $ADDR" + NONCE=$((NONCE + 1)) +done +echo "✓ Deployed ${#TOKENS[@]} tokens" +echo "" + +MINT_AMOUNT_BASE="1000000" # 1M tokens (before decimal scaling) + +echo "--- Step 3: Minting & approving ---" +for TOKEN_INFO in "${TOKENS[@]}"; do + IFS=: read -r ADDR NAME SYM DEC <<< "$TOKEN_INFO" + + # Scale mint amount by decimals + MINT_WEI="${MINT_AMOUNT_BASE}$(printf '%0*d' "$DEC" 0)" + + # Mint + cast send "$ADDR" "mint(address,uint256)" "$FROM_ADDRESS" "$MINT_WEI" \ + --private-key "$PRIVATE_KEY" --rpc-url "$L1_RPC" --nonce "$NONCE" --json > /dev/null 2>&1 + NONCE=$((NONCE + 1)) + + # Approve bridge + cast send "$ADDR" "approve(address,uint256)" "$BRIDGE_ADDRESS" "$MINT_WEI" \ + --private-key "$PRIVATE_KEY" --rpc-url "$L1_RPC" --nonce "$NONCE" --json > /dev/null 2>&1 + NONCE=$((NONCE + 1)) + + BAL=$(cast call "$ADDR" "balanceOf(address)(uint256)" "$FROM_ADDRESS" --rpc-url "$L1_RPC" 2>/dev/null) + echo " $SYM: minted $MINT_AMOUNT_BASE tokens (${DEC}dec), balance=$BAL" +done +echo "✓ All minted and approved" +echo "" + +# Bridge amount per token: 0.1, 0.2, ... 1.0 tokens (scaled by decimals) +BRIDGE_AMOUNTS=(1 2 3 4 5 6 7 8 9 10 + 1 2 3 4 5 6 7 8 9 10) + +echo "--- Step 4: Bridging tokens one by one ---" +TOKEN_IDX=0 +for TOKEN_INFO in "${TOKENS[@]}"; do + IFS=: read -r ADDR NAME SYM DEC <<< "$TOKEN_INFO" + + BRIDGE_AMT="${BRIDGE_AMOUNTS[$TOKEN_IDX]}" + # Use cast to-wei with the token's decimals for precise conversion + BRIDGE_WEI=$(cast to-wei "$BRIDGE_AMT" "$DEC") + + # bridgeAsset(uint32 destNet, address destAddr, uint256 amount, address token, bool forceUpdate, bytes permit) + CALLDATA=$(cast calldata "bridgeAsset(uint32,address,uint256,address,bool,bytes)" \ + "$DEST_NET" \ + "$DEST" \ + "$BRIDGE_WEI" \ + "$ADDR" \ + true \ + "0x") + + RESULT=$(cast send "$BRIDGE_ADDRESS" "$CALLDATA" \ + --private-key "$PRIVATE_KEY" \ + --rpc-url "$L1_RPC" \ + --nonce "$NONCE" \ + --gas-limit 300000 \ + --json 2>&1) + + TX=$(echo "$RESULT" | jq -r '.transactionHash // empty') + STATUS=$(echo "$RESULT" | jq -r '.status // "fail"') + BLOCK=$(echo "$RESULT" | jq -r '.blockNumber // "?"') + + if [[ "$STATUS" == "0x1" ]]; then + echo " ✓ $SYM: bridged $BRIDGE_AMT tokens (${DEC}dec, ${BRIDGE_WEI}wei), block=$((BLOCK)), tx=${TX:0:18}..." + else + echo " ✗ $SYM: FAILED — $(echo "$RESULT" | head -1)" + fi + NONCE=$((NONCE + 1)) + TOKEN_IDX=$((TOKEN_IDX + 1)) +done +echo "" + +echo "=== Done ===" +echo "Tokens deployed and bridged. Monitor with:" +echo " PG=\$(docker ps --filter 'name=postgres-001' --format '{{.Names}}' | grep -v agglayer | head -1)" +echo " docker exec \$PG psql -U master_user -d bridge_db -c \"SELECT id, deposit_cnt, amount, ready_for_claim FROM sync.deposit WHERE dest_net=1 ORDER BY id DESC LIMIT 15;\"" + +# Cleanup +rm -rf "$TMPDIR" diff --git a/scripts/bridge-out-test.sh b/scripts/bridge-out-test.sh new file mode 100755 index 0000000..b16193c --- /dev/null +++ b/scripts/bridge-out-test.sh @@ -0,0 +1,227 @@ +#!/usr/bin/env bash +# +# Bridge-Out Test — L2→L1 withdrawal flow +# +# Tests the full B2AGG bridge-out flow: +# 1. Claim a P2ID note (gives wallet a balance) +# 2. Create B2AGG note (withdraws to L1) +# 3. Verify BridgeEvent is emitted by the proxy +# 4. Verify bridge-service picks it up +# +# Prerequisites: +# - Kurtosis miden-cdk enclave running +# - At least one unclaimed P2ID note on Miden (from prior L1→L2 deposit) +# - claim-note binary built (./scripts/claim-note.sh) +# +# Usage: ./scripts/bridge-out-test.sh [dest_address] +# dest_address - L1 destination (default: kurtosis funded account) + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" + +# L1 destination address for the withdrawal +DEST_ADDRESS="${1:-0xE34aaF64b29273B7D567FCFc40544c014EEe9970}" + +# Auto-detect proxy RPC +PROXY_RPC="${PROXY_RPC:-}" +if [[ -z "$PROXY_RPC" ]]; then + PROXY_RPC=$(kurtosis port print miden-cdk miden-proxy-001 rpc 2>/dev/null || echo "") +fi +[[ -z "$PROXY_RPC" ]] && { echo "ERROR: Cannot find proxy RPC"; exit 1; } + +# Auto-detect Miden node RPC +MIDEN_RPC="${MIDEN_RPC:-}" +if [[ -z "$MIDEN_RPC" ]]; then + MIDEN_NODE_CONTAINER=$(docker ps --format '{{.Names}}' 2>/dev/null | grep -E 'miden-node' | head -1) + if [[ -n "$MIDEN_NODE_CONTAINER" ]]; then + MIDEN_PORT=$(docker port "$MIDEN_NODE_CONTAINER" 57291/tcp 2>/dev/null | head -1 | cut -d: -f2) + [[ -n "$MIDEN_PORT" ]] && MIDEN_RPC="http://localhost:$MIDEN_PORT" + fi +fi +[[ -z "$MIDEN_RPC" ]] && { echo "ERROR: Cannot find Miden node RPC"; exit 1; } + +echo "=========================================" +echo " Bridge-Out Test (L2 → L1)" +echo "=========================================" +echo "" +echo "Proxy RPC: $PROXY_RPC" +echo "Miden Node RPC: $MIDEN_RPC" +echo "L1 Dest: $DEST_ADDRESS" +echo "" + +# Get proxy accounts +ACCOUNTS=$(kurtosis service exec miden-cdk miden-proxy-001 "cat /var/lib/miden-agglayer-service/bridge_accounts.toml" 2>/dev/null | grep -v "^$") +BRIDGE_ID=$(echo "$ACCOUNTS" | grep "^bridge" | cut -d'"' -f2) +FAUCET_ETH=$(echo "$ACCOUNTS" | grep "faucet_eth" | cut -d'"' -f2) +WALLET_ID=$(echo "$ACCOUNTS" | grep "wallet_hardhat" | cut -d'"' -f2) + +echo "Bridge ID: $BRIDGE_ID" +echo "ETH Faucet: $FAUCET_ETH" +echo "Wallet: $WALLET_ID" +echo "" + +# ---- Step 1: Find an unclaimed P2ID note ---- +echo "--- Step 1: Finding unclaimed P2ID notes ---" + +# Use the claim-note tool to find consumable notes +echo "Using claim-note tool to discover notes..." +CLAIM_OUTPUT=$("$SCRIPT_DIR/claim-note.sh" address 2>&1) +CLAIMER_MIDEN=$(echo "$CLAIM_OUTPUT" | grep "Miden:" | awk '{print $2}') +echo "Claimer account: $CLAIMER_MIDEN" + +# Get the ETH faucet ID in hex +AGPG=$(docker ps --filter "name=miden-agglayer-postgres" --format "{{.Names}}" | head -1) +ETH_FAUCET_HEX=$(docker exec "$AGPG" psql -t -U agglayer -d agglayer_store -c \ + "SELECT faucet_id FROM faucet_registry WHERE symbol='ETH';" 2>&1 | tr -d ' ') +echo "ETH Faucet hex: $ETH_FAUCET_HEX" + +# ---- Step 2: Claim a P2ID note ---- +echo "" +echo "--- Step 2: Claiming a P2ID note (to get wallet balance) ---" + +# Find the first unclaimed ETH P2ID note by running claim-note +# The claim-note tool auto-discovers consumable notes +FIRST_NOTE_ID=$("$SCRIPT_DIR/claim-note.sh" address 2>&1 | tail -1 || true) + +# Actually we need to run claim and let it pick a note +echo "Running claim-note to consume first available P2ID note..." +CLAIM_RESULT=$("$SCRIPT_DIR/claim-note.sh" --rebuild claim auto 2>&1) || { + echo "claim-note failed, trying to find a specific note..." + # Fallback: get note ID from the node DB + CONTAINER=$(docker ps --filter "name=miden-node-001" --format '{{.ID}}' | head -1) + TMPDIR=$(mktemp -d) + docker cp "$CONTAINER:/app/data/miden-store.sqlite3" "$TMPDIR/db.sqlite3" + docker cp "$CONTAINER:/app/data/miden-store.sqlite3-wal" "$TMPDIR/db.sqlite3-wal" 2>/dev/null || true + + NOTE_ID=$(sqlite3 "$TMPDIR/db.sqlite3" " + SELECT '0x' || lower(hex(note_id)) FROM notes + WHERE consumed_at IS NULL + AND hex(script_root) = ( + SELECT hex(script_root) FROM notes + WHERE script_root IS NOT NULL + GROUP BY script_root + ORDER BY COUNT(*) DESC LIMIT 1 + ) + ORDER BY committed_at ASC LIMIT 1; + ") + rm -rf "$TMPDIR" + + echo "Found note: $NOTE_ID" + CLAIM_RESULT=$("$SCRIPT_DIR/claim-note.sh" claim "$NOTE_ID" 2>&1) +} +echo "$CLAIM_RESULT" | tail -5 +echo "✓ P2ID note claimed" + +# ---- Step 3: Create B2AGG note (bridge-out) ---- +echo "" +echo "--- Step 3: Creating B2AGG bridge-out note ---" + +# The bridge-out-tool runs inside the proxy container using the proxy's store +# We need: wallet-id, bridge-id, faucet-id, amount, dest-address +# The wallet that claimed the P2ID note is the claimer account, but the +# bridge-out-tool uses the proxy's internal store. We need to run it +# from the proxy container. + +# Actually the bridge-out-tool needs its own wallet. The proxy's wallet_hardhat +# might not have balance. We need to use the claimer wallet's store. +# +# Alternative: exec bridge-out-tool from the claim-note's store directory. +# But bridge-out-tool is in the proxy container, not on the host. +# +# Simplest: copy bridge-out-tool out and run it on the host with the claimer's store. + +echo "Setting up bridge-out-tool..." + +# Extract the bridge-out-tool binary +TMPDIR=$(mktemp -d) +PROXY_CONTAINER=$(docker ps --filter "name=miden-proxy-001" --format '{{.ID}}' | head -1) +docker cp "$PROXY_CONTAINER:/usr/local/bin/bridge-out-tool" "$TMPDIR/bridge-out-tool" 2>/dev/null || { + echo "ERROR: Cannot extract bridge-out-tool from proxy container" + exit 1 +} +chmod +x "$TMPDIR/bridge-out-tool" + +# The claim-note tool creates its store in /tmp/miden-claimer-* +# Find the latest claimer store +CLAIMER_STORE=$(ls -td /tmp/miden-claimer-* 2>/dev/null | head -1) +if [[ -z "$CLAIMER_STORE" ]]; then + echo "ERROR: No claimer store found. Run claim-note first." + exit 1 +fi +echo "Claimer store: $CLAIMER_STORE" + +# Get the bridge and faucet IDs in hex format +# The bridge-out-tool expects hex account IDs +echo "Running bridge-out-tool..." +echo " wallet: $CLAIMER_MIDEN" +echo " bridge: $BRIDGE_ID" +echo " faucet: $ETH_FAUCET_HEX" +echo " amount: 100000 (0.001 ETH in Miden 8-decimal units)" +echo " dest: $DEST_ADDRESS" + +"$TMPDIR/bridge-out-tool" \ + --store-dir "$CLAIMER_STORE" \ + --node-url "$MIDEN_RPC" \ + --wallet-id "$CLAIMER_MIDEN" \ + --bridge-id "$BRIDGE_ID" \ + --faucet-id "$ETH_FAUCET_HEX" \ + --amount 100000 \ + --dest-address "$DEST_ADDRESS" \ + --dest-network 0 2>&1 || { + echo "" + echo "bridge-out-tool failed. This may be because:" + echo " - The wallet hasn't consumed any P2ID notes yet" + echo " - The wallet doesn't have sufficient balance" + echo " - The account IDs are in wrong format" + echo "" + echo "Trying with bech32 IDs..." + "$TMPDIR/bridge-out-tool" \ + --store-dir "$CLAIMER_STORE" \ + --node-url "$MIDEN_RPC" \ + --wallet-id "0x$CLAIMER_MIDEN" \ + --bridge-id "$(echo "$BRIDGE_ID" | sed 's/mlcl1/0x/')" \ + --faucet-id "$ETH_FAUCET_HEX" \ + --amount 100000 \ + --dest-address "$DEST_ADDRESS" \ + --dest-network 0 2>&1 +} + +echo "✓ B2AGG note created" + +# ---- Step 4: Wait for BridgeEvent ---- +echo "" +echo "--- Step 4: Waiting for BridgeEvent emission ---" +echo "Waiting 60s for proxy to detect consumed B2AGG note..." +sleep 60 + +# Check for BridgeEvent in proxy logs +echo "Checking proxy logs for BridgeEvent..." +BRIDGE_EVENTS=$(kurtosis service logs miden-cdk miden-proxy-001 --all 2>&1 | grep -i "bridge.*event\|BridgeEvent\|bridge_out\|B2AGG\|b2agg" | tail -10) +if [[ -n "$BRIDGE_EVENTS" ]]; then + echo "$BRIDGE_EVENTS" + echo "✓ BridgeEvent detected" +else + echo "No BridgeEvent found yet. May need more time." +fi + +# Check synthetic logs in proxy DB +echo "" +echo "Checking synthetic logs for BridgeEvent..." +AGPG=$(docker ps --filter "name=miden-agglayer-postgres" --format "{{.Names}}" | head -1) +docker exec "$AGPG" psql -U agglayer -d agglayer_store -c \ + "SELECT id, block_number, transaction_hash, created_at FROM synthetic_logs WHERE topics[1] LIKE '%501781%' OR address ILIKE '%bridge%' ORDER BY id DESC LIMIT 5;" 2>&1 + +# ---- Step 5: Check bridge-service ---- +echo "" +echo "--- Step 5: Checking bridge-service for BridgeEvent ---" +kurtosis service logs miden-cdk zkevm-bridge-service-001 2>&1 | grep -i "bridge.*event\|deposit.*l2\|network.*2\|withdrawal" | tail -5 + +echo "" +echo "=========================================" +echo " Bridge-Out Test Complete" +echo "=========================================" + +# Cleanup +rm -rf "$TMPDIR" diff --git a/scripts/check-bridge-events.sh b/scripts/check-bridge-events.sh new file mode 100755 index 0000000..19f2d6b --- /dev/null +++ b/scripts/check-bridge-events.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# +# check-bridge-events.sh — Check BridgeEvent synthetic logs on the proxy via eth_getLogs +# +# Usage: ./scripts/check-bridge-events.sh +# +# Shows all BridgeEvents (L2->L1 withdrawals) emitted by the proxy. +# Also shows GER events and ClaimEvents for context. + +set -euo pipefail + +PROXY_RPC="${PROXY_RPC:-}" +if [[ -z "$PROXY_RPC" ]]; then + PROXY_RPC=$(kurtosis port print miden-cdk miden-proxy-001 rpc 2>/dev/null || echo "") +fi +[[ -z "$PROXY_RPC" ]] && { echo "ERROR: Cannot find proxy RPC"; exit 1; } + +# Topics +BRIDGE_EVENT_TOPIC="0x501781209a1f8899323b96b4ef08b168df93e0a90c673d1e4cce39366cb62f9b" +CLAIM_EVENT_TOPIC="0x1df3f2a973a00d6635911755c260704e95e8a5876997546798770f76396fda4d" +GER_EVENT_TOPIC="0x65d3bf36615f1f02a134d12dfa9ea6b1d4a52386e825973cd27ddb70895c2319" + +LATEST=$(curl -s "$PROXY_RPC" -X POST -H 'Content-Type: application/json' \ + -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' | jq -r '.result') + +echo "=== Proxy Synthetic Events (eth_getLogs) ===" +echo "Proxy RPC: $PROXY_RPC" +echo "Latest block: $LATEST ($(printf '%d' "$LATEST"))" +echo "" + +# BridgeEvents +BRIDGE_EVENTS=$(curl -s "$PROXY_RPC" -X POST -H 'Content-Type: application/json' \ + -d "{\"jsonrpc\":\"2.0\",\"method\":\"eth_getLogs\",\"params\":[{\"fromBlock\":\"0x0\",\"toBlock\":\"$LATEST\",\"topics\":[\"$BRIDGE_EVENT_TOPIC\"]}],\"id\":2}") +BRIDGE_COUNT=$(echo "$BRIDGE_EVENTS" | jq '.result | length') +echo "BridgeEvents (L2->L1): $BRIDGE_COUNT" +if [[ "$BRIDGE_COUNT" -gt 0 ]]; then + echo "$BRIDGE_EVENTS" | jq -r '.result[] | " block=\(.blockNumber) tx=\(.transactionHash[0:18])..."' +fi +echo "" + +# ClaimEvents +CLAIM_COUNT=$(curl -s "$PROXY_RPC" -X POST -H 'Content-Type: application/json' \ + -d "{\"jsonrpc\":\"2.0\",\"method\":\"eth_getLogs\",\"params\":[{\"fromBlock\":\"0x0\",\"toBlock\":\"$LATEST\",\"topics\":[\"$CLAIM_EVENT_TOPIC\"]}],\"id\":3}" \ + | jq '.result | length') +echo "ClaimEvents (L1->L2): $CLAIM_COUNT" + +# GER events +GER_COUNT=$(curl -s "$PROXY_RPC" -X POST -H 'Content-Type: application/json' \ + -d "{\"jsonrpc\":\"2.0\",\"method\":\"eth_getLogs\",\"params\":[{\"fromBlock\":\"0x0\",\"toBlock\":\"$LATEST\",\"topics\":[\"$GER_EVENT_TOPIC\"]}],\"id\":4}" \ + | jq '.result | length') +echo "GER events: $GER_COUNT" + +echo "" +echo "Total synthetic logs: $((BRIDGE_COUNT + CLAIM_COUNT + GER_COUNT))" + +# Proxy DB cross-check +AGPG=$(docker ps --filter "name=miden-agglayer-postgres" --format "{{.Names}}" | head -1) +if [[ -n "$AGPG" ]]; then + echo "" + echo "=== Proxy DB: bridge_out_processed ===" + docker exec "$AGPG" psql -U agglayer -d agglayer_store -c \ + "SELECT deposit_count, created_at FROM bridge_out_processed ORDER BY deposit_count;" 2>&1 +fi diff --git a/scripts/check-certificates.sh b/scripts/check-certificates.sh new file mode 100755 index 0000000..213b59b --- /dev/null +++ b/scripts/check-certificates.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +# +# check-certificates.sh — Check AggLayer certificate status +# +# Usage: ./scripts/check-certificates.sh [--all] +# +# Shows certificates sent by aggsender, their settlement status, +# and breakdown of exits (L2->L1) vs imported_exits (L1->L2). +# +# Options: +# --all Show all certificates (default: last 10) + +set -euo pipefail + +SHOW_ALL=false +[[ "${1:-}" == "--all" ]] && SHOW_ALL=true + +echo "=== AggLayer Certificates ===" +echo "" + +# Get all cert lines +CERT_LINES=$(kurtosis service logs miden-cdk aggkit-001 -a 2>&1 | grep "certificate.*sent successfully") +TOTAL=$(echo "$CERT_LINES" | wc -l | tr -d ' ') + +echo "Total certificates sent: $TOTAL" +echo "" + +# Parse and display +echo "Height | Block Range | Exits | Imports | Status" +echo "-------|-------------------|-------|---------|-------" + +SETTLED_LINES=$(kurtosis service logs miden-cdk aggkit-001 -a 2>&1 | grep "status: Settled") + +echo "$CERT_LINES" | while read -r line; do + HEIGHT=$(echo "$line" | grep -o 'height: [0-9]*' | cut -d' ' -f2) + FROM=$(echo "$line" | grep -o 'from block: [0-9]*' | cut -d' ' -f3) + TO=$(echo "$line" | grep -o 'to block: [0-9]*' | cut -d' ' -f3) + EXITS=$(echo "$line" | grep -o 'exits: [0-9]*' | head -1 | cut -d' ' -f2) + IMPORTS=$(echo "$line" | grep -o 'imported_exits: [0-9]*' | cut -d' ' -f2) + + # Check if settled + CERT_ID=$(echo "$line" | grep -o 'certificate: [0-9]*/0x[a-f0-9]*' | cut -d' ' -f2) + STATUS="Pending" + if echo "$SETTLED_LINES" | grep -q "${HEIGHT}/"; then + STATUS="Settled" + fi + + if $SHOW_ALL || [[ "$HEIGHT" -ge $((TOTAL - 10)) ]]; then + printf "%-6s | %-17s | %-5s | %-7s | %s\n" "$HEIGHT" "$FROM - $TO" "$EXITS" "$IMPORTS" "$STATUS" + fi +done + +if ! $SHOW_ALL && [[ "$TOTAL" -gt 10 ]]; then + echo "" + echo "(showing last 10 of $TOTAL, use --all for full list)" +fi + +echo "" +echo "=== Summary ===" +EXITS_TOTAL=$(echo "$CERT_LINES" | grep -o 'exits: [0-9]*' | head -"$TOTAL" | awk '{s+=$2}END{print s}') +# That counts both exits and imported_exits. Let me do it properly +WITH_EXITS=$(echo "$CERT_LINES" | while read -r l; do echo "$l" | grep -o 'exits: [0-9]*' | head -1 | cut -d' ' -f2; done | awk '$1>0{c++}END{print c+0}') +WITH_IMPORTS=$(echo "$CERT_LINES" | while read -r l; do echo "$l" | grep -o 'imported_exits: [0-9]*' | cut -d' ' -f2; done | awk '$1>0{c++}END{print c+0}') + +echo "Certs with exits (L2->L1): $WITH_EXITS" +echo "Certs with imports (L1->L2): $WITH_IMPORTS" +echo "Certs empty: $((TOTAL - WITH_EXITS - WITH_IMPORTS))" diff --git a/scripts/check-l2-deposits.sh b/scripts/check-l2-deposits.sh new file mode 100755 index 0000000..9863d06 --- /dev/null +++ b/scripts/check-l2-deposits.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# +# check-l2-deposits.sh — Check bridge DB for L2->L1 deposits and their claim status +# +# Usage: ./scripts/check-l2-deposits.sh [--all] +# +# Shows deposits with dest_net=0 (L2->L1 withdrawals) from the bridge-service DB. +# Also shows L1->L2 summary for context. +# +# Options: +# --all Also show all L1->L2 deposits + +set -euo pipefail + +SHOW_ALL=false +[[ "${1:-}" == "--all" ]] && SHOW_ALL=true + +PG=$(docker ps --filter 'name=postgres-001' --format '{{.Names}}' | grep -v agglayer | head -1) +[[ -z "$PG" ]] && { echo "ERROR: postgres-001 not found"; exit 1; } + +echo "=== Bridge DB: L2 -> L1 Deposits (dest_net=0) ===" +echo "" +docker exec "$PG" psql -U master_user -d bridge_db -c \ + "SELECT deposit_cnt, dest_net, ready_for_claim, amount, network_id + FROM sync.deposit WHERE dest_net = 0 ORDER BY deposit_cnt;" 2>&1 + +L2_TOTAL=$(docker exec "$PG" psql -t -A -U master_user -d bridge_db -c \ + "SELECT COUNT(*) FROM sync.deposit WHERE dest_net = 0;") +L2_READY=$(docker exec "$PG" psql -t -A -U master_user -d bridge_db -c \ + "SELECT COUNT(*) FROM sync.deposit WHERE dest_net = 0 AND ready_for_claim;") + +echo "" +echo "=== Bridge DB: L1 -> L2 Summary ===" +L1_TOTAL=$(docker exec "$PG" psql -t -A -U master_user -d bridge_db -c \ + "SELECT COUNT(*) FROM sync.deposit WHERE dest_net = 1;") +L1_READY=$(docker exec "$PG" psql -t -A -U master_user -d bridge_db -c \ + "SELECT COUNT(*) FROM sync.deposit WHERE dest_net = 1 AND ready_for_claim;") +echo "L1->L2 deposits: $L1_READY/$L1_TOTAL ready_for_claim" + +if $SHOW_ALL; then + echo "" + docker exec "$PG" psql -U master_user -d bridge_db -c \ + "SELECT deposit_cnt, dest_net, ready_for_claim, amount + FROM sync.deposit WHERE dest_net = 1 ORDER BY deposit_cnt;" 2>&1 +fi + +echo "" +echo "=== Cross-Check ===" +echo "L2->L1: $L2_READY/$L2_TOTAL ready_for_claim" +echo "L1->L2: $L1_READY/$L1_TOTAL ready_for_claim" + +# Check proxy BridgeEvents +PROXY_RPC="${PROXY_RPC:-}" +if [[ -z "$PROXY_RPC" ]]; then + PROXY_RPC=$(kurtosis port print miden-cdk miden-proxy-001 rpc 2>/dev/null || echo "") +fi +if [[ -n "$PROXY_RPC" ]]; then + BRIDGE_EVENTS=$(curl -s "$PROXY_RPC" -X POST -H 'Content-Type: application/json' \ + -d '{"jsonrpc":"2.0","method":"eth_getLogs","params":[{"fromBlock":"0x0","toBlock":"latest","topics":["0x501781209a1f8899323b96b4ef08b168df93e0a90c673d1e4cce39366cb62f9b"]}],"id":1}' \ + | jq '.result | length') + echo "Proxy BridgeEvents: $BRIDGE_EVENTS" +fi + +echo "" +if [[ "$L2_TOTAL" = "$L2_READY" ]] && [[ "$L2_TOTAL" -gt 0 ]]; then + echo "All L2->L1 deposits are ready for claim on L1." +else + NOT_READY=$((L2_TOTAL - L2_READY)) + echo "$NOT_READY L2->L1 deposit(s) not yet ready_for_claim." + echo "This may be because:" + echo " - B2AGG note not yet consumed by bridge account (~30s)" + echo " - BridgeEvent not yet synced by bridge-service" + echo " - Certificate not yet settled on AggLayer" +fi diff --git a/scripts/e2e-kurtosis.sh b/scripts/e2e-kurtosis.sh index e341783..03cd360 100755 --- a/scripts/e2e-kurtosis.sh +++ b/scripts/e2e-kurtosis.sh @@ -19,8 +19,8 @@ # - kurtosis CLI: https://docs.kurtosis.com/install # - Docker running # - Miden images built: -# docker build -t miden-infra/miden-node:agglayer-v0.1 -f Dockerfile.miden-node . -# docker build -t miden-infra/miden-proxy:latest . +# docker build -t miden-infra/miden-node:v0.14.6 -f Dockerfile.miden-node . +# docker build https://github.com/gateway-fm/miden-agglayer.git#release/0.1 -t miden-infra/miden-proxy:release-0.1 set -euo pipefail @@ -87,18 +87,16 @@ check_prerequisites() { fi success "Docker running" - if ! docker image inspect miden-infra/miden-node:agglayer-v0.1 &>/dev/null; then - fail "miden-infra/miden-node:agglayer-v0.1 image not found. Build it first." + if ! docker image inspect miden-infra/miden-node:v0.14.6 &>/dev/null; then + fail "miden-infra/miden-node:v0.14.6 image not found. Build it first." fi success "Miden node image found" - if ! docker image inspect miden-infra/miden-proxy:latest &>/dev/null; then - log "Building miden-proxy image..." - docker build -t miden-infra/miden-proxy:latest -f "$PROJECT_DIR/Dockerfile" "$PROJECT_DIR" - success "Miden proxy image built" - else - success "Miden proxy image found" + if ! docker image inspect miden-infra/miden-proxy:release-0.1 &>/dev/null; then + fail "miden-infra/miden-proxy:release-0.1 image not found. Build from miden-agglayer: + docker build https://github.com/gateway-fm/miden-agglayer.git#release/0.1 -t miden-infra/miden-proxy:release-0.1" fi + success "Miden proxy image found (miden-agglayer)" if ! command -v cast &>/dev/null; then warn "foundry (cast) not found - deposit test will be skipped" @@ -141,6 +139,49 @@ deploy_miden_cdk() { success "Miden-CDK deployed" } +####################################### +# Fix Proposer Mismatch +####################################### + +fix_proposer() { + step "Fixing AggLayer Proposer" + + # The aggkit uses 0x0b68058E... as its signing key (from aggoracle.keystore). + # The AggLayer and L1 rollup contract default to a different address. + # Fix both so certificate submission works. + local AGGKIT_ADDR="0x0b68058E5b2592b1f472AdFe106305295A332A7C" + local ROLLUP_ADDR="0x414e9E227e4b589aF92200508aF5399576530E4e" + + # 1. Update L1 trustedSequencer + if [[ -n "$L1_RPC" ]]; then + cast send "$ROLLUP_ADDR" "setTrustedSequencer(address)" "$AGGKIT_ADDR" \ + --private-key "$KURTOSIS_PRIVATE_KEY" --rpc-url "$L1_RPC" --gas-limit 100000 --json > /dev/null 2>&1 \ + && success "L1 trustedSequencer → $AGGKIT_ADDR" \ + || warn "Failed to update L1 trustedSequencer" + fi + + # 2. Update AggLayer proof-signers config (only the proof-signers line, not all addresses) + kurtosis service exec "$ENCLAVE_NAME" agglayer \ + "sed -i '/\[proof-signers\]/{n;s|\"0x[a-fA-F0-9]*\"|\"'$AGGKIT_ADDR'\"|;}' /etc/agglayer/config.toml" 2>/dev/null \ + && success "AggLayer proof-signers → $AGGKIT_ADDR" \ + || warn "Failed to update AggLayer config" + + # 3. Update AggLayer full-node-rpcs to point to our proxy + kurtosis service exec "$ENCLAVE_NAME" agglayer \ + "sed -i 's|http://cdk-erigon-rpc-001:8545|http://miden-l2-forwarder-001:8545|' /etc/agglayer/config.toml" 2>/dev/null \ + && success "AggLayer full-node-rpcs → miden forwarder" \ + || warn "Failed to update AggLayer RPC endpoint" + + # 4. Restart agglayer + aggkit + kurtosis service stop "$ENCLAVE_NAME" agglayer > /dev/null 2>&1 + sleep 2 + kurtosis service start "$ENCLAVE_NAME" agglayer > /dev/null 2>&1 + kurtosis service stop "$ENCLAVE_NAME" aggkit-001 > /dev/null 2>&1 + sleep 2 + kurtosis service start "$ENCLAVE_NAME" aggkit-001 > /dev/null 2>&1 + success "Restarted agglayer + aggkit" +} + ####################################### # Get Service URLs ####################################### @@ -214,15 +255,21 @@ send_test_deposit() { # Distinctive amount: 0.1 ETH (scaled to 8 decimals for Miden) local amount="100000000000000000" # 0.1 ETH - log "Deposit: 0.1 ETH to Miden (network 2)" + log "Deposit: 0.1 ETH to Miden (network 1)" log "From: $KURTOSIS_ADDRESS" log "Bridge: $BRIDGE_ADDRESS" - # Encode bridgeAsset call + # Encode bridgeAsset call. + # dest_net=1: the rollup's l2NetworkID from rollup manager (rollupID), NOT the + # agglayer network ID (which is 2 in our setup). The bridge service's + # claimtxman calls UpdateL1DepositsStatus(ctx, ger.ExitRoots[0], tm.l2NetworkID, ...) + # where l2NetworkID is the rollupID. Depositing with dest_net=2 leaves the deposit + # stuck at ready_for_claim=false because the UPDATE's `dest_net = $2` clause never matches. + # destination_address uses the claimable address format. local calldata calldata=$(cast calldata "bridgeAsset(uint32,address,uint256,address,bool,bytes)" \ - 2 \ - "0x0000000000000000000000000000000000000001" \ + 1 \ + "0x00000000a417929a101b89100dda63bf4f692800" \ "$amount" \ "0x0000000000000000000000000000000000000000" \ true \ @@ -305,6 +352,7 @@ main() { deploy_miden_cdk get_service_urls test_proxy + fix_proposer if ! $SKIP_DEPOSIT; then send_test_deposit diff --git a/scripts/jsonrpc-snoop.py b/scripts/jsonrpc-snoop.py new file mode 100755 index 0000000..f7360e8 --- /dev/null +++ b/scripts/jsonrpc-snoop.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +"""JSON-RPC snooping reverse proxy with event decoding and block number tracking.""" +import sys, json, time, threading +from http.server import HTTPServer, BaseHTTPRequestHandler +from urllib.request import Request, urlopen +from urllib.error import URLError + +UPSTREAM = sys.argv[1] if len(sys.argv) > 1 else "http://127.0.0.1:62178" +LISTEN_PORT = int(sys.argv[2]) if len(sys.argv) > 2 else 9999 +LOG_FILE = sys.argv[3] if len(sys.argv) > 3 else "/tmp/jsonrpc-snoop.log" + +KNOWN_EVENTS = { + # keccak256("InsertGlobalExitRoot(bytes32,bytes32)") — sovereign chain GER injection + "0x65d3bf36615f1f02a134d12dfa9ea6b1d4a52386e825973cd27ddb70895c2319": + ("InsertGlobalExitRoot", ["bytes32 newGER", "bytes32 lastBlockHash"]), + # keccak256("ClaimEvent(uint256,uint32,address,address,uint256)") — from log_synthesis.rs + "0x1df3f2a973a00d6635911755c260704e95e8a5876997546798770f76396fda4d": + ("ClaimEvent", ["uint256 globalIndex", "uint32 originNet", "address originAddr", "address destAddr", "uint256 amount"]), + # keccak256("BridgeEvent(uint8,uint32,address,uint32,address,uint256,bytes,uint32)") — from log_synthesis.rs + "0x501781209a1f8899323b96b4ef08b168df93e0a90c673d1e4cce39366cb62f9b": + ("BridgeEvent", ["uint8 leafType", "uint32 originNet", "address originAddr", "uint32 destNet", "address destAddr", "uint256 amount"]), +} + +log_lock = threading.Lock() +log_fh = open(LOG_FILE, "a", buffering=1) + +def log(msg): + ts = time.strftime("%H:%M:%S", time.localtime()) + f".{int(time.time()*1000)%1000:03d}" + line = f"{ts} {msg}" + with log_lock: + log_fh.write(line + "\n") + print(line, flush=True) + +def hex2int(h): + try: return int(h, 16) + except: return h + +def decode_log_entry(entry): + topics = entry.get("topics", []) + block = entry.get("blockNumber", "?") + block_dec = hex2int(block) if isinstance(block, str) else block + tx = entry.get("transactionHash", "?") + topic0 = (topics[0].lower() if topics else "") + ev = KNOWN_EVENTS.get(topic0) + name = ev[0] if ev else f"unknown({topic0[:18]}...)" + indexed = [] + for t in topics[1:]: + indexed.append(t[:18] + "...") + return f" event block={block_dec} {name}({', '.join(indexed)}) tx={tx[:18]}..." + +class SnoopHandler(BaseHTTPRequestHandler): + def do_POST(self): + length = int(self.headers.get("Content-Length", 0)) + body = self.rfile.read(length) + try: req = json.loads(body) + except: req = {} + + method = req.get("method", "?") + params = req.get("params", []) + req_id = req.get("id", "?") + + # Format request based on method + if method == "eth_getLogs" and params: + p = params[0] if isinstance(params, list) and params else {} + fb = p.get("fromBlock", "?") + tb = p.get("toBlock", "?") + fb_dec = hex2int(fb) + tb_dec = hex2int(tb) + req_str = f">> eth_getLogs(from={fb_dec}, to={tb_dec})" + elif method == "eth_getBlockByNumber" and params: + bn = params[0] if isinstance(params, list) else "?" + bn_dec = hex2int(bn) if bn not in ("latest","pending","finalized","safe","earliest") else bn + req_str = f">> eth_getBlockByNumber({bn_dec})" + elif method == "eth_blockNumber": + req_str = f">> eth_blockNumber()" + elif method == "eth_sendRawTransaction": + req_str = f">> eth_sendRawTransaction(...)" + elif method == "eth_getTransactionReceipt": + tx = params[0][:18] + "..." if params else "?" + req_str = f">> eth_getTransactionReceipt({tx})" + else: + ps = json.dumps(params, separators=(",",":")) + if len(ps) > 60: ps = ps[:60] + "..." + req_str = f">> {method}({ps})" + + log(req_str) + + try: + upstream_req = Request(UPSTREAM, data=body, headers={"Content-Type": "application/json"}, method="POST") + with urlopen(upstream_req, timeout=120) as resp: + resp_body = resp.read() + status = resp.status + except Exception as e: + log(f"<< ERROR: {e}") + self.send_error(502, str(e)) + return + + try: resp_json = json.loads(resp_body) + except: resp_json = {} + + result = resp_json.get("result") + error = resp_json.get("error") + + # Format response based on method + if error: + msg = error.get("message", "")[:80] + log(f"<< {method} => ERROR: {msg}") + elif method == "eth_blockNumber": + log(f"<< eth_blockNumber => {hex2int(result)} ({result})") + elif method == "eth_getBlockByNumber": + num = result.get("number", "?") if isinstance(result, dict) else "?" + log(f"<< eth_getBlockByNumber => block {hex2int(num)}") + elif method == "eth_getLogs": + items = result if isinstance(result, list) else [] + log(f"<< eth_getLogs => [{len(items)} items]") + for entry in items: + log(decode_log_entry(entry)) + elif method == "eth_sendRawTransaction": + if isinstance(result, str): + log(f"<< eth_sendRawTransaction => {result[:22]}...") + else: + log(f"<< eth_sendRawTransaction => {result}") + elif method == "eth_getTransactionReceipt": + if isinstance(result, dict): + bn = result.get("blockNumber", "?") + st = result.get("status", "?") + log(f"<< eth_getTransactionReceipt => block={hex2int(bn)} status={st}") + else: + log(f"<< eth_getTransactionReceipt => {result}") + else: + r = str(result)[:60] if result else "null" + log(f"<< {method} => {r}") + + self.send_response(status) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(resp_body))) + self.end_headers() + self.wfile.write(resp_body) + + def log_message(self, *a): pass + +log(f"Snoop proxy: :{LISTEN_PORT} -> {UPSTREAM}") +server = HTTPServer(("0.0.0.0", LISTEN_PORT), SnoopHandler) +server.serve_forever() diff --git a/scripts/l2-bridge-out.sh b/scripts/l2-bridge-out.sh new file mode 100755 index 0000000..7aad1f4 --- /dev/null +++ b/scripts/l2-bridge-out.sh @@ -0,0 +1,103 @@ +#!/usr/bin/env bash +# +# l2-bridge-out.sh — Send a B2AGG bridge-out note (L2 -> L1) +# +# Usage: ./scripts/l2-bridge-out.sh [amount] [dest_address] +# +# amount - Miden token units (8-decimal). Default: 5000 (0.00005 ETH) +# dest_address - L1 destination. Default: kurtosis funded account +# +# Prerequisites: +# - Kurtosis miden-cdk running +# - claim-note.sh used to claim at least one P2ID note (wallet has balance) +# - MIDEN_STORE_PATH set to the claimer store (or auto-detected) +# +# Example: +# MIDEN_STORE_PATH=/tmp/miden-claimer-e2e ./scripts/l2-bridge-out.sh 100000 +# + +set -euo pipefail + +AMOUNT="${1:-5000}" +DEST_ADDRESS="${2:-0xE34aaF64b29273B7D567FCFc40544c014EEe9970}" + +# Auto-detect store +STORE_PATH="${MIDEN_STORE_PATH:-}" +if [[ -z "$STORE_PATH" ]]; then + STORE_PATH=$(ls -td /tmp/miden-claimer-* 2>/dev/null | head -1) +fi +if [[ -z "$STORE_PATH" || ! -f "$STORE_PATH/store.sqlite3" ]]; then + echo "ERROR: No claimer store found." + echo "Run: MIDEN_STORE_PATH=/tmp/my-store ./scripts/claim-note.sh claim " + echo "Then: MIDEN_STORE_PATH=/tmp/my-store ./scripts/l2-bridge-out.sh $AMOUNT" + exit 1 +fi + +# Get proxy container + config +PROXY_CONTAINER=$(docker ps --filter "name=miden-proxy-001" --format '{{.ID}}' | head -1) +[[ -z "$PROXY_CONTAINER" ]] && { echo "ERROR: miden-proxy-001 not running"; exit 1; } + +BRIDGE_ID=$(kurtosis service exec miden-cdk miden-proxy-001 \ + "cat /var/lib/miden-agglayer-service/bridge_accounts.toml" 2>/dev/null \ + | grep "^bridge" | cut -d'"' -f2) + +AGPG=$(docker ps --filter "name=miden-agglayer-postgres" --format "{{.Names}}" | head -1) +ETH_FAUCET=$(docker exec "$AGPG" psql -t -A -U agglayer -d agglayer_store -c \ + "SELECT faucet_id FROM faucet_registry WHERE symbol='ETH';") + +echo "=== L2 -> L1 Bridge-Out ===" +echo "Amount: $AMOUNT (Miden 8-dec units)" +echo "Destination: $DEST_ADDRESS" +echo "Store: $STORE_PATH" +echo "Bridge: $BRIDGE_ID" +echo "ETH Faucet: $ETH_FAUCET" +echo "" + +# Clean input_notes to avoid re-consume attempts +python3 -c " +import sqlite3 +conn = sqlite3.connect('$STORE_PATH/store.sqlite3') +conn.execute('DELETE FROM input_notes') +conn.commit() +conn.close() +" + +# Copy store to proxy container +docker cp "$STORE_PATH/." "$PROXY_CONTAINER:/tmp/claimer-store/" 2>&1 > /dev/null + +echo "Submitting B2AGG note..." +RESULT=$(docker exec "$PROXY_CONTAINER" bridge-out-tool \ + --store-dir /tmp/claimer-store \ + --node-url http://miden-node-001:57291 \ + --wallet-id 0xa417929a101b89100dda63bf4f6928 \ + --bridge-id "$BRIDGE_ID" \ + --faucet-id "$ETH_FAUCET" \ + --amount "$AMOUNT" \ + --dest-address "$DEST_ADDRESS" \ + --dest-network 0 2>&1) + +echo "$RESULT" + +if echo "$RESULT" | grep -q "done"; then + # Save updated store back + docker cp "$PROXY_CONTAINER:/tmp/claimer-store/store.sqlite3" "$STORE_PATH/store.sqlite3" + echo "" + echo "Bridge-out committed." + echo "" + echo "Waiting 15s for NTX builder to consume the B2AGG note..." + echo "(This prevents the next bridge-out from hitting a poisoned block)" + sleep 15 + echo "Ready for next bridge-out." + echo "" + echo "Monitor with:" + echo " ./scripts/check-bridge-events.sh" + echo " ./scripts/check-certificates.sh" + echo " ./scripts/check-l2-deposits.sh" +else + echo "" + echo "Bridge-out FAILED. Common causes:" + echo " - 'not committed': NTX builder poisoned the block, retry in 15s" + echo " - 'storage error': stale store, retry (sync catches up)" + echo " - 'RPC error': stale commitment, retry after a few seconds" + echo " - 'insufficient balance': claim more P2ID notes first" +fi diff --git a/scripts/list-notes.sh b/scripts/list-notes.sh index 2a78c8e..08bbcb7 100755 --- a/scripts/list-notes.sh +++ b/scripts/list-notes.sh @@ -1,44 +1,176 @@ #!/usr/bin/env bash -# List all notes from the Miden node's SQLite database. -# Usage: ./scripts/list-notes.sh [enclave] [service] -# enclave - Kurtosis enclave name (default: miden-cdk) -# service - Miden node service name (default: miden-node-001) +# List notes from the Miden node with P2ID/CLAIM detection and faucet info. +# +# Usage: ./scripts/list-notes.sh [options] +# --p2id Show only P2ID notes (output from faucet to recipient) +# --claims Show only CLAIM notes (input to faucet) +# --unconsumed Show only unconsumed notes +# --consumed Show only consumed notes +# --all Show all notes (default) +# --faucets Show faucet registry from proxy DB +# +# Examples: +# ./scripts/list-notes.sh --p2id --unconsumed # P2ID notes waiting to be claimed +# ./scripts/list-notes.sh --faucets # List known faucets set -euo pipefail -ENCLAVE="${1:-miden-cdk}" -SERVICE="${2:-miden-node-001}" -DB_PATH="/app/data/miden-store.sqlite3" +FILTER_TYPE="" +FILTER_CONSUMED="" +SHOW_FAUCETS=false + +while [[ $# -gt 0 ]]; do + case $1 in + --p2id) FILTER_TYPE="p2id"; shift ;; + --claims|--claim) FILTER_TYPE="claim"; shift ;; + --unconsumed) FILTER_CONSUMED="no"; shift ;; + --consumed) FILTER_CONSUMED="yes"; shift ;; + --all) FILTER_TYPE=""; FILTER_CONSUMED=""; shift ;; + --faucets) SHOW_FAUCETS=true; shift ;; + *) echo "Unknown option: $1"; exit 1 ;; + esac +done + +if $SHOW_FAUCETS; then + AGPG=$(docker ps --filter "name=miden-agglayer-postgres" --format "{{.Names}}" | head -1) + if [[ -z "$AGPG" ]]; then + echo "ERROR: miden-agglayer-postgres container not found" >&2 + exit 1 + fi + echo "=== Faucet Registry ===" + docker exec "$AGPG" psql -U agglayer -d agglayer_store -c \ + "SELECT faucet_id, symbol, origin_decimals as l1_dec, miden_decimals as miden_dec, scale, encode(origin_address, 'hex') as l1_token FROM faucet_registry ORDER BY created_at;" 2>&1 + echo "" +fi + +CONTAINER=$(docker ps --filter "name=miden-node-001" --format '{{.ID}}' | head -1) +if [[ -z "$CONTAINER" ]]; then + echo "ERROR: miden-node-001 container not found" >&2 + exit 1 +fi +DB_PATH="/app/data/miden-store.sqlite3" TMPDIR=$(mktemp -d) trap 'rm -rf "$TMPDIR"' EXIT -CONTAINER=$(docker ps --filter "name=$SERVICE" --format '{{.ID}}' | head -1) -if [ -z "$CONTAINER" ]; then - echo "Error: container matching '$SERVICE' not found" >&2 - exit 1 +docker cp "$CONTAINER:$DB_PATH" "$TMPDIR/db.sqlite3" +docker cp "$CONTAINER:${DB_PATH}-wal" "$TMPDIR/db.sqlite3-wal" 2>/dev/null || true +docker cp "$CONTAINER:${DB_PATH}-shm" "$TMPDIR/db.sqlite3-shm" 2>/dev/null || true + +# Get faucet IDs from proxy DB for labeling +AGPG=$(docker ps --filter "name=miden-agglayer-postgres" --format "{{.Names}}" 2>/dev/null | head -1) +FAUCET_MAP="" +if [[ -n "$AGPG" ]]; then + FAUCET_MAP=$(docker exec "$AGPG" psql -t -U agglayer -d agglayer_store -c \ + "SELECT UPPER(REPLACE(faucet_id, '0x', '')) || '=' || symbol FROM faucet_registry;" 2>&1 | tr -d ' ' | grep '=') +fi + +# Identify known script roots +# These are discovered by grouping note script_root values +P2ID_ROOT=$(sqlite3 "$TMPDIR/db.sqlite3" " + SELECT hex(script_root) FROM notes + WHERE script_root IS NOT NULL + GROUP BY script_root + ORDER BY COUNT(*) DESC + LIMIT 1; +") + +CLAIM_ROOT=$(sqlite3 "$TMPDIR/db.sqlite3" " + SELECT hex(script_root) FROM notes + WHERE script_root IS NOT NULL AND hex(script_root) != '$P2ID_ROOT' + GROUP BY script_root + ORDER BY COUNT(*) DESC + LIMIT 1; +") + +# Build WHERE clause +WHERE="1=1" +if [[ "$FILTER_TYPE" == "p2id" && -n "$P2ID_ROOT" ]]; then + WHERE="$WHERE AND hex(script_root) = '$P2ID_ROOT'" +elif [[ "$FILTER_TYPE" == "claim" && -n "$CLAIM_ROOT" ]]; then + WHERE="$WHERE AND hex(script_root) = '$CLAIM_ROOT'" fi +if [[ "$FILTER_CONSUMED" == "yes" ]]; then + WHERE="$WHERE AND consumed_at IS NOT NULL" +elif [[ "$FILTER_CONSUMED" == "no" ]]; then + WHERE="$WHERE AND consumed_at IS NULL" +fi + +echo "=== Notes ===" +[[ -n "$P2ID_ROOT" ]] && echo "P2ID script root: ${P2ID_ROOT:0:16}..." +[[ -n "$CLAIM_ROOT" ]] && echo "CLAIM script root: ${CLAIM_ROOT:0:16}..." +echo "" + +# Query and format +RESULTS=$(sqlite3 -separator '|' "$TMPDIR/db.sqlite3" " + SELECT + hex(note_id), + hex(sender), + COALESCE(hex(target_account_id), ''), + tag, + note_type, + committed_at, + COALESCE(consumed_at, 0), + COALESCE(hex(script_root), '') + FROM notes + WHERE $WHERE + ORDER BY committed_at, batch_index, note_index; +") + +if [[ -z "$RESULTS" ]]; then + echo "(no notes matching filter)" + exit 0 +fi + +printf "%-6s %-18s %-16s %-16s %-8s %-6s %-5s %s\n" \ + "TYPE" "NOTE_ID" "SENDER" "TARGET" "FAUCET" "BLOCK" "CONS" "TAG" +printf "%-6s %-18s %-16s %-16s %-8s %-6s %-5s %s\n" \ + "------" "------------------" "----------------" "----------------" "--------" "------" "-----" "---" + +echo "$RESULTS" | while IFS='|' read -r NOTE_ID SENDER TARGET TAG NOTE_TYPE BLOCK CONSUMED SCRIPT_ROOT; do + # Determine note type + TYPE="???" + if [[ "$SCRIPT_ROOT" == "$P2ID_ROOT" ]]; then + TYPE="P2ID" + elif [[ "$SCRIPT_ROOT" == "$CLAIM_ROOT" ]]; then + TYPE="CLAIM" + elif [[ -n "$SCRIPT_ROOT" ]]; then + TYPE="OTHER" + fi + + # Consumed? + CONS="no" + [[ "$CONSUMED" != "0" ]] && CONS="$CONSUMED" + + # Match sender to faucet name + FAUCET="" + SENDER_UPPER=$(echo "$SENDER" | tr '[:lower:]' '[:upper:]') + while IFS= read -r mapping; do + FAUCET_HEX="${mapping%%=*}" + FAUCET_SYM="${mapping#*=}" + if [[ "$SENDER_UPPER" == "$FAUCET_HEX" ]]; then + FAUCET="$FAUCET_SYM" + break + fi + done <<< "$FAUCET_MAP" + + # Also check target for faucet name + if [[ -z "$FAUCET" && -n "$TARGET" ]]; then + TARGET_UPPER=$(echo "$TARGET" | tr '[:lower:]' '[:upper:]') + while IFS= read -r mapping; do + FAUCET_HEX="${mapping%%=*}" + FAUCET_SYM="${mapping#*=}" + if [[ "$TARGET_UPPER" == "$FAUCET_HEX" ]]; then + FAUCET="→$FAUCET_SYM" + break + fi + done <<< "$FAUCET_MAP" + fi + + printf "%-6s %-18s %-16s %-16s %-8s %-6s %-5s %s\n" \ + "$TYPE" "${NOTE_ID:0:18}" "${SENDER:0:16}" "${TARGET:0:16}" "$FAUCET" "$BLOCK" "$CONS" "$TAG" +done -# Copy DB + WAL + SHM for consistent read -docker cp "$CONTAINER:$DB_PATH" "$TMPDIR/miden-store.sqlite3" -docker cp "$CONTAINER:${DB_PATH}-wal" "$TMPDIR/miden-store.sqlite3-wal" 2>/dev/null || true -docker cp "$CONTAINER:${DB_PATH}-shm" "$TMPDIR/miden-store.sqlite3-shm" 2>/dev/null || true - -sqlite3 -header -column "$TMPDIR/miden-store.sqlite3" " -SELECT - hex(note_id) as note_id, - CASE WHEN consumed_at IS NOT NULL THEN 'YES' ELSE 'NO' END as consumed, - coalesce(consumed_at, '') as consumed_at, - hex(sender) as sender, - coalesce(hex(target_account_id), '(none)') as target, - tag, - CASE note_type - WHEN 1 THEN 'Public' - WHEN 2 THEN 'Private' - WHEN 3 THEN 'Encrypted' - ELSE cast(note_type as text) - END as type, - committed_at as block -FROM notes -ORDER BY committed_at, batch_index, note_index; -" +echo "" +TOTAL=$(echo "$RESULTS" | wc -l | tr -d ' ') +echo "Total: $TOTAL notes" diff --git a/scripts/send-deposit.sh b/scripts/send-deposit.sh index 024be60..e7090bf 100755 --- a/scripts/send-deposit.sh +++ b/scripts/send-deposit.sh @@ -53,8 +53,8 @@ AMOUNT_ETH="${1:-0.01}" # Destination: optional Miden address DEST_MIDEN="${2:-}" -# Convert to wei -AMOUNT_WEI=$(echo "$AMOUNT_ETH * 1000000000000000000" | bc | cut -d'.' -f1) +# Convert to wei (use cast to avoid floating point precision issues) +AMOUNT_WEI=$(cast to-wei "$AMOUNT_ETH" ether) # Kurtosis funded account PRIVATE_KEY="0x12d7de8621a77640c9241b2595ba78ce443d05e94090365ab3bb5e19df82c625" @@ -69,15 +69,8 @@ else echo "Destination: $FROM_ADDRESS (sender's address)" fi -# Bridge contract address - get from env, Docker (proxy), or kurtosis +# Bridge contract address - get from env or kurtosis BRIDGE_ADDRESS="${BRIDGE_ADDRESS:-}" -if [[ -z "$BRIDGE_ADDRESS" ]]; then - # Try reading from the proxy container's env (works even if kurtosis tracking is broken) - _proxy=$(docker ps --format '{{.Names}}' 2>/dev/null | grep -E '^miden-proxy' | head -1) - if [[ -n "$_proxy" ]]; then - BRIDGE_ADDRESS=$(docker exec "$_proxy" printenv BRIDGE_ADDRESS 2>/dev/null || true) - fi -fi if [[ -z "$BRIDGE_ADDRESS" ]]; then # Fallback: kurtosis combined.json BRIDGE_ADDRESS=$(kurtosis service exec miden-cdk contracts-001 "cat /opt/output/combined.json" 2>/dev/null | jq -r '.polygonZkEVMBridgeAddress // empty') @@ -88,8 +81,8 @@ if [[ -z "$BRIDGE_ADDRESS" ]]; then exit 1 fi -# Destination network (Miden = 2) -DEST_NETWORK=2 +# Destination network (Miden = rollup 1 in the bridge, NOT chainId=2) +DEST_NETWORK=1 # Get L1 RPC - try Docker first (faster), then kurtosis L1_RPC="${L1_RPC:-}" diff --git a/scripts/stress-status.sh b/scripts/stress-status.sh new file mode 100755 index 0000000..71638ed --- /dev/null +++ b/scripts/stress-status.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# stress-status.sh — one-line deposit/claim pipeline snapshot. +# +# Usage: +# ./scripts/stress-status.sh # single snapshot +# watch -n 5 ./scripts/stress-status.sh +# +# Env: +# DEST_NET bridge dest_net to count (default: 1) +# WINDOW docker-logs --since window for rate/error counts (default: 1m) + +set -uo pipefail + +DEST_NET="${DEST_NET:-1}" +WINDOW="${WINDOW:-1m}" + +BP=$(docker ps --format '{{.Names}}' | grep -E '^postgres-001--' | head -1) +PX=$(docker ps --format '{{.Names}}' | grep -E '^miden-proxy-001--' | head -1) + +if [[ -z "$BP" || -z "$PX" ]]; then + echo "ERROR: bridge postgres or proxy container not found (is kurtosis enclave running?)" >&2 + exit 1 +fi + +q() { + docker exec "$BP" psql -U bridge_user -d bridge_db -At -c "$1" 2>/dev/null +} + +DEP=$(q "SELECT count(*) FROM sync.deposit WHERE dest_net=$DEST_NET;") +RDY=$(q "SELECT count(*) FROM sync.deposit WHERE dest_net=$DEST_NET AND ready_for_claim=true;") +MON=$(q "SELECT count(*) FROM sync.monitored_txs;") +CLA=$(q "SELECT count(*) FROM sync.claim;") +LATEST=$(q "SELECT index FROM sync.claim ORDER BY index DESC LIMIT 1;") + +LOGS=$(docker logs --since "$WINDOW" "$PX" 2>&1) +RATE=$(printf '%s\n' "$LOGS" | grep -c "submitted claim note txn" || true) +DNS=$(printf '%s\n' "$LOGS" | grep -c "dns error" || true) +NO_ACCT=$(printf '%s\n' "$LOGS" | grep -c "no known Miden AccountId" || true) +OTHER_ERR=$(printf '%s\n' "$LOGS" | grep "\[31mERROR\[0m" | grep -vE "no known Miden AccountId|dns error" | wc -l | tr -d ' ') + +printf '%s deposits=%s ready=%s queued=%s claimed=%s latest_idx=%s last%s: +%s claims, %s dns, %s no-acct, %s other-err\n' \ + "$(date +%H:%M:%S)" "$DEP" "$RDY" "$MON" "$CLA" "${LATEST:-none}" \ + "$WINDOW" "$RATE" "$DNS" "$NO_ACCT" "$OTHER_ERR" diff --git a/scripts/stress-test.sh b/scripts/stress-test.sh index 60d1b10..82df2cb 100755 --- a/scripts/stress-test.sh +++ b/scripts/stress-test.sh @@ -9,7 +9,7 @@ NUM_DEPOSITS="${1:-150}" AMOUNT_WEI="1000000000000000" # 0.001 ETH each PRIVATE_KEY="0x12d7de8621a77640c9241b2595ba78ce443d05e94090365ab3bb5e19df82c625" FROM_ADDRESS="0xE34aaF64b29273B7D567FCFc40544c014EEe9970" -DEST_NETWORK=2 +DEST_NETWORK=1 # Auto-detect ports L1_PORT=$(docker port $(docker ps --filter "name=el-1-geth" -q) 8545 | cut -d: -f2) @@ -25,13 +25,24 @@ echo "Bridge: $BRIDGE_ADDRESS" echo "Amount: 0.001 ETH per deposit" echo "" -# Generate unique destination addresses (deterministic from index) -# Each address: 0x00000000 + keccak256("stress_test_")[0:16] + 00 +# Generate unique destination addresses (deterministic from index). +# Each address must embed a valid Miden AccountId: zero-pad the derived +# id with 4 leading zero bytes. Random keccak bytes fail Miden's AccountId +# bit validation (version/type/storage mode bits), so we derive a real +# AccountId via the claim-note binary instead of hashing. +# +# derive-address does NOT deploy the account — it only computes the id +# locally from CLAIMER_SEED, so this is cheap (~1s per call). +CLAIM_NOTE_BIN="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/target/release/claim-note" +if [[ ! -x "$CLAIM_NOTE_BIN" ]]; then + echo "ERROR: $CLAIM_NOTE_BIN not found. Build it: cargo build --release --bin claim-note" >&2 + exit 1 +fi + generate_dest_address() { local idx=$1 - # Use cast to compute keccak, take first 30 hex chars for the Miden part - local hash=$(cast keccak "stress_test_deposit_${idx}" 2>/dev/null | cut -c3-32) - echo "0x00000000${hash}00" + CLAIMER_SEED="stress_test_deposit_${idx}" "$CLAIM_NOTE_BIN" derive-address 2>/dev/null \ + | awk '/Eth:/ {print $2}' } # Encode bridgeAsset calldata @@ -118,9 +129,9 @@ EXPECTED=$((SENT + 2)) # +2 for the deposits already in the system for attempt in $(seq 1 60); do TOTAL_DEPS=$(docker exec $(docker ps --filter 'name=postgres' -q) psql -U bridge_user -d bridge_db -t -c \ - "SELECT count(*) FROM sync.deposit WHERE dest_net = 2;" 2>/dev/null | tr -d ' ') + "SELECT count(*) FROM sync.deposit WHERE dest_net = 1;" 2>/dev/null | tr -d ' ') READY_DEPS=$(docker exec $(docker ps --filter 'name=postgres' -q) psql -U bridge_user -d bridge_db -t -c \ - "SELECT count(*) FROM sync.deposit WHERE dest_net = 2 AND ready_for_claim = true;" 2>/dev/null | tr -d ' ') + "SELECT count(*) FROM sync.deposit WHERE dest_net = 1 AND ready_for_claim = true;" 2>/dev/null | tr -d ' ') NOT_READY=$((TOTAL_DEPS - READY_DEPS)) echo " [${attempt}0s] Deposits: $TOTAL_DEPS total, $READY_DEPS ready, $NOT_READY pending" @@ -202,9 +213,9 @@ echo " Send time: ${SEND_TIME}s" echo "" echo "Bridge Sync:" FINAL_TOTAL=$(docker exec $(docker ps --filter 'name=postgres' -q) psql -U bridge_user -d bridge_db -t -c \ - "SELECT count(*) FROM sync.deposit WHERE dest_net = 2;" 2>/dev/null | tr -d ' ') + "SELECT count(*) FROM sync.deposit WHERE dest_net = 1;" 2>/dev/null | tr -d ' ') FINAL_READY=$(docker exec $(docker ps --filter 'name=postgres' -q) psql -U bridge_user -d bridge_db -t -c \ - "SELECT count(*) FROM sync.deposit WHERE dest_net = 2 AND ready_for_claim = true;" 2>/dev/null | tr -d ' ') + "SELECT count(*) FROM sync.deposit WHERE dest_net = 1 AND ready_for_claim = true;" 2>/dev/null | tr -d ' ') echo " Deposits detected: $FINAL_TOTAL" echo " Ready for claim: $FINAL_READY" echo " Not ready: $((FINAL_TOTAL - FINAL_READY))" diff --git a/src/agglayer_faucet.rs b/src/agglayer_faucet.rs index 0970555..6c6a438 100644 --- a/src/agglayer_faucet.rs +++ b/src/agglayer_faucet.rs @@ -4,7 +4,7 @@ //! to the Miden network. The agglayer faucet is required for processing CLAIM notes //! that mint tokens to destination accounts. -use miden_agglayer::{create_agglayer_faucet, create_bridge_account, EthAddressFormat}; +use miden_agglayer::{create_agglayer_faucet, create_bridge_account, EthAddress, MetadataHash}; use miden_client::keystore::FilesystemKeyStore; use miden_client::transaction::TransactionRequestBuilder; use miden_client::Client; @@ -40,9 +40,10 @@ pub async fn create_and_deploy_agglayer_faucet( configured_faucet_id_hex: &str, bridge_admin_id: AccountId, ger_manager_id: AccountId, - origin_token_address: &EthAddressFormat, + origin_token_address: &EthAddress, origin_network: u32, scale: u8, + metadata_hash: MetadataHash, ) -> Result { info!("╔══════════════════════════════════════════════════════════════════╗"); info!("║ Creating bridge account ║"); @@ -135,6 +136,7 @@ pub async fn create_and_deploy_agglayer_faucet( origin_token_address, origin_network, scale, + metadata_hash, ); let agglayer_faucet_id = agglayer_faucet.id(); diff --git a/src/bin/claim_note.rs b/src/bin/claim_note.rs index 2f45cf3..2150084 100644 --- a/src/bin/claim_note.rs +++ b/src/bin/claim_note.rs @@ -54,7 +54,7 @@ fn derive_key_pair(seed: &[u8; 32]) -> AuthSecretKey { let mut rng = ChaCha20Rng::from_seed(key_seed); // Generate the key pair deterministically - AuthSecretKey::new_falcon512_rpo_with_rng(&mut rng) + AuthSecretKey::new_falcon512_poseidon2_with_rng(&mut rng) } /// Derive a deterministic init seed for account creation @@ -77,7 +77,7 @@ async fn get_or_create_account( // Create auth component from the public key let auth_component: AccountComponent = - AuthSingleSig::new(key_pair.public_key().to_commitment(), AuthScheme::Falcon512Rpo).into(); + AuthSingleSig::new(key_pair.public_key().to_commitment(), AuthScheme::Falcon512Poseidon2).into(); // Build account let account = AccountBuilder::new(init_seed) @@ -161,7 +161,7 @@ async fn main() -> Result<(), Box> { let init_seed = derive_init_seed(&seed); let auth_component: AccountComponent = - AuthSingleSig::new(key_pair.public_key().to_commitment(), AuthScheme::Falcon512Rpo).into(); + AuthSingleSig::new(key_pair.public_key().to_commitment(), AuthScheme::Falcon512Poseidon2).into(); let account = AccountBuilder::new(init_seed) .account_type(AccountType::RegularAccountUpdatableCode) @@ -312,7 +312,7 @@ async fn main() -> Result<(), Box> { let storage = details.storage(); println!(" Storage ({} items):", storage.num_items()); for (i, value) in storage.items().iter().enumerate() { - println!(" [{}]: {}", i, value.as_int()); + println!(" [{}]: {}", i, value.as_canonical_u64()); } println!(); } @@ -347,10 +347,10 @@ async fn main() -> Result<(), Box> { for (i, value) in storage.items().iter().enumerate() { // Try to interpret first item as account ID (P2ID pattern) if i == 0 { - let bytes: [u8; 8] = value.as_int().to_le_bytes(); - println!(" [{}]: {} (raw: 0x{})", i, value.as_int(), hex::encode(bytes)); + let bytes: [u8; 8] = value.as_canonical_u64().to_le_bytes(); + println!(" [{}]: {} (raw: 0x{})", i, value.as_canonical_u64(), hex::encode(bytes)); } else { - println!(" [{}]: {}", i, value.as_int()); + println!(" [{}]: {}", i, value.as_canonical_u64()); } } println!(); @@ -438,7 +438,7 @@ async fn main() -> Result<(), Box> { println!(" Script root: 0x{}", hex::encode(note.script().root().as_bytes())); println!(" Note storage ({}):", note.storage().num_items()); for (i, val) in note.storage().items().iter().enumerate() { - println!(" [{}]: {}", i, val.as_int()); + println!(" [{}]: {}", i, val.as_canonical_u64()); } println!("═══════════════════════════════════════════════════════════════"); println!(); diff --git a/src/bin/verify_notes.rs b/src/bin/verify_notes.rs index a4ae41b..83e1aa1 100644 --- a/src/bin/verify_notes.rs +++ b/src/bin/verify_notes.rs @@ -45,8 +45,8 @@ fn format_asset(asset: &Asset) -> String { } Asset::NonFungible(non_fungible) => { format!( - "NonFungible {{ faucet_prefix: {:?} }}", - non_fungible.faucet_id_prefix() + "NonFungible {{ faucet: {} }}", + non_fungible.faucet_id() ) } } @@ -97,7 +97,7 @@ fn print_note_details(note: &InputNoteRecord, indent: &str) { if !storage_items.is_empty() { println!("{}Storage: {} item(s)", indent, storage_items.len()); for (i, value) in storage_items.iter().enumerate() { - println!("{} [{}]: 0x{:016x}", indent, i, value.as_int()); + println!("{} [{}]: 0x{:016x}", indent, i, value.as_canonical_u64()); } } @@ -106,7 +106,7 @@ fn print_note_details(note: &InputNoteRecord, indent: &str) { let script = recipient.script(); let root = script.root(); println!("{}Script: 0x{:016x}{:016x}{:016x}{:016x}", indent, - root[0].as_int(), root[1].as_int(), root[2].as_int(), root[3].as_int()); + root[0].as_canonical_u64(), root[1].as_canonical_u64(), root[2].as_canonical_u64(), root[3].as_canonical_u64()); } #[tokio::main] diff --git a/src/client.rs b/src/client.rs index 7383ce1..a261227 100644 --- a/src/client.rs +++ b/src/client.rs @@ -11,7 +11,7 @@ use std::sync::Arc; use anyhow::Result; use miden_agglayer::{ - create_claim_note, ClaimNoteStorage, EthAddressFormat, EthAmount, ExitRoot, GlobalIndex, + create_claim_note, ClaimNoteStorage, EthAddress, EthAmount, ExitRoot, GlobalIndex, LeafData, MetadataHash, ProofData, SmtNode, }; use miden_client::Client; @@ -203,9 +203,9 @@ where let leaf_data = LeafData { origin_network: params.origin_network, - origin_token_address: EthAddressFormat::new(params.origin_token_address), + origin_token_address: EthAddress::new(params.origin_token_address), destination_network: params.destination_network, - destination_address: EthAddressFormat::new(params.destination_address), + destination_address: EthAddress::new(params.destination_address), amount: EthAmount::new(params.amount), metadata_hash: MetadataHash::new(params.metadata_hash), }; @@ -240,20 +240,14 @@ pub fn build_claim_transaction_request( output_notes: Vec, ) -> Result { - use miden_client::transaction::{TransactionRequestBuilder, OutputNote}; + use miden_client::transaction::TransactionRequestBuilder; info!( num_notes = output_notes.len(), "Building claim transaction request" ); - // Convert Notes to OutputNotes for the builder - let output_notes: Vec = output_notes - .into_iter() - .map(OutputNote::Full) - .collect(); - - // Build transaction request with output notes + // own_output_notes takes Vec directly in 0.14 let tx_request = TransactionRequestBuilder::new() .own_output_notes(output_notes) .build() diff --git a/src/main.rs b/src/main.rs index c8355ab..14bb537 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,7 +32,7 @@ use alloy_primitives::{Address, Bytes}; use miden_protocol::account::{AccountId, AccountIdV0}; // Miden agglayer function for AccountId -> 20-byte destination conversion -use miden_agglayer::{EthAddressFormat, EthAmount, ExitRoot, UpdateGerNote}; +use miden_agglayer::{EthAddress as MidenEthAddress, EthAmount, EthEmbeddedAccountId, ExitRoot, MetadataHash, UpdateGerNote}; /// Get chain ID from environment variable, defaults to 2 (agglayer Miden network ID) fn get_chain_id() -> u64 { @@ -759,7 +759,7 @@ async fn submit_claim_to_miden( claim_data: ClaimSubmissionData, ) -> Result { use miden_client::transaction::TransactionRequestBuilder; - use miden_protocol::crypto::rand::RpoRandomCoin; + use miden_protocol::crypto::rand::RandomCoin as RpoRandomCoin; use miden_protocol::Felt; use miden_rpc_proxy::{create_bridge_claim_note, BridgeClaimParams}; @@ -847,7 +847,7 @@ async fn submit_claim_to_miden( // Generate key pair for signing info!(" Generating Falcon512 key pair for signing..."); - let key_pair = AuthSecretKey::new_falcon512_rpo(); + let key_pair = AuthSecretKey::new_falcon512_poseidon2(); info!(" Public key commitment generated"); // Build the ephemeral account first (need ID for keystore) @@ -859,7 +859,7 @@ async fn submit_claim_to_miden( let ephemeral_account = AccountBuilder::new(init_seed) .account_type(AccountType::RegularAccountUpdatableCode) .storage_mode(AccountStorageMode::Public) - .with_auth_component(AuthSingleSig::new(key_pair.public_key().to_commitment(), AuthScheme::Falcon512Rpo)) + .with_auth_component(AuthSingleSig::new(key_pair.public_key().to_commitment(), AuthScheme::Falcon512Poseidon2)) .with_component(BasicWallet) .build() .map_err(|e| { @@ -917,7 +917,9 @@ async fn submit_claim_to_miden( // TODO: bridge_admin_id and ger_manager_id should come from genesis config let dummy_admin_id = submitter_account_id; let dummy_ger_manager_id = submitter_account_id; - let origin_token_address = miden_agglayer::EthAddressFormat::new([0u8; 20]); + let origin_token_address = MidenEthAddress::new([0u8; 20]); + // MetadataHash from raw L1 metadata bytes — matches L1 bridge's getTokenMetadata + let metadata_hash = MetadataHash::from_abi_encoded(&claim_data.metadata); let faucet_result = create_and_deploy_agglayer_faucet( &mut client, &config.bridge_faucet_id_hex, @@ -926,6 +928,7 @@ async fn submit_claim_to_miden( &origin_token_address, 0, // origin_network: mainnet 10, // scale: 18 - 8 = 10 decimal places + metadata_hash, ).await?; faucet_result.faucet_id }; @@ -1022,7 +1025,7 @@ async fn submit_claim_to_miden( let claim_script_root = claim_script.root(); info!(" → CLAIM script root: {:?}", claim_script_root); info!(" → CLAIM script root hex: 0x{}", claim_script_root.iter() - .map(|f| format!("{:016x}", f.as_int())) + .map(|f| format!("{:016x}", f.as_canonical_u64())) .collect::>() .join("")); @@ -1041,10 +1044,8 @@ async fn submit_claim_to_miden( info!(" - Output: CLAIM note (faucet will auto-consume)"); // Build transaction request with CLAIM note as output - use miden_client::transaction::OutputNote; - let claim_output_note = OutputNote::Full(claim_note.clone()); let tx_request = TransactionRequestBuilder::new() - .own_output_notes(vec![claim_output_note]) + .own_output_notes(vec![claim_note.clone()]) .build() .map_err(|e| ClientError::TransactionError(format!( "Failed to build claim request: {}", e @@ -1093,7 +1094,7 @@ async fn submit_ger_to_miden( global_exit_root: [u8; 32], ) -> Result { use miden_client::transaction::{OutputNote, TransactionRequestBuilder}; - use miden_protocol::crypto::rand::RpoRandomCoin; + use miden_protocol::crypto::rand::RandomCoin as RpoRandomCoin; use miden_protocol::Felt; let ger_hex = hex::encode(&global_exit_root); @@ -1184,7 +1185,7 @@ async fn submit_ger_to_miden( // Submit as output note from GER manager let tx_request = TransactionRequestBuilder::new() - .own_output_notes(vec![OutputNote::Full(update_ger_note)]) + .own_output_notes(vec![update_ger_note]) .build() .map_err(|e| { ClientError::TransactionError(format!( @@ -1241,8 +1242,8 @@ fn bytes_to_account_id(bytes: &[u8; 15]) -> Result { /// /// Each Felt holds 4 bytes (as u32) from the hash fn bytes_to_felts_32(bytes: &[u8; 32]) -> [miden_protocol::Felt; 8] { - use miden_protocol::{Felt, FieldElement}; - let mut felts = [::ZERO; 8]; + use miden_protocol::{Felt, ZERO}; + let mut felts = [ZERO; 8]; for (i, chunk) in bytes.chunks(4).enumerate() { let value = u32::from_le_bytes(chunk.try_into().unwrap_or([0; 4])); felts[i] = Felt::new(value as u64); @@ -1792,7 +1793,7 @@ impl EthApiServer for EthApiImpl { }; // Log the round-trip conversion for debugging (using miden-agglayer functions) - let dest_bytes_20 = EthAddressFormat::from_account_id(miden_account_id.inner()).into_bytes(); + let dest_bytes_20 = EthEmbeddedAccountId::from_account_id(miden_account_id.inner()).to_bytes(); debug!( miden_account_id = %miden_account_id, destination_bytes_20 = hex::encode(&dest_bytes_20), @@ -2601,7 +2602,7 @@ async fn initialize_miden_accounts( // Generate key pair for signing info!(" Generating Falcon512 key pair for signing..."); - let key_pair = AuthSecretKey::new_falcon512_rpo(); + let key_pair = AuthSecretKey::new_falcon512_poseidon2(); info!(" Public key commitment generated"); // Build the ephemeral account first (need ID for keystore) @@ -2613,7 +2614,7 @@ async fn initialize_miden_accounts( let ephemeral_account = AccountBuilder::new(init_seed) .account_type(AccountType::RegularAccountUpdatableCode) .storage_mode(AccountStorageMode::Public) - .with_auth_component(AuthSingleSig::new(key_pair.public_key().to_commitment(), AuthScheme::Falcon512Rpo)) + .with_auth_component(AuthSingleSig::new(key_pair.public_key().to_commitment(), AuthScheme::Falcon512Poseidon2)) .with_component(BasicWallet) .build() .map_err(|e| { @@ -2652,7 +2653,9 @@ async fn initialize_miden_accounts( // TODO: bridge_admin_id and ger_manager_id should come from genesis config let dummy_admin_id = ephemeral_account_id; let dummy_ger_manager_id = ephemeral_account_id; - let origin_token_address = miden_agglayer::EthAddressFormat::new([0u8; 20]); + let origin_token_address = MidenEthAddress::new([0u8; 20]); + // Empty metadata matches L1 bridge convention for native ETH + let metadata_hash = MetadataHash::from_abi_encoded(&[]); let faucet_result = create_and_deploy_agglayer_faucet( &mut client, &bridge_faucet_id_hex, @@ -2661,6 +2664,7 @@ async fn initialize_miden_accounts( &origin_token_address, 0, // origin_network: mainnet 10, // scale: 18 - 8 = 10 decimal places + metadata_hash, ).await?; info!(" ✓ Bridge account ID: {}", faucet_result.bridge_account_id); info!(" ✓ Agglayer faucet ID: {}", faucet_result.faucet_id); diff --git a/tests/common/mod.rs b/tests/common/mod.rs index bfb8e2e..6b73dcc 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -62,7 +62,7 @@ pub async fn create_test_client() -> Result<(TestClient, FilesystemKeyStore, Pat let client: TestClient = ClientBuilder::new() .grpc_client(&endpoint, Some(10_000)) .store(Arc::new(store)) - .filesystem_keystore(&keystore_path_str) + .filesystem_keystore(&keystore_path_str)? .build() .await?; diff --git a/tests/phase1.rs b/tests/phase1.rs index cfcf7b5..e265edc 100644 --- a/tests/phase1.rs +++ b/tests/phase1.rs @@ -5,11 +5,11 @@ //! //! Uses miden-client and miden-protocol from agglayer-v0.1 tag. -use miden_client::account::component::{ - AccountComponent, AuthRpoFalcon512, BasicFungibleFaucet, BasicWallet, -}; +use miden_client::account::component::{AccountComponent, BasicFungibleFaucet, BasicWallet}; +use miden_protocol::account::auth::AuthScheme; +use miden_standards::account::auth::AuthSingleSig; use miden_client::account::{AccountBuilder, AccountType}; -use miden_client::keystore::FilesystemKeyStore; +use miden_client::keystore::{FilesystemKeyStore, Keystore}; use miden_client::transaction::{PaymentNoteDescription, TransactionRequestBuilder}; use miden_protocol::account::auth::AuthSecretKey; use miden_protocol::account::AccountStorageMode; @@ -36,12 +36,9 @@ async fn create_wallet( client.rng().fill_bytes(&mut init_seed); // Create key pair and auth component - let key_pair = AuthSecretKey::new_falcon512_rpo(); + let key_pair = AuthSecretKey::new_falcon512_poseidon2(); let auth_component: AccountComponent = - AuthRpoFalcon512::new(key_pair.public_key().to_commitment()).into(); - - // Add key to keystore - keystore.add_key(&key_pair)?; + AuthSingleSig::new(key_pair.public_key().to_commitment(), AuthScheme::Falcon512Poseidon2).into(); // Build account let account = AccountBuilder::new(init_seed) @@ -54,6 +51,9 @@ async fn create_wallet( // Add account to client client.add_account(&account, false).await?; + // Add key to keystore (associated with account) + keystore.add_key(&key_pair, account.id()).await?; + Ok(account) } @@ -70,12 +70,9 @@ async fn create_faucet( client.rng().fill_bytes(&mut init_seed); // Create key pair and auth component - let key_pair = AuthSecretKey::new_falcon512_rpo(); + let key_pair = AuthSecretKey::new_falcon512_poseidon2(); let auth_component: AccountComponent = - AuthRpoFalcon512::new(key_pair.public_key().to_commitment()).into(); - - // Add key to keystore - keystore.add_key(&key_pair)?; + AuthSingleSig::new(key_pair.public_key().to_commitment(), AuthScheme::Falcon512Poseidon2).into(); // Create token symbol let token_symbol = TokenSymbol::new(symbol)?; @@ -92,6 +89,9 @@ async fn create_faucet( // Add account to client client.add_account(&account, false).await?; + // Add key to keystore (associated with account) + keystore.add_key(&key_pair, account.id()).await?; + Ok(account) } @@ -312,7 +312,7 @@ mod tc_1_5_consumption { .expect("Failed to get notes"); if !notes.is_empty() { - let notes_to_consume: Vec<_> = notes.into_iter().map(|(n, _)| n).collect(); + let notes_to_consume: Vec = notes.into_iter().map(|(n, _)| n.try_into().unwrap()).collect(); let tx_request = TransactionRequestBuilder::new() .build_consume_notes(notes_to_consume) .expect("Failed to build consume request"); @@ -372,7 +372,7 @@ mod tc_1_6_p2id { .expect("Failed to get Alice's notes"); if !alice_notes.is_empty() { - let notes_to_consume: Vec<_> = alice_notes.into_iter().map(|(n, _)| n).collect(); + let notes_to_consume: Vec = alice_notes.into_iter().map(|(n, _)| n.try_into().unwrap()).collect(); let consume_request = TransactionRequestBuilder::new() .build_consume_notes(notes_to_consume) .expect("Failed to build consume request"); @@ -434,7 +434,7 @@ mod tc_1_6_p2id { // Alice consumes mint note let alice_notes = client.get_consumable_notes(Some(alice.id())).await.unwrap(); if !alice_notes.is_empty() { - let notes_to_consume: Vec<_> = alice_notes.into_iter().map(|(n, _)| n).collect(); + let notes_to_consume: Vec = alice_notes.into_iter().map(|(n, _)| n.try_into().unwrap()).collect(); let consume_request = TransactionRequestBuilder::new() .build_consume_notes(notes_to_consume) .unwrap(); @@ -458,7 +458,7 @@ mod tc_1_6_p2id { // Bob consumes P2ID note let bob_notes = client.get_consumable_notes(Some(bob.id())).await.unwrap(); if !bob_notes.is_empty() { - let notes_to_consume: Vec<_> = bob_notes.into_iter().map(|(n, _)| n).collect(); + let notes_to_consume: Vec = bob_notes.into_iter().map(|(n, _)| n.try_into().unwrap()).collect(); let consume_request = TransactionRequestBuilder::new() .build_consume_notes(notes_to_consume) .unwrap(); diff --git a/tests/phase2.rs b/tests/phase2.rs index 367bd8e..d63a413 100644 --- a/tests/phase2.rs +++ b/tests/phase2.rs @@ -5,11 +5,11 @@ //! //! Uses miden-client and miden-protocol from agglayer-v0.1 tag. -use miden_client::account::component::{ - AccountComponent, AuthRpoFalcon512, BasicFungibleFaucet, BasicWallet, -}; +use miden_client::account::component::{AccountComponent, BasicFungibleFaucet, BasicWallet}; +use miden_protocol::account::auth::AuthScheme; +use miden_standards::account::auth::AuthSingleSig; use miden_client::account::{AccountBuilder, AccountType}; -use miden_client::keystore::FilesystemKeyStore; +use miden_client::keystore::{FilesystemKeyStore, Keystore}; use miden_client::transaction::TransactionRequestBuilder; use miden_protocol::account::auth::AuthSecretKey; use miden_protocol::account::AccountStorageMode; @@ -34,11 +34,10 @@ async fn create_wallet( let mut init_seed = [0u8; 32]; client.rng().fill_bytes(&mut init_seed); - let key_pair = AuthSecretKey::new_falcon512_rpo(); + let key_pair = AuthSecretKey::new_falcon512_poseidon2(); let auth_component: AccountComponent = - AuthRpoFalcon512::new(key_pair.public_key().to_commitment()).into(); + AuthSingleSig::new(key_pair.public_key().to_commitment(), AuthScheme::Falcon512Poseidon2).into(); - keystore.add_key(&key_pair)?; let account = AccountBuilder::new(init_seed) .account_type(AccountType::RegularAccountUpdatableCode) @@ -48,6 +47,9 @@ async fn create_wallet( .build()?; client.add_account(&account, false).await?; + + // Add key to keystore (associated with account) + keystore.add_key(&key_pair, account.id()).await?; Ok(account) } @@ -62,11 +64,10 @@ async fn create_faucet( let mut init_seed = [0u8; 32]; client.rng().fill_bytes(&mut init_seed); - let key_pair = AuthSecretKey::new_falcon512_rpo(); + let key_pair = AuthSecretKey::new_falcon512_poseidon2(); let auth_component: AccountComponent = - AuthRpoFalcon512::new(key_pair.public_key().to_commitment()).into(); + AuthSingleSig::new(key_pair.public_key().to_commitment(), AuthScheme::Falcon512Poseidon2).into(); - keystore.add_key(&key_pair)?; let token_symbol = TokenSymbol::new(symbol)?; let max_supply_felt = Felt::new(max_supply); @@ -79,6 +80,9 @@ async fn create_faucet( .build()?; client.add_account(&account, false).await?; + + // Add key to keystore (associated with account) + keystore.add_key(&key_pair, account.id()).await?; Ok(account) } @@ -169,7 +173,7 @@ mod tc_2_2_claim_redemption { let notes = client.get_consumable_notes(Some(redeemer.id())).await.unwrap(); if !notes.is_empty() { - let notes_to_consume: Vec<_> = notes.into_iter().map(|(n, _)| n).collect(); + let notes_to_consume: Vec = notes.into_iter().map(|(n, _)| n.try_into().unwrap()).collect(); let consume_request = TransactionRequestBuilder::new() .build_consume_notes(notes_to_consume) .unwrap(); @@ -202,7 +206,7 @@ mod tc_2_2_claim_redemption { let notes = client.get_consumable_notes(Some(redeemer.id())).await.unwrap(); if !notes.is_empty() { let ids: Vec<_> = notes.iter().map(|(n, _)| n.id()).collect(); - let notes_to_consume: Vec<_> = notes.into_iter().map(|(n, _)| n).collect(); + let notes_to_consume: Vec = notes.into_iter().map(|(n, _)| n.try_into().unwrap()).collect(); let consume_request = TransactionRequestBuilder::new() .build_consume_notes(notes_to_consume) .unwrap(); diff --git a/tests/phase3.rs b/tests/phase3.rs index e9ec104..fe85619 100644 --- a/tests/phase3.rs +++ b/tests/phase3.rs @@ -5,11 +5,11 @@ //! //! Uses miden-client and miden-protocol from agglayer-v0.1 tag. -use miden_client::account::component::{ - AccountComponent, AuthRpoFalcon512, BasicFungibleFaucet, BasicWallet, -}; +use miden_client::account::component::{AccountComponent, BasicFungibleFaucet, BasicWallet}; +use miden_protocol::account::auth::AuthScheme; +use miden_standards::account::auth::AuthSingleSig; use miden_client::account::{AccountBuilder, AccountType}; -use miden_client::keystore::FilesystemKeyStore; +use miden_client::keystore::{FilesystemKeyStore, Keystore}; use miden_client::transaction::{PaymentNoteDescription, TransactionRequestBuilder}; use miden_protocol::account::auth::AuthSecretKey; use miden_protocol::account::AccountStorageMode; @@ -34,11 +34,10 @@ async fn create_wallet( let mut init_seed = [0u8; 32]; client.rng().fill_bytes(&mut init_seed); - let key_pair = AuthSecretKey::new_falcon512_rpo(); + let key_pair = AuthSecretKey::new_falcon512_poseidon2(); let auth_component: AccountComponent = - AuthRpoFalcon512::new(key_pair.public_key().to_commitment()).into(); + AuthSingleSig::new(key_pair.public_key().to_commitment(), AuthScheme::Falcon512Poseidon2).into(); - keystore.add_key(&key_pair)?; let account = AccountBuilder::new(init_seed) .account_type(AccountType::RegularAccountUpdatableCode) @@ -48,6 +47,9 @@ async fn create_wallet( .build()?; client.add_account(&account, false).await?; + + // Add key to keystore (associated with account) + keystore.add_key(&key_pair, account.id()).await?; Ok(account) } @@ -62,11 +64,10 @@ async fn create_faucet( let mut init_seed = [0u8; 32]; client.rng().fill_bytes(&mut init_seed); - let key_pair = AuthSecretKey::new_falcon512_rpo(); + let key_pair = AuthSecretKey::new_falcon512_poseidon2(); let auth_component: AccountComponent = - AuthRpoFalcon512::new(key_pair.public_key().to_commitment()).into(); + AuthSingleSig::new(key_pair.public_key().to_commitment(), AuthScheme::Falcon512Poseidon2).into(); - keystore.add_key(&key_pair)?; let token_symbol = TokenSymbol::new(symbol)?; let max_supply_felt = Felt::new(max_supply); @@ -79,6 +80,9 @@ async fn create_faucet( .build()?; client.add_account(&account, false).await?; + + // Add key to keystore (associated with account) + keystore.add_key(&key_pair, account.id()).await?; Ok(account) } @@ -173,7 +177,7 @@ mod tc_3_2_transfer_flow { // Alice consumes mint note let alice_notes = client.get_consumable_notes(Some(alice.id())).await.unwrap(); if !alice_notes.is_empty() { - let notes_to_consume: Vec<_> = alice_notes.into_iter().map(|(n, _)| n).collect(); + let notes_to_consume: Vec = alice_notes.into_iter().map(|(n, _)| n.try_into().unwrap()).collect(); let consume_request = TransactionRequestBuilder::new() .build_consume_notes(notes_to_consume) .unwrap(); @@ -226,7 +230,7 @@ mod tc_3_3_withdrawal_flow { let notes = client.get_consumable_notes(Some(user.id())).await.unwrap(); if !notes.is_empty() { - let notes_to_consume: Vec<_> = notes.into_iter().map(|(n, _)| n).collect(); + let notes_to_consume: Vec = notes.into_iter().map(|(n, _)| n.try_into().unwrap()).collect(); let consume_request = TransactionRequestBuilder::new() .build_consume_notes(notes_to_consume) .unwrap(); @@ -450,7 +454,7 @@ mod tc_3_9_full_cycle { // Step 2: Alice consumes deposit let alice_notes = client.get_consumable_notes(Some(alice.id())).await.unwrap(); if !alice_notes.is_empty() { - let notes_to_consume: Vec<_> = alice_notes.into_iter().map(|(n, _)| n).collect(); + let notes_to_consume: Vec = alice_notes.into_iter().map(|(n, _)| n.try_into().unwrap()).collect(); let consume_request = TransactionRequestBuilder::new() .build_consume_notes(notes_to_consume) .unwrap(); @@ -476,7 +480,7 @@ mod tc_3_9_full_cycle { // Step 4: Bob consumes transfer let bob_notes = client.get_consumable_notes(Some(bob.id())).await.unwrap(); if !bob_notes.is_empty() { - let notes_to_consume: Vec<_> = bob_notes.into_iter().map(|(n, _)| n).collect(); + let notes_to_consume: Vec = bob_notes.into_iter().map(|(n, _)| n.try_into().unwrap()).collect(); let consume_request = TransactionRequestBuilder::new() .build_consume_notes(notes_to_consume) .unwrap();