From a430afe9d57f1209f029b5480f16788112ad3808 Mon Sep 17 00:00:00 2001 From: Michael Turner Date: Tue, 17 Feb 2026 14:21:06 -0500 Subject: [PATCH 01/17] Change SnarkVM dep to latest rev on the corresponding SnarkVM:feat/dynamic-dispatch branch --- wasm/Cargo.lock | 1495 ++++++++++++++++++++++++++--------------------- wasm/Cargo.toml | 32 +- 2 files changed, 863 insertions(+), 664 deletions(-) diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 0200c6552..aedd384b7 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + [[package]] name = "adler2" version = "2.0.1" @@ -51,7 +60,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9ebd144c81671193ed85aa2db9bb5e183421843e0485de8fffc07e5cf50e18a" dependencies = [ "proc-macro2", - "quote 1.0.44", + "quote 1.0.42", "syn 1.0.109", ] @@ -62,7 +71,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68f6ff9e4c36858fa2c29e5284b77527b5a7466743976e1ba1f5824e16683545" dependencies = [ "proc-macro2", - "quote 1.0.44", + "quote 1.0.42", "syn 1.0.109", ] @@ -74,7 +83,7 @@ checksum = "12aca1021aef2c476bad30d2f681e891b2be4f07dbc230a96df09cb693bfb3cb" [[package]] name = "aleo-wasm" -version = "0.9.18" +version = "0.9.17" dependencies = [ "anyhow", "async-trait", @@ -84,7 +93,7 @@ dependencies = [ "hex", "indexmap", "js-sys", - "rand", + "rand 0.8.5", "rayon", "reqwest 0.11.27", "serde", @@ -117,9 +126,12 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +dependencies = [ + "backtrace", +] [[package]] name = "arrayref" @@ -140,8 +152,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", - "quote 1.0.44", - "syn 2.0.117", + "quote 1.0.42", + "syn 2.0.111", ] [[package]] @@ -156,6 +168,43 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-lc-rs" +version = "1.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.37.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b092fe214090261288111db7a2b2c2118e5a7f30dc2569f1732c4069a6840549" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + [[package]] name = "base16ct" version = "0.2.0" @@ -197,9 +246,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "blake2" @@ -212,9 +261,9 @@ dependencies = [ [[package]] name = "blake2s_simd" -version = "1.0.4" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee29928bad1e3f94c9d1528da29e07a1d3d04817ae8332de1e8b846c8439f4b3" +checksum = "e90f7deecfac93095eb874a40febd69427776e24e1bd7f87f33ac62d6f0174df" dependencies = [ "arrayref", "arrayvec", @@ -241,9 +290,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "byteorder" @@ -257,35 +306,62 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" -[[package]] -name = "cast" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" - [[package]] name = "cc" -version = "1.2.56" +version = "1.2.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + [[package]] name = "colored" -version = "3.1.1" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" +checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", ] [[package]] @@ -306,9 +382,9 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "constant_time_eq" -version = "0.4.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "cookie" @@ -323,9 +399,9 @@ dependencies = [ [[package]] name = "cookie_store" -version = "0.22.1" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15b2c103cf610ec6cae3da84a766285b42fd16aad564758459e6ecf128c75206" +checksum = "3fc4bff745c9b4c7fb1e97b25d13153da2bc7796260141df62378998d070207f" dependencies = [ "cookie", "document-features", @@ -421,7 +497,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", - "rand_core", + "rand_core 0.6.4", "subtle", "zeroize", ] @@ -447,15 +523,15 @@ dependencies = [ "openssl-probe 0.1.6", "openssl-sys", "schannel", - "socket2 0.6.2", + "socket2 0.6.1", "windows-sys 0.59.0", ] [[package]] name = "curl-sys" -version = "0.4.85+curl-8.18.0" +version = "0.4.84+curl-8.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0efa6142b5ecc05f6d3eaa39e6af4888b9d3939273fb592c92b7088a8cf3fdb" +checksum = "abc4294dc41b882eaff37973c2ec3ae203d0091341ee68fbadd1d06e0c18a73b" dependencies = [ "cc", "libc", @@ -478,9 +554,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.8" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", ] @@ -524,8 +600,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", - "quote 1.0.44", - "syn 2.0.117", + "quote 1.0.42", + "syn 2.0.111", ] [[package]] @@ -537,6 +613,12 @@ dependencies = [ "litrs", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "ecdsa" version = "0.16.9" @@ -568,7 +650,7 @@ dependencies = [ "ff", "generic-array", "group", - "rand_core", + "rand_core 0.6.4", "sec1", "subtle", "zeroize", @@ -663,8 +745,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "685adfa4d6f3d765a26bc5dbc936577de9abf756c1feeb3089b01dd395034842" dependencies = [ "proc-macro2", - "quote 1.0.44", - "syn 2.0.117", + "quote 1.0.42", + "syn 2.0.111", ] [[package]] @@ -711,21 +793,21 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ - "rand_core", + "rand_core 0.6.4", "subtle", ] [[package]] name = "find-msvc-tools" -version = "0.1.9" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] name = "flate2" -version = "1.1.9" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" dependencies = [ "crc32fast", "miniz_oxide", @@ -773,11 +855,17 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures" -version = "0.3.32" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -790,9 +878,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.32" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -800,15 +888,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.32" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.32" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -817,38 +905,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.32" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.32" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", - "quote 1.0.44", - "syn 2.0.117", + "quote 1.0.42", + "syn 2.0.111", ] [[package]] name = "futures-sink" -version = "0.3.32" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.32" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.32" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -858,6 +946,7 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", + "pin-utils", "slab", ] @@ -883,9 +972,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.17" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "js-sys", @@ -896,17 +985,24 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.4.1" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "wasip2", - "wasip3", + "wasm-bindgen", ] +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + [[package]] name = "gloo-timers" version = "0.3.0" @@ -926,7 +1022,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -951,16 +1047,16 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.13" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "http 1.4.0", + "http 1.3.1", "indexmap", "slab", "tokio", @@ -990,12 +1086,6 @@ dependencies = [ "foldhash 0.2.0", ] -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - [[package]] name = "hermit-abi" version = "0.5.2" @@ -1030,11 +1120,12 @@ dependencies = [ [[package]] name = "http" -version = "1.4.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", + "fnv", "itoa", ] @@ -1056,7 +1147,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.4.0", + "http 1.3.1", ] [[package]] @@ -1067,7 +1158,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http 1.4.0", + "http 1.3.1", "http-body 1.0.1", "pin-project-lite", ] @@ -1118,8 +1209,8 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2 0.4.13", - "http 1.4.0", + "h2 0.4.12", + "http 1.3.1", "http-body 1.0.1", "httparse", "itoa", @@ -1136,7 +1227,7 @@ version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "http 1.4.0", + "http 1.3.1", "hyper 1.8.1", "hyper-util", "rustls", @@ -1159,41 +1250,26 @@ dependencies = [ "tokio-native-tls", ] -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper 1.8.1", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - [[package]] name = "hyper-util" -version = "0.1.20" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56" dependencies = [ "base64 0.22.1", "bytes", "futures-channel", + "futures-core", "futures-util", - "http 1.4.0", + "http 1.3.1", "http-body 1.0.1", "hyper 1.8.1", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.2", - "system-configuration 0.7.0", + "socket2 0.6.1", + "system-configuration 0.6.1", "tokio", "tower-service", "tracing", @@ -1248,9 +1324,9 @@ checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" dependencies = [ "icu_collections", "icu_locale_core", @@ -1262,9 +1338,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" [[package]] name = "icu_provider" @@ -1281,12 +1357,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - [[package]] name = "idna" version = "1.1.0" @@ -1310,9 +1380,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", "hashbrown 0.16.1", @@ -1329,9 +1399,9 @@ checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "iri-string" -version = "0.7.10" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" dependencies = [ "memchr", "serde", @@ -1348,15 +1418,47 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] [[package]] name = "js-sys" -version = "0.3.88" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7e709f3e3d22866f9c25b3aff01af289b18422cc8b4262fb19103ee80fe513d" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" dependencies = [ "once_cell", "wasm-bindgen", @@ -1380,31 +1482,19 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - [[package]] name = "libc" -version = "0.2.182" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" - -[[package]] -name = "libm" -version = "0.2.16" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libredox" -version = "0.1.12" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.10.0", "libc", ] @@ -1422,9 +1512,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.12.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litemap" @@ -1449,9 +1539,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "lru" @@ -1462,11 +1552,17 @@ dependencies = [ "hashbrown 0.16.1", ] +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "memchr" -version = "2.8.0" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "mime" @@ -1476,9 +1572,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "minicov" -version = "0.3.8" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4869b6a491569605d66d3952bcdf03df789e5b536e5f0cf7758a7f08a55ae24d" +checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b" dependencies = [ "cc", "walkdir", @@ -1502,9 +1598,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" dependencies = [ "libc", "wasi", @@ -1513,17 +1609,17 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.18" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" dependencies = [ "libc", "log", "openssl", - "openssl-probe 0.2.1", + "openssl-probe 0.1.6", "openssl-sys", "schannel", - "security-framework", + "security-framework 2.11.1", "security-framework-sys", "tempfile", ] @@ -1544,7 +1640,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -1570,8 +1666,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", - "quote 1.0.44", - "syn 2.0.117", + "quote 1.0.42", + "syn 2.0.111", ] [[package]] @@ -1590,7 +1686,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -1604,16 +1699,19 @@ dependencies = [ ] [[package]] -name = "once_cell" -version = "1.21.3" +name = "object" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] [[package]] -name = "oorandom" -version = "11.1.5" +name = "once_cell" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "openssl" @@ -1621,7 +1719,7 @@ version = "0.10.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.10.0", "cfg-if", "foreign-types", "libc", @@ -1637,8 +1735,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", - "quote 1.0.44", - "syn 2.0.117", + "quote 1.0.42", + "syn 2.0.111", ] [[package]] @@ -1743,22 +1841,68 @@ dependencies = [ ] [[package]] -name = "prettyplease" -version = "0.2.37" +name = "proc-macro2" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ - "proc-macro2", - "syn 2.0.117", + "unicode-ident", ] [[package]] -name = "proc-macro2" -version = "1.0.106" +name = "quinn" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" dependencies = [ - "unicode-ident", + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2 0.5.10", + "thiserror 2.0.17", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.17", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.5.10", + "tracing", + "windows-sys 0.52.0", ] [[package]] @@ -1769,9 +1913,9 @@ checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" [[package]] name = "quote" -version = "1.0.44" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] @@ -1789,8 +1933,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", ] [[package]] @@ -1800,7 +1954,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", ] [[package]] @@ -1809,7 +1973,16 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.17", + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", ] [[package]] @@ -1818,7 +1991,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" dependencies = [ - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -1847,7 +2020,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.10.0", ] [[package]] @@ -1856,7 +2029,7 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom 0.2.17", + "getrandom 0.2.16", "libredox", "thiserror 1.0.69", ] @@ -1876,7 +2049,7 @@ dependencies = [ "http 0.2.12", "http-body 0.4.6", "hyper 0.14.32", - "hyper-tls 0.5.0", + "hyper-tls", "ipnet", "js-sys", "log", @@ -1903,35 +2076,35 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.28" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" dependencies = [ "base64 0.22.1", "bytes", "encoding_rs", "futures-core", - "h2 0.4.13", - "http 1.4.0", + "h2 0.4.12", + "http 1.3.1", "http-body 1.0.1", "http-body-util", "hyper 1.8.1", "hyper-rustls", - "hyper-tls 0.6.0", "hyper-util", "js-sys", "log", "mime", - "native-tls", "percent-encoding", "pin-project-lite", + "quinn", + "rustls", "rustls-pki-types", + "rustls-platform-verifier", "serde", "serde_json", - "serde_urlencoded", "sync_wrapper 1.0.2", "tokio", - "tokio-native-tls", + "tokio-rustls", "tower", "tower-http", "tower-service", @@ -1959,12 +2132,24 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.17", + "getrandom 0.2.16", "libc", "untrusted", "windows-sys 0.52.0", ] +[[package]] +name = "rustc-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustc_version" version = "0.4.1" @@ -1976,11 +2161,11 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.4" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys", @@ -1989,10 +2174,11 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.36" +version = "0.23.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" dependencies = [ + "aws-lc-rs", "log", "once_cell", "ring", @@ -2002,6 +2188,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe 0.2.1", + "rustls-pki-types", + "schannel", + "security-framework 3.5.1", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -2012,20 +2210,49 @@ dependencies = [ ] [[package]] -name = "rustls-pki-types" -version = "1.14.0" +name = "rustls-pki-types" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework 3.5.1", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" -dependencies = [ - "zeroize", -] +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.9" +version = "0.103.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -2039,9 +2266,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.23" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" @@ -2082,11 +2309,24 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.7.0" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.10.0", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -2095,9 +2335,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.17.0" +version = "2.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" dependencies = [ "core-foundation-sys", "libc", @@ -2141,22 +2381,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", - "quote 1.0.44", - "syn 2.0.117", + "quote 1.0.42", + "syn 2.0.111", ] [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "indexmap", "itoa", "memchr", + "ryu", "serde", "serde_core", - "zmij", ] [[package]] @@ -2182,6 +2422,15 @@ dependencies = [ "digest", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -2195,20 +2444,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", - "rand_core", + "rand_core 0.6.4", ] [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "slab" -version = "0.4.12" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" @@ -2227,9 +2476,8 @@ dependencies = [ [[package]] name = "snarkvm-algorithms" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa8ac1b6a6f09d6563f0c2b8907d4f604e30dd734615d74d047fffd69d515d82" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "aleo-std", "anyhow", @@ -2241,7 +2489,7 @@ dependencies = [ "indexmap", "itertools", "num-traits", - "rand", + "rand 0.8.5", "rayon", "serde", "sha2", @@ -2250,14 +2498,13 @@ dependencies = [ "snarkvm-fields", "snarkvm-parameters", "snarkvm-utilities", - "thiserror 2.0.18", + "thiserror 2.0.17", ] [[package]] name = "snarkvm-circuit" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "789ebaac0b333ff0bc67c080321b485b8f83341f608e2c0f7789ffda09f26bb1" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "snarkvm-circuit-account", "snarkvm-circuit-algorithms", @@ -2270,9 +2517,8 @@ dependencies = [ [[package]] name = "snarkvm-circuit-account" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e386197b4b8b9da94935cae1f7c8de5a48080846a99b89d48a37bdda19a41274" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "snarkvm-circuit-network", "snarkvm-circuit-types", @@ -2281,9 +2527,8 @@ dependencies = [ [[package]] name = "snarkvm-circuit-algorithms" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61dfe1df8b9f1dab0437c7077fd849f9c6c175c2d2bc6a4a21777abee9fcec7" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "snarkvm-circuit-types", "snarkvm-console-algorithms", @@ -2292,9 +2537,8 @@ dependencies = [ [[package]] name = "snarkvm-circuit-collections" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc59afd654098930527c9240eb34d48cb9b66f1b14aceaf9d9383cfca0be936d" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "snarkvm-circuit-algorithms", "snarkvm-circuit-types", @@ -2303,10 +2547,10 @@ dependencies = [ [[package]] name = "snarkvm-circuit-environment" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dee5ea35b2fcd22572c6a2f9bf5665b99239880aa27b1e8c859066006d56e66" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ + "anyhow", "indexmap", "itertools", "nom", @@ -2318,19 +2562,18 @@ dependencies = [ "snarkvm-curves", "snarkvm-fields", "snarkvm-utilities", + "thiserror 2.0.17", ] [[package]] name = "snarkvm-circuit-environment-witness" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0722b642ba183bb9d8ee819555ffaf3f6517fb135e6ab7ba04d3397e39bac67" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" [[package]] name = "snarkvm-circuit-network" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38ff0ad7521a67f3115eacdde833ad21c96caf5ed490067ab0a3f22b7fdfe16f" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "snarkvm-circuit-algorithms", "snarkvm-circuit-collections", @@ -2340,9 +2583,8 @@ dependencies = [ [[package]] name = "snarkvm-circuit-program" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73c4e7b786afeeff9b2668a28290c05ac1cac2c7553bf2bab46629d9a54d12b8" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "snarkvm-circuit-account", "snarkvm-circuit-algorithms", @@ -2355,9 +2597,8 @@ dependencies = [ [[package]] name = "snarkvm-circuit-types" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "863535b214170279b4458819e6e36ad88dfdf47998f83dd15d8e3dca2038f014" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "snarkvm-circuit-environment", "snarkvm-circuit-types-address", @@ -2371,9 +2612,8 @@ dependencies = [ [[package]] name = "snarkvm-circuit-types-address" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ab1004c1033eea2a0b63e0cf3dae135dc3f4ef643ecafa68140bee610b9928" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "snarkvm-circuit-environment", "snarkvm-circuit-types-boolean", @@ -2385,9 +2625,8 @@ dependencies = [ [[package]] name = "snarkvm-circuit-types-boolean" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf15f8adcd01eb17b9f1f6351f12d7f12c31dd872515a5e316cf1b1950252867" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "snarkvm-circuit-environment", "snarkvm-console-types-boolean", @@ -2395,9 +2634,8 @@ dependencies = [ [[package]] name = "snarkvm-circuit-types-field" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a8fd90969d0f5f7bb3568b8f3c46be0411767e3a6bc3c61eda8bcdec95ce6b0" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "snarkvm-circuit-environment", "snarkvm-circuit-types-boolean", @@ -2406,9 +2644,8 @@ dependencies = [ [[package]] name = "snarkvm-circuit-types-group" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60fad82bb36772a0d5d5aed64023b30632a7d7feea1b2a9a5f115beb807292b6" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "snarkvm-circuit-environment", "snarkvm-circuit-types-boolean", @@ -2419,9 +2656,8 @@ dependencies = [ [[package]] name = "snarkvm-circuit-types-integers" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faefa15a1aa2a85066f0c36ae840d5760a27166efba1564b60f6e39e08e15ed8" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "snarkvm-circuit-environment", "snarkvm-circuit-types-boolean", @@ -2432,9 +2668,8 @@ dependencies = [ [[package]] name = "snarkvm-circuit-types-scalar" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e98982adf5e0667d8764613417c6e64c8ad954efcdd3c345cfcabd4c3a1ce46" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "snarkvm-circuit-environment", "snarkvm-circuit-types-boolean", @@ -2444,9 +2679,8 @@ dependencies = [ [[package]] name = "snarkvm-circuit-types-string" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e2ad5b31dd261c741646e2b4c06d82e842a2508def775f87807afca1b671d9" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "snarkvm-circuit-environment", "snarkvm-circuit-types-boolean", @@ -2457,9 +2691,8 @@ dependencies = [ [[package]] name = "snarkvm-console" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eedd50cc76dbd958e54128ab3c91bd6c8b99bca0a3fdace1db6a9575a18cd0c" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "snarkvm-console-account", "snarkvm-console-algorithms", @@ -2471,9 +2704,8 @@ dependencies = [ [[package]] name = "snarkvm-console-account" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5294eb812914aa4e0d453a7a5c72bb744dd43fe2411422296f4cb65405e69a93" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "bs58", "snarkvm-console-network", @@ -2483,9 +2715,8 @@ dependencies = [ [[package]] name = "snarkvm-console-algorithms" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efad39f931761918c4014ecca58dfe3118ebfb5f0b361baa86dcc9ef348597fe" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "blake2s_simd", "hex", @@ -2500,9 +2731,8 @@ dependencies = [ [[package]] name = "snarkvm-console-collections" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcb86ad2603ffd541a18ea524de0333b55b2c45fc44e38e891962e15ad2e2c28" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "aleo-std", "rayon", @@ -2513,9 +2743,8 @@ dependencies = [ [[package]] name = "snarkvm-console-network" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c63ede70a074cce404e121b569e8765e127cd1b9f87730ce7944518d2bd45d22" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "anyhow", "enum-iterator", @@ -2534,16 +2763,15 @@ dependencies = [ [[package]] name = "snarkvm-console-network-environment" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fac3b4b95c381a9537102ee84e20335df1598557f06d5e1138e74b98741d988" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "anyhow", "bech32", "itertools", "nom", "num-traits", - "rand", + "rand 0.8.5", "serde", "snarkvm-curves", "snarkvm-fields", @@ -2553,14 +2781,14 @@ dependencies = [ [[package]] name = "snarkvm-console-program" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bffd370083d4396a7c4c7b89e38be0923cd8ce283caf20e54f729c3f4008dd74" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "enum-iterator", "enum_index", "enum_index_derive", "indexmap", + "itertools", "num-derive", "num-traits", "seq-macro", @@ -2575,9 +2803,8 @@ dependencies = [ [[package]] name = "snarkvm-console-types" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c51363e05726d4aa1c8e73fb8aaadbb1a3895a2a0838ee1a50f4e2af111e295" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "snarkvm-console-network-environment", "snarkvm-console-types-address", @@ -2591,9 +2818,8 @@ dependencies = [ [[package]] name = "snarkvm-console-types-address" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4572a1aa0593f89ee7d338f496794dca57251a7dc43b2e4631d9f41b1db3cb9" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "snarkvm-console-network-environment", "snarkvm-console-types-boolean", @@ -2603,18 +2829,16 @@ dependencies = [ [[package]] name = "snarkvm-console-types-boolean" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0255c235e2fe2d61652357ddbcc032a5c34c21e2e1f575f6a0cce8b741dd1870" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "snarkvm-console-network-environment", ] [[package]] name = "snarkvm-console-types-field" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc1fc0bcaf8d66dad69469b9331279577402e3bb6e8744728ea3f19305d05dd" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "snarkvm-console-network-environment", "snarkvm-console-types-boolean", @@ -2623,9 +2847,8 @@ dependencies = [ [[package]] name = "snarkvm-console-types-group" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b4fb849c1dc4016cb449d15e7e5fb096bc23872422c03f12e180e16ba6accf" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "snarkvm-console-network-environment", "snarkvm-console-types-boolean", @@ -2635,9 +2858,8 @@ dependencies = [ [[package]] name = "snarkvm-console-types-integers" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea1dd3fb2575a7c9ed8e3797ae27759a94fe85d41004fa2f50ce4f35864825e" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "snarkvm-console-network-environment", "snarkvm-console-types-boolean", @@ -2647,9 +2869,8 @@ dependencies = [ [[package]] name = "snarkvm-console-types-scalar" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c300ee4c5a71033ae492b15a2b3270bb8fff1ed7bbfb4caaf8311f73230c0479" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "snarkvm-console-network-environment", "snarkvm-console-types-boolean", @@ -2659,9 +2880,8 @@ dependencies = [ [[package]] name = "snarkvm-console-types-string" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b842c747b5df818aa01cdd668ec1af51934d22ef73005d7b3c110af3f3bf563" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "snarkvm-console-network-environment", "snarkvm-console-types-boolean", @@ -2671,44 +2891,41 @@ dependencies = [ [[package]] name = "snarkvm-curves" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eef241fc007b4d15eb3bc704dd17734e005babf075f843b2ab4f6603a255a63d" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ - "rand", + "rand 0.8.5", "rustc_version", "serde", "snarkvm-fields", "snarkvm-utilities", - "thiserror 2.0.18", + "thiserror 2.0.17", ] [[package]] name = "snarkvm-fields" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdf420b40e671f6982e436d7356c75e008b04a8a2631e30bbe5789b1b72c9417" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "aleo-std", "anyhow", "itertools", "num-traits", - "rand", + "rand 0.8.5", "rayon", "serde", "snarkvm-utilities", - "thiserror 2.0.18", + "thiserror 2.0.17", "zeroize", ] [[package]] name = "snarkvm-ledger-authority" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5747f7656153c24fbcbe38031d0936ce4ccf8fc00a28e724fedcacc5d2356814" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "anyhow", - "rand", + "rand 0.8.5", "serde_json", "snarkvm-console", "snarkvm-ledger-narwhal-subdag", @@ -2716,12 +2933,12 @@ dependencies = [ [[package]] name = "snarkvm-ledger-block" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8280e3c51a6220d04be7f5a09fcb6c6ce9384492ae23b40f4537842b2f6d86b5" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "anyhow", "indexmap", + "itertools", "rayon", "serde_json", "snarkvm-console", @@ -2739,9 +2956,8 @@ dependencies = [ [[package]] name = "snarkvm-ledger-committee" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436fba11c005b91c8cf03671a8c6791750ba5c5dce2e5b28d02ab2829fc68ef0" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "indexmap", "rayon", @@ -2752,9 +2968,8 @@ dependencies = [ [[package]] name = "snarkvm-ledger-narwhal-batch-certificate" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a36980e0d857135083f9887e4cf66827735cbfd8b65ee4c627e197159c3c2b" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "indexmap", "rayon", @@ -2766,9 +2981,8 @@ dependencies = [ [[package]] name = "snarkvm-ledger-narwhal-batch-header" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e02c6b118935828771b13d8d85880a00364d4487d9e2b2822af64301dce5a5" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "indexmap", "rayon", @@ -2779,9 +2993,8 @@ dependencies = [ [[package]] name = "snarkvm-ledger-narwhal-data" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e49a698596a3adc0491b57521e9ebbb6a8b2f07eb7fa5283fa0b87b0c9db24da" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "bytes", "serde_json", @@ -2790,9 +3003,8 @@ dependencies = [ [[package]] name = "snarkvm-ledger-narwhal-subdag" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81e8640d272eea7842fb4f6a85bf5214908bcf294edcba8161a3fe852b5319b8" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "indexmap", "rayon", @@ -2806,9 +3018,8 @@ dependencies = [ [[package]] name = "snarkvm-ledger-narwhal-transmission-id" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecf18739f9594fe5494b628cd8d2f72b53c50a28773bf7940fea848fc26a6f17" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "snarkvm-console", "snarkvm-ledger-puzzle", @@ -2816,9 +3027,8 @@ dependencies = [ [[package]] name = "snarkvm-ledger-puzzle" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3c60576c33e4417f347a41d6f6dceeb38895af1ec4b7ef49eef467c5956ea8" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "aleo-std", "anyhow", @@ -2826,8 +3036,8 @@ dependencies = [ "indexmap", "lru", "parking_lot", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "rayon", "serde_json", "snarkvm-algorithms", @@ -2836,9 +3046,8 @@ dependencies = [ [[package]] name = "snarkvm-ledger-puzzle-epoch" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcb9278a74ef96adecd302979d43f01bb1fda9dab01af8586426eab0be916f07" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "aleo-std", "anyhow", @@ -2846,8 +3055,8 @@ dependencies = [ "indexmap", "lru", "parking_lot", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "rayon", "snarkvm-circuit", "snarkvm-console", @@ -2859,13 +3068,12 @@ dependencies = [ [[package]] name = "snarkvm-ledger-query" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03f55770a9b2aabc103b658e681346f62eb4e00b98293d185e04d654edf1ab31" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "anyhow", "async-trait", - "reqwest 0.12.28", + "reqwest 0.13.2", "serde", "serde_json", "snarkvm-console", @@ -2877,9 +3085,8 @@ dependencies = [ [[package]] name = "snarkvm-ledger-store" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f71a9d7badda594dc9dd75f6dd31a08b282d4e5cc2cb679d3802d396506387" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "aleo-std-storage", "anyhow", @@ -2903,9 +3110,8 @@ dependencies = [ [[package]] name = "snarkvm-parameters" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0969bee3d4c8b033bde17d35df2db9fbc92d2e7d2aceb01af6a59ebf32e455be" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "aleo-std", "anyhow", @@ -2918,20 +3124,19 @@ dependencies = [ "lazy_static", "parking_lot", "paste", - "rand", + "rand 0.8.5", "serde_json", "sha2", "snarkvm-curves", "snarkvm-utilities", - "thiserror 2.0.18", + "thiserror 2.0.17", "web-sys", ] [[package]] name = "snarkvm-synthesizer" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf9a1805635e7e2d85d7b7e51d1234bd87a6a7be8b2eef7be19617261a488f4" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "aleo-std", "anyhow", @@ -2939,7 +3144,7 @@ dependencies = [ "itertools", "lru", "parking_lot", - "rand", + "rand 0.8.5", "rayon", "serde_json", "snarkvm-algorithms", @@ -2952,26 +3157,39 @@ dependencies = [ "snarkvm-ledger-puzzle-epoch", "snarkvm-ledger-query", "snarkvm-ledger-store", + "snarkvm-synthesizer-error", "snarkvm-synthesizer-process", "snarkvm-synthesizer-program", "snarkvm-synthesizer-snark", "snarkvm-utilities", "tokio", "tracing", + "tracing-subscriber", +] + +[[package]] +name = "snarkvm-synthesizer-error" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +dependencies = [ + "anyhow", + "snarkvm-circuit-environment", + "snarkvm-console-network", + "thiserror 2.0.17", ] [[package]] name = "snarkvm-synthesizer-process" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73e2a61297295cd9a8ed0e509c14170bd1e4338eeefba1224302e7a6aea451d7" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "aleo-std", "colored", "indexmap", + "itertools", "parking_lot", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "rayon", "serde_json", "snarkvm-algorithms", @@ -2980,6 +3198,7 @@ dependencies = [ "snarkvm-ledger-block", "snarkvm-ledger-query", "snarkvm-ledger-store", + "snarkvm-synthesizer-error", "snarkvm-synthesizer-program", "snarkvm-synthesizer-snark", "snarkvm-utilities", @@ -2987,19 +3206,20 @@ dependencies = [ [[package]] name = "snarkvm-synthesizer-program" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70af04be0ae7bd5f2ab9a9fb37cbc8c57a74a54838f2b5fceaae3de342586014" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "enum-iterator", "indexmap", "paste", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "rayon", "serde_json", + "snarkvm-algorithms", "snarkvm-circuit", "snarkvm-console", + "snarkvm-synthesizer-error", "snarkvm-synthesizer-snark", "snarkvm-utilities", "tiny-keccak", @@ -3007,9 +3227,8 @@ dependencies = [ [[package]] name = "snarkvm-synthesizer-snark" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f62babd345a3cb305e88946f5781040591ad6351dec4dd3cbca23d46c2a4ec88" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "bincode", "serde_json", @@ -3021,9 +3240,8 @@ dependencies = [ [[package]] name = "snarkvm-utilities" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21eed8688602a2468ad1833a3cd2fbf902517d53b519ebd05c9e5cb6431e3c96" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "aleo-std", "anyhow", @@ -3031,36 +3249,34 @@ dependencies = [ "colored", "num-bigint", "num_cpus", - "rand", + "rand 0.8.5", "rand_xorshift", "rayon", "serde", "serde_json", "smol_str", "snarkvm-utilities-derives", - "thiserror 2.0.18", + "thiserror 2.0.17", "tracing", "zeroize", ] [[package]] name = "snarkvm-utilities-derives" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b14dca149fde666bdd9ea91395ff4d48a442e83a75de9ff4b4224efc379fdbc" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ "proc-macro2", - "quote 1.0.44", - "syn 2.0.117", + "quote 1.0.42", + "syn 2.0.111", ] [[package]] name = "snarkvm-wasm" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899545f032a9e518752d76ee35f601a765833b25c6717f10c78cb7cebc4fe4c7" +version = "4.4.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" dependencies = [ - "getrandom 0.2.17", + "getrandom 0.2.16", "snarkvm-console", "snarkvm-curves", "snarkvm-fields", @@ -3083,9 +3299,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" dependencies = [ "libc", "windows-sys 0.60.2", @@ -3117,7 +3333,7 @@ checksum = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" dependencies = [ "quote 0.3.15", "synom", - "unicode-xid 0.0.4", + "unicode-xid", ] [[package]] @@ -3127,18 +3343,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", - "quote 1.0.44", + "quote 1.0.42", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.117" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", - "quote 1.0.44", + "quote 1.0.42", "unicode-ident", ] @@ -3163,7 +3379,7 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" dependencies = [ - "unicode-xid 0.0.4", + "unicode-xid", ] [[package]] @@ -3173,8 +3389,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", - "quote 1.0.44", - "syn 2.0.117", + "quote 1.0.42", + "syn 2.0.111", ] [[package]] @@ -3190,11 +3406,11 @@ dependencies = [ [[package]] name = "system-configuration" -version = "0.7.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.10.0", "core-foundation 0.9.4", "system-configuration-sys 0.6.0", ] @@ -3221,12 +3437,12 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.25.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", - "getrandom 0.4.1", + "getrandom 0.3.4", "once_cell", "rustix", "windows-sys 0.61.2", @@ -3243,11 +3459,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.18" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.18", + "thiserror-impl 2.0.17", ] [[package]] @@ -3257,19 +3473,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", - "quote 1.0.44", - "syn 2.0.117", + "quote 1.0.42", + "syn 2.0.111", ] [[package]] name = "thiserror-impl" -version = "2.0.18" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", - "quote 1.0.44", - "syn 2.0.117", + "quote 1.0.42", + "syn 2.0.111", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", ] [[package]] @@ -3339,15 +3564,15 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.49.0" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ "bytes", "libc", "mio", "pin-project-lite", - "socket2 0.6.2", + "socket2 0.6.1", "windows-sys 0.61.2", ] @@ -3373,9 +3598,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.18" +version = "0.7.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" dependencies = [ "bytes", "futures-core", @@ -3386,9 +3611,9 @@ dependencies = [ [[package]] name = "tower" -version = "0.5.3" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", @@ -3405,10 +3630,10 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.10.0", "bytes", "futures-util", - "http 1.4.0", + "http 1.3.1", "http-body 1.0.1", "iri-string", "pin-project-lite", @@ -3431,9 +3656,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.44" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -3442,22 +3667,48 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.31" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", - "quote 1.0.44", - "syn 2.0.117", + "quote 1.0.42", + "syn 2.0.111", ] [[package]] name = "tracing-core" -version = "0.1.36" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", ] [[package]] @@ -3474,9 +3725,9 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" -version = "1.0.24" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-xid" @@ -3484,12 +3735,6 @@ version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - [[package]] name = "untrusted" version = "0.9.0" @@ -3498,9 +3743,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "3.2.0" +version = "3.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc97a28575b85cfedf2a7e7d3cc64b3e11bd8ac766666318003abbacc7a21fc" +checksum = "d39cb1dbab692d82a977c0392ffac19e188bd9186a9f32806f0aaa859d75585a" dependencies = [ "base64 0.22.1", "cookie_store", @@ -3523,16 +3768,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d81f9efa9df032be5934a46a068815a10a042b494b6a58cb0a1a97bb5467ed6f" dependencies = [ "base64 0.22.1", - "http 1.4.0", + "http 1.3.1", "httparse", "log", ] [[package]] name = "url" -version = "2.5.8" +version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", "idna", @@ -3552,6 +3797,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "vcpkg" version = "0.2.15" @@ -3591,27 +3842,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.111" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec1adf1535672f5b7824f817792b1afd731d7e843d2d04ec8f27e8cb51edd8ac" +checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" dependencies = [ "cfg-if", "once_cell", @@ -3624,12 +3866,11 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.61" +version = "0.4.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe88540d1c934c4ec8e6db0afa536876c5441289d7f9f9123d4f065ac1250a6b" +checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" dependencies = [ "cfg-if", - "futures-util", "js-sys", "once_cell", "wasm-bindgen", @@ -3638,124 +3879,94 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.111" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e638317c08b21663aed4d2b9a2091450548954695ff4efa75bff5fa546b3b1" +checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" dependencies = [ - "quote 1.0.44", + "quote 1.0.42", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.111" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c64760850114d03d5f65457e96fc988f11f01d38fbaa51b254e4ab5809102af" +checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" dependencies = [ "bumpalo", "proc-macro2", - "quote 1.0.44", - "syn 2.0.117", + "quote 1.0.42", + "syn 2.0.111", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.111" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60eecd4fe26177cfa3339eb00b4a36445889ba3ad37080c2429879718e20ca41" +checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-bindgen-test" -version = "0.3.61" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f9483e929b4ae6889bc7c62b314abda7d0bd286a8d82b21235855d5327e4eb4" +checksum = "bfc379bfb624eb59050b509c13e77b4eb53150c350db69628141abce842f2373" dependencies = [ - "async-trait", - "cast", "js-sys", - "libm", "minicov", - "nu-ansi-term", - "num-traits", - "oorandom", - "serde", - "serde_json", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test-macro", - "wasm-bindgen-test-shared", ] [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.61" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30f8b972c5c33f97917c9f418535f3175e464d48db15f5226d124c648a1b4036" +checksum = "085b2df989e1e6f9620c1311df6c996e83fe16f57792b272ce1e024ac16a90f1" dependencies = [ "proc-macro2", - "quote 1.0.44", - "syn 2.0.117", -] - -[[package]] -name = "wasm-bindgen-test-shared" -version = "0.2.111" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0000397743a3b549ddba01befd1a26020eff98a028429630281c4203b4cc538d" - -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", + "quote 1.0.42", + "syn 2.0.111", ] [[package]] -name = "wasm-metadata" -version = "0.244.0" +name = "web-sys" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "wasmparser" -version = "0.244.0" +name = "web-time" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ - "bitflags 2.11.0", - "hashbrown 0.15.5", - "indexmap", - "semver", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "web-sys" -version = "0.3.88" +name = "webpki-root-certs" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6bb20ed2d9572df8584f6dc81d68a41a625cadc6f15999d649a70ce7e3597a" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" dependencies = [ - "js-sys", - "wasm-bindgen", + "rustls-pki-types", ] [[package]] name = "webpki-roots" -version = "1.0.6" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" dependencies = [ "rustls-pki-types", ] @@ -3826,6 +4037,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -3871,6 +4091,21 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -3919,6 +4154,12 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -3937,6 +4178,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -3955,6 +4202,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -3985,6 +4238,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -4003,6 +4262,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -4021,6 +4286,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -4039,6 +4310,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -4069,91 +4346,9 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap", - "prettyplease", - "syn 2.0.117", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote 1.0.44", - "syn 2.0.117", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.11.0", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid 0.2.6", - "wasmparser", -] +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "writeable" @@ -4179,29 +4374,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", - "quote 1.0.44", - "syn 2.0.117", + "quote 1.0.42", + "syn 2.0.111", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.39" +version = "0.8.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +checksum = "43fa6694ed34d6e57407afbccdeecfa268c470a7d2a5b0cf49ce9fcc345afb90" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.39" +version = "0.8.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +checksum = "c640b22cd9817fae95be82f0d2f90b11f7605f6c319d16705c459b27ac2cbc26" dependencies = [ "proc-macro2", - "quote 1.0.44", - "syn 2.0.117", + "quote 1.0.42", + "syn 2.0.111", ] [[package]] @@ -4220,8 +4415,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", - "quote 1.0.44", - "syn 2.0.117", + "quote 1.0.42", + "syn 2.0.111", "synstructure", ] @@ -4236,13 +4431,13 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.4.3" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", - "quote 1.0.44", - "syn 2.0.117", + "quote 1.0.42", + "syn 2.0.111", ] [[package]] @@ -4274,12 +4469,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", - "quote 1.0.44", - "syn 2.0.117", + "quote 1.0.42", + "syn 2.0.111", ] - -[[package]] -name = "zmij" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/wasm/Cargo.toml b/wasm/Cargo.toml index 029f2f9f9..3ce54766c 100644 --- a/wasm/Cargo.toml +++ b/wasm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aleo-wasm" -version = "0.9.18" +version = "0.9.17" authors = [ "The Provable Team" ] description = "WebAssembly based toolkit for developing zero-knowledge applications with Aleo" homepage = "https://provable.com" @@ -22,14 +22,17 @@ crate-type = [ "cdylib", "rlib" ] doctest = false [dependencies.snarkvm-algorithms] -version = "4.5.0" +git = "https://github.com/ProvableHQ/snarkVM.git" +rev = "ed7b59031741f13f148f63d7cda4e1e1634399f6" [dependencies.snarkvm-circuit-network] -version = "4.5.0" +git = "https://github.com/ProvableHQ/snarkVM.git" +rev = "ed7b59031741f13f148f63d7cda4e1e1634399f6" features = ["wasm"] [dependencies.snarkvm-console] -version = "4.5.0" +git = "https://github.com/ProvableHQ/snarkVM.git" +rev = "ed7b59031741f13f148f63d7cda4e1e1634399f6" default-features = false features = [ "account", @@ -42,31 +45,38 @@ features = [ ] [dependencies.snarkvm-ledger-block] -version = "4.5.0" +git = "https://github.com/ProvableHQ/snarkVM.git" +rev = "ed7b59031741f13f148f63d7cda4e1e1634399f6" features = ["wasm"] [dependencies.snarkvm-ledger-query] -version = "4.5.0" +git = "https://github.com/ProvableHQ/snarkVM.git" +rev = "ed7b59031741f13f148f63d7cda4e1e1634399f6" features = ["async", "wasm"] [dependencies.snarkvm-ledger-store] -version = "4.5.0" +git = "https://github.com/ProvableHQ/snarkVM.git" +rev = "ed7b59031741f13f148f63d7cda4e1e1634399f6" [dependencies.snarkvm-parameters] -version = "4.5.0" +git = "https://github.com/ProvableHQ/snarkVM.git" +rev = "ed7b59031741f13f148f63d7cda4e1e1634399f6" default-features = false features = ["wasm"] [dependencies.snarkvm-synthesizer-program] -version = "4.5.0" +git = "https://github.com/ProvableHQ/snarkVM.git" +rev = "ed7b59031741f13f148f63d7cda4e1e1634399f6" features = ["wasm"] [dependencies.snarkvm-synthesizer] -version = "4.5.0" +git = "https://github.com/ProvableHQ/snarkVM.git" +rev = "ed7b59031741f13f148f63d7cda4e1e1634399f6" features = ["async", "wasm"] [dependencies.snarkvm-wasm] -version = "4.5.0" +git = "https://github.com/ProvableHQ/snarkVM.git" +rev = "ed7b59031741f13f148f63d7cda4e1e1634399f6" features = ["fields", "utilities"] [dependencies.anyhow] From 42ec63aa151ef0f6c4f802aa147f633f2b28dedf Mon Sep 17 00:00:00 2001 From: marshacb Date: Mon, 23 Feb 2026 20:00:57 -0500 Subject: [PATCH 02/17] [Fix] add WASM support for dynamic dispatch variant types (#1199) * Add WASM support for dynamic dispatch variant types (DynamicRecord, DynamicFuture, RecordWithDynamicID, ExternalRecordWithDynamicID) * fmt * fix(wasm): normalize dynamic dispatch type strings to match snarkVM serialization and add missing doc comments * add JS/TS fixture tests for dynamic dispatch variant types (record_dynamic, record_with_dynamic_id, external_record_with_dynamic_id) --- sdk/src/models/input/inputJSON.ts | 1 + sdk/src/models/input/inputObject.ts | 1 + sdk/src/models/output/outputJSON.ts | 1 + sdk/src/models/output/outputObject.ts | 3 +- sdk/tests/data/dynamic-dispatch.ts | 53 ++++ sdk/tests/dynamic-dispatch.test.ts | 302 +++++++++++++++++++++++ wasm/src/programs/data/helpers/future.rs | 12 + wasm/src/programs/data/helpers/input.rs | 61 +++++ wasm/src/programs/data/helpers/output.rs | 87 +++++++ wasm/src/programs/manager/execute.rs | 2 +- wasm/src/programs/manager/mod.rs | 2 +- wasm/src/programs/program.rs | 15 ++ wasm/src/programs/request.rs | 9 +- 13 files changed, 544 insertions(+), 5 deletions(-) create mode 100644 sdk/tests/data/dynamic-dispatch.ts create mode 100644 sdk/tests/dynamic-dispatch.test.ts diff --git a/sdk/src/models/input/inputJSON.ts b/sdk/src/models/input/inputJSON.ts index fbe9cbf2f..db0fe7009 100644 --- a/sdk/src/models/input/inputJSON.ts +++ b/sdk/src/models/input/inputJSON.ts @@ -6,4 +6,5 @@ export interface InputJSON { id: string; tag?: string; value?: string; + dynamic_id?: string; } \ No newline at end of file diff --git a/sdk/src/models/input/inputObject.ts b/sdk/src/models/input/inputObject.ts index b6a991e4f..434339d87 100644 --- a/sdk/src/models/input/inputObject.ts +++ b/sdk/src/models/input/inputObject.ts @@ -12,4 +12,5 @@ export interface InputObject { id: "string" | Field, tag?: string | Field, value?: Ciphertext | Plaintext | PlaintextObject, + dynamic_id?: string | Field, } diff --git a/sdk/src/models/output/outputJSON.ts b/sdk/src/models/output/outputJSON.ts index 3537f0484..e79c2c311 100644 --- a/sdk/src/models/output/outputJSON.ts +++ b/sdk/src/models/output/outputJSON.ts @@ -3,4 +3,5 @@ export interface OutputJSON { id: string; checksum?: string; value: string; + dynamic_id?: string; } diff --git a/sdk/src/models/output/outputObject.ts b/sdk/src/models/output/outputObject.ts index c3fc29eb0..5691a9c1c 100644 --- a/sdk/src/models/output/outputObject.ts +++ b/sdk/src/models/output/outputObject.ts @@ -14,5 +14,6 @@ export interface OutputObject { checksum?: string | Field, program?: string, function?: string, - arguments?: Array | Array<OutputObject> + arguments?: Array<Plaintext> | Array<OutputObject>, + dynamic_id?: string | Field, } diff --git a/sdk/tests/data/dynamic-dispatch.ts b/sdk/tests/data/dynamic-dispatch.ts new file mode 100644 index 000000000..c7d02af02 --- /dev/null +++ b/sdk/tests/data/dynamic-dispatch.ts @@ -0,0 +1,53 @@ +/// Fixture 1: dynamic_transfer_pub_to_priv +/// Covers: record_with_dynamic_id (output), record_dynamic (output) +/// +/// Transition 0 (credits.aleo/transfer_public_to_private): +/// - output[0]: record_with_dynamic_id (id, checksum, value, sender_ciphertext, dynamic_id) +/// - output[1]: future +/// +/// Transition 1 (test_dcall.aleo/dynamic_transfer_pub_to_priv): +/// - output[0]: record_dynamic (id only) +/// - output[1]: future +export const FIXTURE_DYNAMIC_TRANSFER_PUB_TO_PRIV = `{"type":"execute","id":"at1jdfcgjg2rj557an4yp3jqd7l082lnaf8glwa9kms6udejm0rmuyscl23dj","execution":{"transitions":[{"id":"au1pcpflselr5t3388z4h9d8nrkg38kcdugl8a4fd4y9kjufeupcgpqdnnr59","program":"credits.aleo","function":"transfer_public_to_private","inputs":[{"type":"private","id":"5967937462307069153609763750388580527664179030252138179661733822107436960687field","value":"ciphertext1qgqxhj3z4j69jx3e0g55w6m5mdf02cjxc28ld95nn48p7x3audffypscuzzc6q4z2nn7mpwnqps5z78e3fzsxsnhtwpcvk0hag2u3n9hq5ys6hfh"},{"type":"public","id":"7414711679616971493788162804256399922422624924874275066688454275720188041435field","value":"1234u64"}],"outputs":[{"type":"record_with_dynamic_id","id":"4264113407511347004847831761505929124636057423741483702377902705446146217811field","checksum":"7117930001611763553311282775351584349017667247096160350906241672011897441044field","value":"record1qvqsp7x3qhyqx43wvdfe6ttj2lv06hmls8uznfqgdjp28klyy8wxutgrqyxx66trwfhkxun9v35hguerqqpqzqzx37lz6qygwqz3e7azhtn7u40p5qt9zjxvklx8zmhyr8fcx5y0z8ugh6w2kcvr2scpavxs7c8jmlfwedd67frnw6qff5kvt5wvt30qq8e8xnw","sender_ciphertext":"3095425280187963304829173723786041703108770226542383173509612499530577787338field","dynamic_id":"516401906143901259191749101164901797071163367235654600972680634285668179302field"},{"type":"future","id":"972885488774796241452924857130838612318566578418894974047768182162611758923field","value":"{\\n program_id: credits.aleo,\\n function_name: transfer_public_to_private,\\n arguments: [\\n aleo1jujevsglwk368geavfhq0gu8pjkm4s3xtfdya63ljk3tqqjxaqys45yzf4,\\n 1234u64\\n ]\\n}"}],"tpk":"3576710885511774824815337252612484126496979162559185315472419300054336627274group","tcm":"5283871843337864116499954751308893920287340136958489589301982445174619709218field","scm":"3641033720944516835923001739639146627419573066331147043782676513687918099786field"},{"id":"au1smpj6u7sp2f6ejgshfa9mrgj7p4n78uptcw32gfzrkv9ms36quystdca5k","program":"test_dcall.aleo","function":"dynamic_transfer_pub_to_priv","inputs":[{"type":"public","id":"7739023282644420247659373436040864472920325261755885935617457812702499771945field","value":"32497618326483555field"},{"type":"public","id":"5789892904599774654720291260525667174750054366773467071594964978163601866156field","value":"1868917857field"},{"type":"public","id":"6003937225321956013941902886494865897493948996175638804781652180255536012374field","value":"163031276046149327277138208237194600527678254627957973064970868field"},{"type":"private","id":"5483621010747016535835096547543095748371446590791393263852393396080821074246field","value":"ciphertext1qgqf6jscamthudekyaugxkhfj3tv5d9e23a0xqqq785gpt5py2c87qxnxlwqwtq562m8tjtll2s6m9nj6vfpqaaxhyzpvd0przxltzh7qg4m4tv2"},{"type":"public","id":"6130752427110659999250346046015909676286323218546436832365679226302096119102field","value":"1234u64"}],"outputs":[{"type":"record_dynamic","id":"3595660378369771963007441081821563255360604870699939861510121149629721318354field"},{"type":"future","id":"3612131906016895034148144350319066151432893876571586114611916540413838268551field","value":"{\\n program_id: test_dcall.aleo,\\n function_name: dynamic_transfer_pub_to_priv,\\n arguments: [\\n { _program_id: credits.aleo, _function_name: transfer_public_to_private, _checksum: 4084319180982256212772062219217732585661766881946880293072051094411502326680field }\\n ]\\n}"}],"tpk":"6615511335695644818161025207111852283731631692388814169161305359164460894278group","tcm":"7946690758754156429025644125030067091939677051879655167137866272925561852908field","scm":"3641033720944516835923001739639146627419573066331147043782676513687918099786field"}],"global_state_root":"sr1t8dcttmmtlm9t79u0xn9cw6n3sx72xhd77vgx7vfsms3cf0rfuqqnth9c2","proof":"proof1qypsqqqqqqqqqqqpqqqqqqqqqqqqzqqqqqqqqqqqqyqqqqqqqqqqpmjup5xdw2zmwq4azks76emz2q7nhmmq488dej7htpy8rcr4xq0gfqr746rvtpcnxst08lt9h9e4syajnj3n3ur0ue5wa42as4fw627uccng4zpjdzk0dpqgsy6e5gaz5vn9x7crel7tn35mkvtymfqgvqp3tv2r8ke8cspxn9g7lpp04ucck22xt56shd5r362dsmtufz052n9atq73slygz0ueg90zvd6z5yqszt23kwuyxzvhx73d9p6tzkw5tqrdxgdxwa7fne0e88vauzh0dtqes0wrmgs9guz6ev4skvd6r24lqp36yru9khazsugtmmnsefd7f43p3q7xdhkysa2nthtr3zfct7xlzeptamz64pzrxpc4nagj5z6mvqx8tepqd2fzxxs4d5h80clqthggyreefj22vcks5shjq0yxkwl0htfjw4s8unzmp0n0lj2vemcc8cq828y08ystnvpyjk0wajmrp4kdrejaxzn9xhx2tc6nmmma20ffqw9kwu34hv7d32u26lux46shasqqv378056fwkatue836hm6596x5q6w4ax26n6rcgvtk3956sfsz2l7rcw4dp73tuklmwasf63lks6gqzfmnyz5lszd7x458qvr35zawgffhds7sp63xsa5fg9vjpt8gz3lzfp7gd8qgk38dkumyrla482yqpmduktzsf87xz0axwcrd8uqzgzmp6xxy3u7php49lwwcyzp7awj6mpj3ajpsq0uurlnscf954ux5q24sxy7nhuc30xc5xy23k53xvz6j3meeawfxqvhlj7tuphes8x3dc4e94dwej9m5nd9zzrdrle4vkqwx2uuy5scwh8g49u02epvw6lmr0tqtkkacj0uzqsnut5ym2evstvv8zhq6gtexgfwn0lp6xry6zqqfuez3wskz3nyldnarp3uyf4qluyamszz48crkwr0f2vcenad5wuvlzt2lyy9jrxzlf0e44g4j7tsr3lcfc65j2s0zyslk2nzk8ctag9y2wj7qgmm5hvagtz50jf9cunj52362tvkevxufh6679mruuq4q949j4j3fa8lruac5fc2ytmz6f42x9ljncshs0wk9vea5r5ju3gshp9h9fludgmyefk5j8erx5ctyqxq70z0vnv80atu05jvum4gar3dv3qgt265vgu6drgpeldy7px3lpreu30dtj6cylfs0k2qeequpgqye79zzaqzl4cqjmr94yn9j9y974a0sr2emcursfs7qqngpka9y6acl385sqnyg6mvn3csag74vtuptewpndh4l44k8gkksrm50yg79v6d09hj74ttuwzkd57ccpqpqgf84s5lsgl2eu0gn3cvtwuhkvrmk28je6kv9l63dys74qducflnvrwn6qwnxu4x23zj5lft3xkke2906d379jdld8wt07pg3avltnpyqxgvaxfu5q06zhkczk6umqp8cazyfr65fpd2k3rg8p7xlnxep543ru77x6fp36jk4q0v23pk2mn5f5fhdqgzheg66k7dlvmg3cyaz5s27y3klcghn4keg4ryxsecg0d4edm7e8c740vhkgfahmdevztwggf0dcn7ss0c5v4ypq0sqhxeey3dt2np7kys5fav2vdfnvnsmqgc7p0jx04dpp4w88q9g3gmf29qz77awfrl6fnnh8u7mw8e8qwxgrgspauxhgzqve4qxulg82e7tzs9hu5yghr0lwq6354lecvsfqnm8n4qxpgqadgpm4anr62n642a6naqh68cu2cyt5w9km8u7lcu4uusqygrqrylujlrlc6835mdqkz6ewqlhd77jlqzmk9q99v3fjvmspjtggx6rynwe4aqjczdfurd2y3gua8tw5t9rkdx9ac9hpjty667sgngvyz0asjwyxge07ry769wc3ka0lvjut3785gvwt5h2nvrsvteysxnp6j5g6sm2k2ykydq0ydwj8waf7zlqt6tcpd0hz7fe080pt9ayzssxlgeuugarzyh30qvaeqkx7q35tpwmpvsr0p8pes8apmxarv3qrcszujlr4vt39f8wk2actna4vkf8n68x0ftq6dvj8dl4yj9y3f2ugyvlcuy5saftyws8ka8nyvd2ngpgprydcnrdze49ch8gwhapj9ggyf42yzlll0gn7xvtmlejku646nsmzadz5fvrqrqv05m6ulny67kpxegqr8wvu6mjvdehxegp3yufzxj5pvj7xywc39vvnkcqvx9qrjprtkj0gzxcmxc8kpd38rtl64ap0m2vs486s8rc8deexjscls524s2gpmha7q3q8esj88ycha48wmmtwrpmjkpr9v4mm0jzq8qu2tdsqx85zfnwk8ypw7h6ywqhqgfm5m4a47284jyjyeul4hmfdpp6qmywy45myz2l9mm68metqnm80kf92vkkdd7pyl3ljdmxh02k3a6e6s8zrddfpgftxwk03cthlwh3vw7q5uj5hdacp2nzvyyujwxz7j3azzstpr5xjp7yc374fqfxguzlvw0f9mwqphh8dp59x27lst0s8mnakgxss38kad8ckwxwkhcdql9ughgzgd9p2uq5d7cp8d9fcjdejsqruzrsw9qahhrwjlyhm390gwglxt2jzvg7w4mt8f39glqwufhny7wurx0jlukzdm2ls6egxfgdnd3q52ws4ncjq4u4agt3uyjfkeqcr2fpqpsqqqqqqqqqqz9q60l0lau8j5kmt7jdwzl0kqcrudcw56uejv2f0vdxa8ckeae20s6p6lyqpk9jhsea2d3xwt7a7qqp2zpwjsrnhwajjdzkvyxgps4e95sexfa60swruhxvp35rgsd2t5e5rznkc09fwnlg6yclrc4pqzgsqqh40z5rspu4yt75uq59xksydvwe0epyfuxhv0qck8gfye0v93s5pj0kh3rhe6k4c95esdgtlqwwf542g8wk4c3j4u0k4lzrnepquv4sfsahh0yvv9rhldrenvkkkajnvqsqht7l93"},"fee":{"transition":{"id":"au17s008actkj0px2axypcddda5laeza3q0mt372grjdta9x00035gs2jm2vk","program":"credits.aleo","function":"fee_public","inputs":[{"type":"public","id":"6869332197193445904700284599538860172991302766808411911430347203324640704110field","value":"4105u64"},{"type":"public","id":"796199301169861628016948664720874705168650620201291011899017955059778846606field","value":"0u64"},{"type":"public","id":"7184656139085985156417660140955353483567119241433833905433181299478048049889field","value":"4507483086141467314696755302751445255356275442674905199690888977497094580495field"}],"outputs":[{"type":"future","id":"3908297900432248245407586895656719645978201872846460272342396167362340884509field","value":"{\\n program_id: credits.aleo,\\n function_name: fee_public,\\n arguments: [\\n aleo1a8llclfmn0lnh39ajzu56j7wqawk9qyl7u3pgt5f64msmdeyg5fq09q8t9,\\n 4105u64\\n ]\\n}"}],"tpk":"4723315438886740017227583005365499965500833945426435242925433150240285472266group","tcm":"7177002381703871487840630034562152622904027289443366848207414958184820172182field","scm":"5095678889134714181572686525122225866543329958943404554627209095073748927023field"},"global_state_root":"sr1t8dcttmmtlm9t79u0xn9cw6n3sx72xhd77vgx7vfsms3cf0rfuqqnth9c2","proof":"proof1qyqsqqqqqqqqqqqpqqqqqqqqqqq9x6gh5v6z2p3vufhpzvqymr866eg5x0fld6kfacsd40rfr2cshcd9839ar8uczhecsxtpk77taxcpq94fc60644ht4fm8ua4k674rnaw600j3y8gj7amqk0mkstmseuy9uxq4g9yhvnlz6x7cr0de5qdzfqzxgd7k0tqqk3lqh0h4tu8eu7yyqmk8vja06jjsupggdgrrp4gvx57f9r8wafs4cmr5m6eq7zeauvqx0yt9pd4h6kg36lheg4qtrl5sjr9k30ghatezpnzvnwt3eem089f4pt9ggafvxdz34xpp9nczkxcpkmxhe4zq27kchvff650vus9frdqm7tjh45chqtw3ehzup39tf997hl6xs2lf23xtq5wmse2ykgycpxay388x6lrm747gwlw4uf4w0qjeyv6p24r3yrmvt08q6ecc2ermfwvjyekqngyms05qujevfryesrzfuk0kz2nlmr70a5ddn5vg3wlrq4uldtjsun5dakfwhzfgftvujj5mykl6flgzuags37a0tasexqqal0vzqnl6dfepyx7y3vh9jqp5gzty3c6pqp60c8a8ucjnh3suygpxc4zmnqq5daz4dfy67tptg7qdydcrdr5mdnxyf9dzsgvel6e6sc7kmk66fmchmhys4pzsl69xmv3mnqz954shfd753q4h87nf22sqpx4pknxpr24lxpfu2r0vvzke6nj30ytutd6qrzkp3u7rva4hjvxd7phqqj8sfvmaug6dstkxrcf9dq5xaxgel7ep5t4uk8rw2t04gpv45ldfn040mvpla2tulk0nz85eujkwkhs9adg3j7v8earaylvaqkp9x93su0t5d6pkxx495psvjqhqwdxnqlze6y4u07d8sve6ywe3z5sjc0fr8z0u08ekhmf26682uyhxy0l9vnc5qy4yuygrhxkgxxg8c8acemkw9ap9nemmp98yxntq5ajcvghzap9e34q7xwg9tu2dkuysgtzxy6qng82dxnzj68n6jsc7gwrepynm994gzzlvylfv503dkp3jyypx4m4x0h20m7wgl2vplwujxvfsh8xwm59ndjxwrpqz5f4qpf5y5vqs5cam5nsdajjg4aq069qlpjsrwvgtggehzzcvxhn893ns6q0nzdw00fggacpmmw6257pa647f03jt49zjxyff6fek8n6r66ssqvqqqqqqqqqqqhxk28kf6vfp58ydn97gq7p7v2c7az55d89z3y9rzljvgxdmqk600kw2qdhjcuf6htu9p702ymquqyqrkqg43js0elp92c6cmduus475pje0lt84xgew0h3zt5tujyckk835rafgtz8mknyhmu03skrfdgcqqyec9p4ewdj5guy3d4nr3u65cnj7y204yf9ppw2c7645zrt55q4s7mdm5duues84xwk0k36f32xfxh50a74wmmm8yln0gamkfkd60ledtkklg5j00kvsfxejl5mpgypaqqqqvaxwlc"}}`; + +/// Fixture 2: dynamic_transfer_private +/// Covers: record_with_dynamic_id (input + output), record_dynamic (input + output) +/// +/// Transition 0 (credits.aleo/transfer_private): +/// - input[0]: record_with_dynamic_id (id, tag, dynamic_id) +/// - output[0]: record_with_dynamic_id (id, checksum, value, sender_ciphertext, dynamic_id) +/// - output[1]: record_with_dynamic_id (id, checksum, value, sender_ciphertext, dynamic_id) +/// +/// Transition 1 (test_dcall.aleo/dynamic_transfer_private): +/// - input[3]: record_dynamic (id only) +/// - output[0]: record_dynamic (id only) +/// - output[1]: record_dynamic (id only) +export const FIXTURE_DYNAMIC_TRANSFER_PRIVATE = `{"type":"execute","id":"at1v82rljem6grav07tum7sk7qtrk96sn24wa47s9y7hr9jsp8vzyzslrajv4","execution":{"transitions":[{"id":"au1r5uy69qvxedlzvtptz2q9yu9w45ztqmmmye7neswsl58nmgsf59qy6st0v","program":"credits.aleo","function":"transfer_private","inputs":[{"type":"record_with_dynamic_id","id":"4340801102707416300638609337989154355669556610865126467402962093253730701049field","tag":"5398762306679927363306932652002014161051209079090258590089376223109206520184field","dynamic_id":"2206308467419848471460935096827294116529344041620953846068697778246504600423field"},{"type":"private","id":"2380489691230251256582067177585945909396421923061876053912587998533044961509field","value":"ciphertext1qgqy96pz84yes6qvxn7gn06hchfa2aycnuaw60tz3qd4t4h2vevuyqmlg244cmqpl77wv3r2pae0pmtteqr9pzznqxumpamvhuxeje3wpuxxf7dn"},{"type":"private","id":"6088522186271007974487692151457127678825925966808654576075340201309891244131field","value":"ciphertext1qyqg62wmgqna48fcf6r269aqe564zpn3n2l0r556cx3ffamvxu38yqshjscth"}],"outputs":[{"type":"record_with_dynamic_id","id":"3919166698375740754085198038657190353266777730539147154913622342478735832624field","checksum":"519513362128789580688485430709106745858113205533183342808111599099893051923field","value":"record1qvqsqkctlp9h0s7hyvgenyu6ntphl040lvg0f8ueme2d069ge6tcetgvqyxx66trwfhkxun9v35hguerqqpqzqyss634exghw957nl4xqfu6x4gflqlsuvqp0cuc6xn3dltj6j9vzyykyenv0t5d62r5kvdm3fgnev2st32k2rl85llhxezlrycq94fss0tllqw","sender_ciphertext":"1727033995548011888104594677794859484152482231605726128786372460179390353375field","dynamic_id":"8072621597261150069716218104584875067121673800724991710069455167064373854399field"},{"type":"record_with_dynamic_id","id":"671738505188211445722971045237791972658863538212837702282366785801427977978field","checksum":"7507237973010423779217220275788139625914165302238293674390832559293970169463field","value":"record1qvqsq6xquvn900jalgr322edcqdfs5nrdukhrs36uck6rslk8thnwwcqqyxx66trwfhkxun9v35hguerqqpqzqrjehet962hvwlsen3xrl0utjel84nr0kk4eywc9u6cqyaj3kq6qmykyt9ppnxuuafxrhcuxajhjykhj9uhxw2srmdau4n6eq9y65wsqf4qtd2","sender_ciphertext":"2307238033614689327816935352899443685274320652618064998127848688083490821505field","dynamic_id":"5893915337901927501447085724836791359290644103409888672646579484941623936350field"}],"tpk":"1530929441290996923535832156559604471523019511586212647463225803823638105762group","tcm":"3285326901513455782396475400326099140356598844239538198415602826279206954661field","scm":"3654371952914406981022167568515537402624900888527422499983539913501443493708field"},{"id":"au1sqc3gnz0su8ama9wxqggw9mrtljx032r5sza4h7gy0ptyllug59sns85qw","program":"test_dcall.aleo","function":"dynamic_transfer_private","inputs":[{"type":"public","id":"4967853756956195126091283842215031834821438104414125798446070292481547428372field","value":"32497618326483555field"},{"type":"public","id":"4881978119624521131450942566946569139070269288156945398766420271043877573482field","value":"1868917857field"},{"type":"public","id":"5345901185991028858136822427967683009681842138839957669811790665209383060767field","value":"134856310785155548303824185267685782132field"},{"type":"record_dynamic","id":"3182143570078819068370013460478348957502692124826933237026687603726691336173field"},{"type":"private","id":"4352694699104306331929131805046103796892153633611127205497713638074324123280field","value":"ciphertext1qgq9vvyczsh4yazuxlvq4wntj6hn3u95rwp2cy6d3fvlfw5m7k94sy0thpxa9q4x6qczcyd4mf9cprfxk038rtuwcwgw0zwnjxg2elk8qsmtpf4g"},{"type":"private","id":"1490651799772472899122936149876793471094328356819116210285071199498540219189field","value":"ciphertext1qyq0pzz9xevjkfkfee4au7eecv0z2n4tll9est0ludahwlk28yfykqgwckmty"}],"outputs":[{"type":"record_dynamic","id":"1693188195607589779730689858483580830510771249058193811296868188639853093610field"},{"type":"record_dynamic","id":"6920560536318680243490606243927416557250783284781706272433755876467105262560field"}],"tpk":"7739529172162544540585629530713726513961462592000646057729176187133493950386group","tcm":"5059178706032618674347612141353492201428751630846341843847210223190306679166field","scm":"3654371952914406981022167568515537402624900888527422499983539913501443493708field"}],"global_state_root":"sr1jf97damfx457m3m5vyw5eflr6agl559mstdy0x84wfwvduz0gcqsknqpqt","proof":"proof1qyzqqqqqqqqqqqqpqqqqqqqqqqqqzqqqqqqqqqqqqyqqqqqqqqqqqqcqqqqqqqqqqrgry6e53cfulcngg9jvuwcvn2cn8asdmca8r94xuus6cntzp3qgluskgwa7v8wq5s65v7q9f06heqf9zvqd3lkp6d8qal48tq7wmnhkxxrdynte0gdrzsgkm07utpz23zj85jrf4mukvvyjfeed7uvvhcqzccmsa7wg84suvv7melsld9vuaslup0qqhje2c9he5uwz6mksryh4ezvs4qnpvyhp3nk70320e0vqlahyhrk0ywu0amgwhne0wd92z260lr0wzhndxtesl3grjmm2dzsslr2hfuyjvew7r7ehct5pdvdqrcev6r3w48qu7gejdplsaqkwgup2tq3mg7mm6v9csevyvcle8xy5tnprvekjs4uyldyq4lrlpw62s8tyyyftumgdwdkrd9qqg3el6092cvj6gln5xchj430dx89j78t5wnutaldk4gfu5shlynmanzhpvqgpm0y5avrkm847axxy3j0gnq6540swq4y2mz4lzpxx7avdu86ytt9z4rd4x3lz34npur66prx3fk8grw03e6qujkz38nzzha49svxlf9a0wz3dkxg44hs0k06zhw87nc2hfgp7m9vexleryqslcjemyew9sqfm248l70fa4sv8zhzwc6kmdpdffggj78u0r7s0wvzd43xkqjx33hrwpkxu2g5k3pcr5kjr0ku42qrpdwld6604363ye0nqa348w23sqekwk4ltrdftdpsz2ycyzqqd0dcfvayxp54f7zj5crfll2kn8kqzt7e689jaltgpl8myj8wttut03gelynps7v6055gncr0a30vc8quzxnmqahr732alu7c6rkvqn95qd2wmyn5m3zmcaeemr289gxajjvqzmz885f7qjpkelw2kjwwu2adt4dnqtqkc8xjhhfkfvdgtwczcp4xmx7h88qm2eljyfyel0sskyys0w0nf3rsjdzf6gmv6f3tunkgpthm3vw9xe8vhn0crtlayuldysrs42ya8qkqn67h4m89ld32ca5ecz0zlrrn3duhwmwj8fr8t7v2r3n6ttfv0yqs0hpeyjlvjq675sq9pkprrqt3627lxz02v0pxjcmqpmsgca7rhnux37pvj0fsl5x6ew7sxt3ccujc9ukylmhp8m6salgq005pjnx0hp553mq4wg0gzzr2fcc6hcsxhwxegyu9lms2z8hqcnsnunwcen4q4643qxay8k52n68uqqav574frqx2x078jt7wujvgu0jacmdfmvdk9uzhurhudex8xtd3yxp3wmnhv5luyv0amlpedl2dsqk09tgzknyz6xw90hu26w55242awjj3w0vpduxnpt4z44yz7ylkck9fk38t287tlrmdw6e55zwc4sp3vdr47uhjzlgxkwpw65kc4ffxjacuamvf6k5wptf52ffjgtgncctlhzykqxl8rpm00x7w7h2ad7q93qrrwau38eg995702275ya5hkg400y0k6e6j5svy7txgl6w5vknp92s347fjhu5yvzc3eaatus6qqkqmj40t2wtu60rsh47kz9xlx69qljrak5j77tk3l3wqg2kzw5sve0jtsu9swm9tmwxh5f6zw2lyptlqah6xm3a473eg00j67rx7uzz8mjuh6s5fhkkrz65u4xq6k03sy4x8hsmuzn7wdakc3kxhafs6cq37fm3qf6d707ply3x60e64jam3s9rzrtz9pcg4kgffzut69sa0hysw896t9k60klgmvh90fxrrksxqh6pde4tyv7f780n9vctnrh0uwvy9le8yju9sk95u9zhu295nsjmm90d08cxc0vyyqzfv3zh55t0rhj33q3h9h5wh76ky48x6227cscr433xrcpk0c3apfuvp60alay8qulpqnfa838tyvdf8xs8xls5r9j49e0lh49vnyjauu4ss64hjkcj795yfczgjf9c0ttg930rm7zr6ejd6gt0jygguufcmz2xep5gpeedezykvw0k7lr5n8as678sempj68hgwlqazgwwrh4dpsmkpj50zw0fuqhcfpzkcec79kf5wk3gls4mld8a74g59s2df9dzsmnga86cvju3vwm0dqly9y220d60fp29gfs76jmhljtrhyfv6n6v946vvg7qpzh70n8ezgl4k4pt2qls82xq804vzx3mcs7fffwuakkg56j22zmsp5dzyfwjp5n0m69ck497052qvm809t63f2dw00c6n7nhpra4jfpdjnm5hya5rxknt644u3645wppmhlyvhyd9s7tw3mfzkpqgpu4davvpl2c6ajgp9nyq0dqzjhcssxp4m0tphptuva9sd6d5th94gmkwe449dcj3fjhda5ar66ka2jwqtwk3zjvpc69jn42etd7x4a8cen0mmn2vz8j9wtc59xyjf63xrtsg00ps64tlzzlz83uvrpn8vu45ta9rps0r65492qnm40x0ev8t4upmy09tg3aunwtkpmvc3pwmuf74tt2pzr2j9z2kk0qhs7g0d9caup0tawqqqpk3c930j4v2uathzqm2jsk6r58t3u6rulclq6gddcm6pz2zlz8ndg24449ltxtt2c28lxl2ppu5pm7ju5j046fcvt8vw2zc8kxcpvd6f0lfh30k2tdp33wt20mf48ga7n6jspada2zxcwddnwvfw8gdqzst5ure9p8v7rxkr63m08d4wt4uhdgcaqnkqfgfp3uatxz4c7uxuh3rqkqwl7r9q6gkf4dthren6nhdmq2zfg6hg4yhqaut3ps8twanpl0d2w028kujc0atpy6gv5363rd4t32e6fvspv508kdysrgtzxhh4dmxwm0wjrn7y834xq56tkyukczhvjwn8uuqhkfdmaqsgemdxvp4daghxvqpmdahkr9zqns7rges5h93nmvedknu5rj7rhy8pn7up8swjkc39m6ej6xe80cl4hua3fyyn9jjztq6k2xsaj47l7z054qnm8wlh8yzr7nzgnkzg3mn8gd7prwgh04rhf68q79k5wyftpgtsnkdd2avyp05nes5h605ja0d9ckztudxj9f64kjmmrzj326pqdf07znz0z8f4x39jry7vg8l8j5h49eeprdpn765vrydm026ykzgfxkx6935a82gdmm2hurfrhhjtrrdsy93hezwxkugp329u6pvvmurhl8jl4wtf39yy4kwkd43z27ek68gc545ykfyz6y7w0rarxepsuzema3jq33qpgwlxmkxaaf8w35dg8ng2sl4me78p0snhqpnwjwyaz82je5h7782r38e2ryxhnkewe07ewsyxqrjtkwpcufhey79zrvnq9vt63ucxhuhfcrrulxf4ef2pmqtac9rnpsfeyk30azf4ldjzrygza7t4arwt8kt4l9dyxh08vmh4f3fu96urtss9a3fj6dsutdwxfy9vyu87ugataz9dluf0ewtdu28csc33dv55xqsc8l4c9kfl9a2jvzezmeh6l57cp5laqqvrmkwqxqjkc7mz2uj0wm5zsnp9yp5nrf4hpqqcqk7066wm043dhfh3ehnv9utdh6wyq5ud9qn06lm2ugqjx7zq5l4m87nrdtlf8k23tyxptquf7s4nq0s42u5sf53snpqqn57d4kq2jrgrfrgadf5780yqvclayyxuk97vy7typcmtsslshf54685035gnpgm3psdkhxccj5utkwrqyygufhgt9cq2ly9q696cv4ar4q45uplfqagh99qnfqfk7dgh53mwepwf2dnesse4ewh8pzw00ccw2ev0q7xwgppq5unmt73zqq70m8y6an7dx9t6m79z67wrklu9qulj5fgsfruet08v4fqvemlzuhkknt4kyz9vhvnp2j07uhscr20505qtjnc9gng88573v2fetfat53ch9vwym4ws9lw7evmkfvasfa69n0epp5rqxqqqqqqqqqqqfhh3vn5yfg8kj84eyjak6rkvh6zrm8v8kemtxwr4ux0jr8k6xtafmz82z2t09306chgntqewda9qqqp0lc7wrw2p7je8lw69f4ye5p7z6pncrdf25cqcyhve9hpy9ujtstpanlzrw9zv244mn2xtfz3m4jqqrqtzcs44n0t5zlng6pfqyz0dg7ct02cjjwlcqdwdptnarrcxq9sznutc4zykjr5fvftcy50hgvvyrl854huejxekvzc8l2fn7aeh8r85s2dqrphs4afynpt9a0q8snjcqqq9737na"},"fee":{"transition":{"id":"au1mhhp24znqf42mpkecfr37n0rmwj43qdd7l3jws4e7vva6kxqucgsnv7zyp","program":"credits.aleo","function":"fee_public","inputs":[{"type":"public","id":"3013802753895152782509284949587611851712315891457084861075613732393293026444field","value":"4314u64"},{"type":"public","id":"134511851084984024405623079175762687998935071396197247058013707986826795260field","value":"0u64"},{"type":"public","id":"6580592465863761611618257469464912777545435411281832957568242911385950887073field","value":"2701258829152317233190900775944091253221817968742875072141452800748203225836field"}],"outputs":[{"type":"future","id":"6197065375941196019139071993789192055316648415353308612184367165214970829995field","value":"{\\n program_id: credits.aleo,\\n function_name: fee_public,\\n arguments: [\\n aleo1a8llclfmn0lnh39ajzu56j7wqawk9qyl7u3pgt5f64msmdeyg5fq09q8t9,\\n 4314u64\\n ]\\n}"}],"tpk":"3359257794367559419067803970362834946075755598110298655622362404426671264560group","tcm":"4820477280835216246069196743884161790791742770904147102743658985707359162492field","scm":"3407566919520391537827095381082921197605311161096619515030678677420400864801field"},"global_state_root":"sr1jf97damfx457m3m5vyw5eflr6agl559mstdy0x84wfwvduz0gcqsknqpqt","proof":"proof1qyqsqqqqqqqqqqqpqqqqqqqqqqqzjfx36waz5n9pgvcxmqpmt0ngah956ucd2dfa4mx8e3l7e4dw8ld94rn4k0etx7lvml3fnezy96yqqyz6epe6k82kpgtzdwlk3xnwtk2nqm3xspaq7gu974sp4xaw4cmvf57uyc504t2t5a0z647kssyr2q98ug4ffatlysjvp5dtemf0w3w76wgrtlw2f374zwj8rqw5p3zdek84nvk9x0zs70l54hdj66jtnzqjphkydlyklawvs7h0326vy4szmj3hylhvcezandpz5qwvhhs95tuwa7n8zrsctmyjdq4ghatt5jsqz272zh35l9qxy7eugaau3wh3guqklx7xqa00wv3myhl22qsr6k2rq7d0vrlkdjx9969p0kpz5zqqqztxvxnm9836tf7wk2kgmwv39vmnm3jt03vkjewgqugsyatp0d6gk768s6j5jhyyjrfqn3z69zngspee7ak2lgp8kr2l6x892cg4u0l8zxurcsllags4c294jxvek4nuexaatagslynudrv5ee5jrztwaqr4fjvyzjv7u4vpltevsgvr8jm40uj9w7ddgflwnvngn8lm597c772ldmwgd8u4wnh8g7vcur580yqzx8lzz8p9jm9qag5a9fx49j3v6l9wde20tw3plgkgsyuewh6we3g69464zqlgf3a4h4nvvymel3vqk4t5zs2wpp5pt6x0a784s9cqqgcfqvkks5c6244znqse2yam7vqqtdq5hjwx7am7j04pv8667jmnr4cd4envkpgnuvjq6ueepku07qcutlahsmpk4zjw2gvjp0enuvf7vlze4nquhnrhts2ym853mcp9zxjxfgk9s37xennazp5w80jepa85r4t8ckz76wt4tjj2y4e0cacqkhcsyj8pvskgu6xas4wkhmukg8a909tjmczlnl7xv2pqyvn3e3qx2xzjskmzmapkk8yp2e3j3hyjeud0wl0ph8lfx9f0na9gsx5uvsp09wxeyj79xgg03s5n8dzjlkkyfpc36g0nhg0c5dvj4cnm3x5m5z0fgrc2fskwsxcm9tdy6tz342dr5qwthu0duq5jv0fck0qf3gklq8aafud8q6hlgmcc4m5m386hp565n7zy6rs27vkkl9cvcu444j4squrgheswws0ws3wfh4emxr5pq627ex3n8udylcx6w4pzshheuassqvqqqqqqqqqqqlcsvklsw6ns0t28mzxd8l0sv74ynvsmpahnk3xx7wqyhkl0zc0uhygur66qleahnncgsp8afng8qyq0fslh3hdysgwgffww8e8dugv3rh692nmxfk9nkg8wrs9frl4s9mwsvy6jx2mlhsrjltha2acyqeupq8c9r3uhev9ns0py6k7az0txheaw43kd3k4fd0pdyhyqwyl80desk9uy47sts9hyaal4l4ca8envql6q45twyxutd6znzehpjtlz4nnaglnf2psrac5hatw4dr4hn8yvsyqqceczce"}}`; + +/// Fixture 3: universal_swap (cross-program dynamic dispatch) +/// Covers: record_with_dynamic_id (input + output), record_dynamic (input + output) +/// +/// Transition 0 (credits_a.aleo/transfer_private_to_public): +/// - input[0]: record_with_dynamic_id (id, tag, dynamic_id) +/// - output[0]: record_with_dynamic_id (id, checksum, value, sender_ciphertext, dynamic_id) +/// +/// Transition 1 (credits_b.aleo/transfer_public_to_private): +/// - output[0]: record_with_dynamic_id (id, checksum, value, sender_ciphertext, dynamic_id) +/// +/// Transition 2 (amm.aleo/buy_token_b): +/// - input[5]: record_dynamic (id only) +/// - output[0]: record_dynamic (id only) +/// - output[1]: record_dynamic (id only) +export const FIXTURE_UNIVERSAL_SWAP = `{"type":"execute","id":"at1d0jkklkk0dhrshwz0rs79gphw5xsuy4579pd97ejwydgxwzmssrqxeq9m4","execution":{"transitions":[{"id":"au1c90vkw978znh2f9fpnlrmjfj73e3s0tn7axsnfv6czc9klg87sgspr32ke","program":"credits_a.aleo","function":"transfer_private_to_public","inputs":[{"type":"record_with_dynamic_id","id":"2935758188788477729353123157581983277787724062190170356481470689101270975643field","tag":"6679686961620526023074355601161195765841129191035825052958600076637530347070field","dynamic_id":"7294922441359738964337302738841846091267814563241130271713313501366091382408field"},{"type":"public","id":"4694474143722056572473406555750385688211827958586030837628797979507672415049field","value":"aleo1rrj2mgall8mw57lcpkkvkxwqkawpc5rjarqm57w8gux2ahnt9sxqf0md56"},{"type":"public","id":"5898390175057567429884446337792539582762144171864480292053508932332745301550field","value":"100u64"}],"outputs":[{"type":"record_with_dynamic_id","id":"6988272367174527946664029405434344916422148226966342752639621896828889646587field","checksum":"5544396399665015092202125637395417271615889144958742653728461593976333210286field","value":"record1qvqsqpepr2xtdkc0u8lx9sm3nxgfxvy9z6ukjurvtgq2lx249wg99fcyqyxx66trwfhkxun9v35hguerqqpqzqpegduwk5zfvnf7dhjegtlyndsqgsvy0yc3eyg8zy6775gwwayxqdsz0wldsytvtpcsm4z55zgrskqefjfugmrdmxhtd6fppqszrvtpyww56dl","sender_ciphertext":"546862072540618809863542417529627396549255211751571510765342252087989631851field","dynamic_id":"3772686669997336510179951910708062255443775805303900154917019609926053464431field"},{"type":"future","id":"6662734312070899902126830054320418260609411066299828838387330740536605427425field","value":"{\\n program_id: credits_a.aleo,\\n function_name: transfer_private_to_public,\\n arguments: [\\n aleo1rrj2mgall8mw57lcpkkvkxwqkawpc5rjarqm57w8gux2ahnt9sxqf0md56,\\n 100u64\\n ]\\n}"}],"tpk":"1857458205135391805690433506248188343025837512920419398554414600962493113571group","tcm":"7212885671972326865503303197489529760371273036278217762871292341456241830847field","scm":"6685308348266694494639422926825196983455371881394719384083433115214943841707field"},{"id":"au1n9x332h7nxvzp8nvqclnp6auuc2uff6m7x68unfk98pjhq829ygqfkmh5d","program":"credits_b.aleo","function":"transfer_public_to_private","inputs":[{"type":"private","id":"7795873849997417078054290678159316525874307108923146171007373315837885105969field","value":"ciphertext1qgq0dq8cpq2xhmhq34eh97m7wsnsvgp0nxrc8utq8pr4mdtgwfhgqyql4wz4tr44a5rw3jqgrxl80l2dmmgpry5gcv8jkfwvu72vqx7uzy2xqnue"},{"type":"public","id":"8340204136406711491619630990594377225939075605931649942042458759820805466085field","value":"100u64"}],"outputs":[{"type":"record_with_dynamic_id","id":"332311061031037169318887798383324478156902411914797971038327317212214563408field","checksum":"7524261719331322807590946095959538859078206092698883895027534544571546590121field","value":"record1qvqspanf0v0fa9kfdt4f7f62t7z29ch5f772wsflchkrmrc3prf6sncwqyxx66trwfhkxun9v35hguerqqpqzqq85nmvj7y5a4k49k3g7mu70zrs6e5xzkx5dpx5tpy9yv3yq0xfz830kxzl26yemkz6jmq824sadlaqpnyjchyh3cl9mgn4xgxs5yjqqwn02wg","sender_ciphertext":"7339748269029871925069110878702172902510882488116422745908046263231736705795field","dynamic_id":"3909585016788031141822622927788756993219664743550919865376377617699137695340field"},{"type":"future","id":"6470629056524302267683332505118944187672285468012455842344098968764871467273field","value":"{\\n program_id: credits_b.aleo,\\n function_name: transfer_public_to_private,\\n arguments: [\\n aleo1rrj2mgall8mw57lcpkkvkxwqkawpc5rjarqm57w8gux2ahnt9sxqf0md56,\\n 100u64\\n ]\\n}"}],"tpk":"145051787864899542169523397371352730204179154803382477278915866715111408971group","tcm":"3657785441633375238523375070031091661744723043724470604738473420430814755678field","scm":"6685308348266694494639422926825196983455371881394719384083433115214943841707field"},{"id":"au1s0np8l8g24pj6dvrl9x9ae9y50ye2gktkuj4vmh2aa5ak4z73qfqygvplw","program":"amm.aleo","function":"buy_token_b","inputs":[{"type":"public","id":"4496290540720001002573299527109768498176064091681110497838718054424909325701field","value":"1796212144201756144227field"},{"type":"public","id":"3692528659073985547691700097252818374361223555770034864607895511638770138114field","value":"1814658888275465695843field"},{"type":"public","id":"6779304990746928014412101752189026432064048579258494218372934041004981156742field","value":"1868917857field"},{"type":"public","id":"6891565631548126702232358682074502678932372477582008058923214391180033690362field","value":"159748619646624572882733203183532374243803035081386454010655348field"},{"type":"public","id":"4754541307318780947640812670871042261288314173336383571153657531443413036827field","value":"163031276046149327277138208237194600527678254627957973064970868field"},{"type":"record_dynamic","id":"2302762380181227129449374330430974483000459226362731866657987538513152750011field"},{"type":"public","id":"32051433659444326950256364222812450477744740034339117005228829582061540488field","value":"100u64"},{"type":"public","id":"6027250465250854506200892970258780366572023644588168005342166523209094389862field","value":"100u64"}],"outputs":[{"type":"record_dynamic","id":"1549761178425747586833165341991534951225925971073181611580489046252661141359field"},{"type":"record_dynamic","id":"5673758827897291135811295325110122561827170725762334965495394304062600677656field"},{"type":"future","id":"5454263319386136161829885376771200469379810005027764041164782613159886871407field","value":"{\\n program_id: amm.aleo,\\n function_name: buy_token_b,\\n arguments: [\\n 100u64,\\n 100u64,\\n { _program_id: credits_a.aleo, _function_name: transfer_private_to_public, _checksum: 1922390625838771060408108899893116837761319781814346099177868397726841812507field },\\n { _program_id: credits_b.aleo, _function_name: transfer_public_to_private, _checksum: 1922390625838771060408108899893116837761319781814346099177868397726841812507field }\\n ]\\n}"}],"tpk":"2601760624064971805260762624936264183278163106227588217147974260418866159999group","tcm":"2552151665385759558426318820683358010078380725261435734976638972261454388148field","scm":"6685308348266694494639422926825196983455371881394719384083433115214943841707field"}],"global_state_root":"sr12jksk7fftxcjsju9e7duuvcfl2kh294cm20y5l22djxyckuwucrqa7r4r0","proof":"proof1qyrqqqqqqqqqqqqpqqqqqqqqqqqqyqqqqqqqqqqqqyqqqqqqqqqqqqgqqqqqqqqqqqqsqqqqqqqqqqqpqqqqqqqqqqqzlzmrkyvjdztdrtvf9a5t2u2yxdv272hyn4kqtvxm2yhv64w633gf098nw6nyrega964fnlnq7egptqf33j9cdjz2ah3w9uek2l8s72fr5p39l4atz0fgph47plrju3cwq8wzjzd58mklnd63uf6dycvsrwwqts58u4qm3gjwnqf3t6mhdh495uvkpf89fs25f5taxelajmce0rlmt539tt499gfa8djg6wf3s90hz8pk8pyp04jz2mfj5a7qula6n450yk50rhhl2ffr586cn70px4prs5n67hvcmzz9nrgtx46j2q2jt528rzmtux7a7k530gg43uwtrt4vx8eakdawr75gymjyz8amqzxg4a62zfxjc3szr9tj8337ugq8244vrg6rjr8m5te9qnd2ye9sp72f2zeh4hjuch88vx5z97w0rncpwlm28njj4zmvmdplsq8cyqqpfavdh4drcrle7r8g9gdy2ygmww7nlpjcazhku9rq8uhx40wlkc7d2r2aru9x7mr8tw6hftdsrdxqqqdlj0ez3nywkqxvh4hqwnx40mpua5vlc6lyz33r2fajtz8rzdmlyae5gaagkhfdn9833vegmzazlgqdmhy6zcz9z3guqg8hjrg7qmq75lflxnm06plnp488vr9j6asacdh37qzx4gygty4xrxza0k7samyqxqfng5h8z2u07kjg7azspugykzedmzgavky7smn28znt0qgh8nm4m4sa67ce50vgkcu943ag7whcqe9hp6q7j0gssqm0py6q8hqg76fsr5l9fk62s9udglgqxywlcgnxy55vu58umdj3q2t57n8rldmeqp8ev7p5kadaf8ncqsddmdd92qdsrfs08zjmlkyfk4s4ga3he974nyk4gxr8cfjrfq2lu84mpevy7qrlt7tf4hjv6qle85fecmyh4ugw344czngdggx2zgaerm9xexpwj5e4s2ry8mphw64ss08k3n47tgq9tehys5al9kzxdcmvx42uv80hqa489t0d0s0uku6adz64tvu2h9taxqce2tzkfrt5ga4h5a2e5dyp7zya0qg2mp8a7kxmmhektcmvyejspg53dw8h9y3wrdlaxe6yzhj80js4g0qlrc3074glah8cwz0spfv96xwv06jlewqsvxeumgvmatanp7qkfaqpgt22l2phmcpmnuna57d4fnfe75f0rgl2vsgegv32sxyp0edpyjy2lzm92uqu33fl94ul4s5er3rf0xnm0wyn3rcemutpvgj6q4hv06nf5cgc7gnzz4pj2qtn78qs824xw93uddgarv4klddtd0umdasgz24az3vxz7jhs67qtdmjl9fa2l2d3vjmzpu4f578s5qe8d6j6kmd204asdww3szvcwdegvl9ahnwuq0fg8evth7tvvss0mzkkkd2jfc3yqh629w3wwvf5ggqc8g5000a8kkn6tvvj09skx54wtyvpwgt3ljgpht6rsh3m4wt23j85yxtzvf7vvmt6226a95ehqaqraay0k8wzqhu7wgnf5tx5v9lypjfnz0kqfcdvrxlnq0px2tyngy3gjs46czdwy0nmjv3wwannndpsysz6svjtt00ya29q803969rfqn2sc627ah2u594sjssajzc697du5mvp60rah3wj8454np460cszqru0m4g942k4pxdgtaj958cru8mu9sel662c8a3wwq6dajd0dexmp8tpus6f0cek62e944xd7md5cqtq0afxs2dnyzfgly46j7gj3zrmezzvnwckgqkr7ux2xhqgjgvxeg4ak2u3g74w2a3u3j02ksjcuyp4nmpzfrjguhfan320w37uq0zlxsuyc99mfkzc9esnwejlfx9nhtrhy6ck9xrgdlrpyde40e0u3ecrkh2xp9uq7myxr3795j4a3kq7lf0lxz374m9suyzcdvz635yc29acph8kf9n4v4yvdpsjtx3u7asqzd25f9n9czdc7qy2cdapvgxkhw8qyt6zlc37qkcr8ftgnnk6e6jjvktlttxqjjjlr9ze0gex8z5yqdeyrfj2j6yx874ttvs6yruahj5uhcykq4qfcn745dajccxj92ppavthuwz96xyhjdykrklgxxnyqq3sgjuf6hwrts8sxzs34c8t24rl0lwdlpupf7u9sgrwthek3qtu3v2smcd9z7efrrtatpct9pe83cqtuev9netu8azu63jeam3r6s7xjxfvnw5grgnjpx3qawl95ved3wv2r84pgxcmw72qa6svugyjp9gr2k3xm455wpmkckzt3hraqw8txyzgqa8qw62ergul7uhqa39yvcpcynun3aelyu8af7cjaw2u4lc88my9cdgzdazqwxqv3nf6dzc9s8xmvvg5v3fnpexnu7u3jxded6cjm2znu8fajwudzktar8qmc6qkqe2vhkte6p7mejs0az78hmrghwxg4wdmzlwqdxcsy7wlz66jdm0zgfdhaxc9uwqvw7fe4h05np5vl5fzatucfyluuhukx9sydpsx6tqqgwez7rddta4n6svrev0t8ys7s4nq7c0e7t8vl28elm6hld0rfst4rwq8ptcpl7h9fctnf3203h453umju9ejac77kr7cerhgwukw58eef4egjszys8zehuw4pppj94wsxa7cayy2ahqrgsr0ft9j3m52rgc287863hj2r530sl4stjnd4k8e64p5ljkkf959vsd85u8akx2qe8jhaef088e54mv3ed2xvueheqe2tuccm3lfrqrzhlsqxlmh9zsq7vgmddjf7mx2jghu98jlf7w7csnzpszmep86k5ldg9lj30s8fsjagmcmgjtzada0l0qrmnsd2wuxx2hrsp2yzz3mard8fq6gftzty84arp8xyrvsc5xhnqpnlw0aq7kwtqv4c0yuytg6waahvta9h5rqqqv67wp3muq235tx6re6p0rxh79ujfm4jezvtt8rld7yftpjejcz9uy8klgew9w84xdmpu33ksjlsgt4e8kh8xzqp489mlxjcw3p09q5xvr5g6frtpc9jcgrrtnsckfem2rcrqfkutmqmjsdnenwsc7nyqfs4an0jw998pq6s5p0l657zllwgru59kg0nm2jduqhmukgv7k7uxg3pwnp6hsgs9vwcv6vdp7q29sycxgpf9zwsklejx0ee3w9f00yz8v4p9uap0a4k05l74tezwsuuwgqwtryvr4kctjqf6nhg4yd73ypvvgp7qkrfnxckuyaqjwhavxdz0ufe5dgvlgpvgcy77a27tdklfqf74vu7mdw87pk40jftqvrf404g2u42xff5385wngy0w0vsty6mqrr0qjut6wkavsg0tpz30k0802k9s3dlsxsxvep4ddwvxkaqtqyv9nnat6ruksgalgljqc75jqfgwejddv8ujutd76605w6mnujsd8yrth9cl37tsupsx5tv6qcep52qprwrmqlju6jf5qh4lmmpgdmytmpcxz7ez0uylp9347jvkuhe4dnjxps56p4chuzrm3f4lupp5hezrst0tepw64kttxvs3d3qy0hq6v0a3wraul3f78kpxyt5njmwth8hq3meuw8ftlq7j5znlzzrrzp25sysdqeyzjfsy9g4jx728kj57kqyq7q9dnmj9ask8mjkj08tdcv786pp39dmlmzr34tjelz3l63tsjuz9udtwqkwpw6fneay2pjsz8d7t54aua4q7fveknxmtue90vfs6kp7aj6xhyjptrhwx05tc3cuzqtkatm2mgcucqp6taj66vlpakjrfqs6hhgppx02a04q2taawnvdjjg24hwm4cs6kr2slntk2hpd6exxqggj9x9jnt7zhu0u40p37pjreaz54wzgjzkfphmmh58amtgs8xs58nakgk4j2d6jr64nydxej883syf9a28ywq0h0ntzyx8mt5q6v8vzwts6fw80w2am54u39d0dwuagffcpkte4kusghx4qqj8zz2n2q5pydhngqy6t5qf3jc8337lnzuts0gczjg5xttxnkxlsrx7vxeadvq86ngmpl74gttuw0nheydsxcmk36frh3jmn7dx22xc2yxcn2l4cqd3zxv345pavfedhmemugejvpnhecdd30st7kzsxc0kcnuy3ljsqr7624q6x7a7zw8llkt56kxwes2rcez9j0vftd8rlqcf0jrsqu2kq4qhnchr2hzwlsfsjrmamspa82d7sgf80jqq4k0v5fp8ntwprf6q8h925a7xpkdjylsx2gwwt7cfvzjd4ukdkxtxk2e0n387hzp968s8zjxuc57xvr6cgrlwjau7wqew6n72pq0ykh9hmjlcstj7u4hfhgzqemwkq9nv6mxgqdp3gv4ucy6qrcykhsasld546wxr3yrsejpqvyl655c3u0syqjnnlnemgjjwn593msvlzf4xapztak2prn8vjx9cqw3gtv02klr7qt0hrzyzegkgwa82fv0tl8xputya3ddwp3mwvvnqvpwqq8x4zxy2t53w8gl4vevedg0cywd2pj54eud4ppmtpqcfr3qkfnfpejpuwn5alxa2jnppx92cv723tsv4d7t4765l8qtpzhtrccfgfzj335zvejlpmzuh9ze0jty0ph63v5x36zng76rexh76ucce5ypyw7wmfhds42gsm4eejckhtgwtq0awxsa62mc8f487vga58vzkry5gu2gsh96uj28zvzlpylmcqvgqzgdapn5f2fxp842r4x9nu5ypmhvkzv658hpkxkv99hxpgla9df4eyh5ne0kw8zxjnx3hrrnn2zsuv9a3cnr8e0hquxsahg57xdal3jze4d0cqxhyapsf6kcwhx72dgyppv5dcr72gygt5tj7fyel2d5pjuuwxxl4w5mcl0356fuskw4vuzz2axmfuvu8q5zjpctzdj3x5rygu6z9sj54jna4u72qq68fpu4qqpkss03gjthtpyfew5vemdes6vfram6waf5rm9g4p9v0ezkwfl3zx5vsynqwmzk2q2t7w00gfq5gnfmvyvxl7ek7max9nlzmnl9le0q02x0x44uq2ypzlyuzez6hamvv8yxj7ug700dgwhveeqtshhwc8qxgs45pnxewd9tk7n7pkv660vr22een3ruxyh3qqpeku2wl0hf6yywux6d3msh8edvaqxe2hmf8nsx6ysjyz2l8phevf8xguzwteg5gzqrqqqqqqqqqqqxyt0tugr2q39eyk5srd4nq20pkz96hhcfpq68gtx2k4lewm5cqg40efdx7tg553eu75heewe5uhsqqqtmr4n3gtjpqyywkef79p37swzeejk4ch2at67akslzmf2x8hu033xd349aa8ypy6dngqn0lrezuqqpctfr0dmlw5f6udwg60csecusrlus5gkay0enrf3yd5077mu5uuya03tx5t8a9u76ch5hyn8e5kyzjm52ldr2d0jjcu6k6ntmc6ky3l9gudrrgs730apeuhqm4vmvhmuqqqk92yma"},"fee":{"transition":{"id":"au1665f5rqu67ke0t4l7y07x3l8q64cjs7my7uggqg98t4zzz82tv8sm92854","program":"credits.aleo","function":"fee_public","inputs":[{"type":"public","id":"8400172332048743692691087087017482296233740778517404245255459534697166324262field","value":"8709u64"},{"type":"public","id":"4043989596083935561156098870546894253407017982845985344637416627777617820912field","value":"0u64"},{"type":"public","id":"3476730643010212110757539276441018537636409608938425089745559217011541033306field","value":"915451993948189046831275925409281175613817688524699229864907732819158243103field"}],"outputs":[{"type":"future","id":"2305800434785298260597113464267023210240446067281788799565715256771402455273field","value":"{\\n program_id: credits.aleo,\\n function_name: fee_public,\\n arguments: [\\n aleo1a8llclfmn0lnh39ajzu56j7wqawk9qyl7u3pgt5f64msmdeyg5fq09q8t9,\\n 8709u64\\n ]\\n}"}],"tpk":"880344706687863670375267452459828966256043058296329615673672464307996568312group","tcm":"2079123413379319628908171237826421387816516352305052421287018385943555342332field","scm":"4095266928797962654377652841583901914742135997150552053964598152128338727834field"},"global_state_root":"sr12jksk7fftxcjsju9e7duuvcfl2kh294cm20y5l22djxyckuwucrqa7r4r0","proof":"proof1qyqsqqqqqqqqqqqpqqqqqqqqqqqfcr7hvvay4u7pya7d2vhn95kpc9c2sd79yjzupugxen54hh7r8f0rw37xuejc6snj7579aty34zvpq87ds90et6tesjd2l3zrzvk2lykcmk74frr8s9ps8pq80cuyj6rkan72yj28992khvmvfckt9ms6zq8s5z97zdrhggum95v4fpz0zpsvch2mdryetk3grrc4kw4k6ynngkajnrk8nh63cwe9esmxy45n07q94j0wzhnzz3d6ae3wtjweua9nclh58zx9rcqz08ntvzg9jwkvp93x7df9228qfqrmxup75yyse2qqv6zgmrq8nulu4r4yle8xr5j93dst0s0hazqcxta80ee6vm7gm9qusvstgpv6cdtj3sdesjd7dszsz3z53m97essslg8pzcf5v74vungp4adfhqn2gjqx40sq6q5fhayajhys86e92nd40vudkcmqn4wlspu69rvvxcw4zgsjup4m2axaajdccwxvucnf9fjhd4q9lrsky264v8rhns4ywmhe82lyzg3y2hy59qpz6dz3y0406vw5m72e3pnz2ej8utrddu6gnhxjlzlmkwa7qh0rx3604hrqud34ljvzv29q0l3hu5qy3hsu0mg0sqe9hz73z2khnnuntmf6trxvvskanm4sxylfqresquujlqlnff9e7xan8reku86rhxgp9fxj372frju5hhrugtxcnhttdcuhuj6gt8jmlh57vun00jsz3vgsncku0mk52sf4yj99ul0fmdwactx4ne0yjuh2rxsd0pymup2nxzyswgwvdwe2gttv4ed3gtfyfd9dneyck7xl4gpfjcepf5vsg63jpdt6th6502z7nxprhm6n6agc8fx9xahfn0rtt44ve7qm2c0lqzf3zt5w2lhlc6ne0wm7d6rg55rw2scc8dyhsa0x6c8w42na8nqt6asgq06p3hhk03yz8af3evdmpej42crxh9djvnnmrw3d7ltvr8xc9uqtmz46a2463c5pee60yqfwjklj3d8ht69zqk6h4amd7p2m40v4wpklgfynxdeyp3rpej7282frjejk78d8k5e2sqruavleugpsc6pyqwygmm7vtfdrffjpetaquranva55sayhf5z6aps7cey382acg76pzjlat7gahte0ldnwmynzyg5wq5css6lh25xpwzuxtnz8clrmrmgrqvqqqqqqqqqqqw7gf6rclq5tv5a5adzz8cjynzt9wtt3qzf38r64qm6e28c582anqlzp8mf7vlpd6sar5j90vfqsqqqrlj6fahqswuzs7gtcwpnz2lgmhjztj4dxjfuhx2d9v0jmxaz52hae6va3qlrdphgpunauzn774qvqqxqhppghuul4v0yfpjh8cl6r4c3tpy9r5c9kt3vq3mwvha052lx3p82dnn25dsz4npfdhhc3czfcks33a9f5y2vefaxf3mjn7m89hpqkg57jhpsthgef02s583sk2dj3qqqqv23mh6"}}`; + +/// Fixture 4: external_record_with_dynamic_id +/// Covers: external_record_with_dynamic_id (input), record_dynamic (input) +/// +/// Transition 0 (flow_external.aleo/get_external_liters): +/// - input[0]: external_record_with_dynamic_id (id, dynamic_id) +/// - output[0]: public +/// +/// Transition 1 (gas_manager_external.aleo/call_external_liters): +/// - input[0]: record_dynamic (id only) +/// - output[0]: public +export const FIXTURE_EXTERNAL_RECORD_DYNAMIC = `{"type":"execute","id":"at1w47c2x8nqu60vlvkstr33dnss5cjcu6m67u3js62c0dhtf889sps5j4e7u","execution":{"transitions":[{"id":"au1m78d79rmnnmr4hnptj4284v50pj06e3nwqpk73yazcnjqgxxxqgs03mjge","program":"flow_external.aleo","function":"get_external_liters","inputs":[{"type":"external_record_with_dynamic_id","id":"3249885246159029030048272339106378916725742667129673862945960835379009943879field","dynamic_id":"7995916969163020622176837059890541448212972689120865094112034425861344326131field"}],"outputs":[{"type":"public","id":"4155230599942985850286377007146804531897075026784481423568372834658603285946field","value":"100u64"}],"tpk":"3894616011976327982357301664640972187560595028605714937080375970001223851546group","tcm":"2193687817658210704193560852641858900312944181357922907819708648837756127053field","scm":"4770864491977636023250964821557136659916626722028053364825452955964158018989field"},{"id":"au142q2kfzjk30agtx63a3ehygtkuuuw7y88axhagc2sqw0upte2ygq4625v5","program":"gas_manager_external.aleo","function":"call_external_liters","inputs":[{"type":"record_dynamic","id":"2009859521189968220366667835222907858121400305268090962151526639014134735981field"}],"outputs":[{"type":"public","id":"1805366768954791969789072732140845371589086745575176725065220235610509112770field","value":"100u64"}],"tpk":"5646292805522371412122272976507727207823248370245122952541111710709014436301group","tcm":"4385257021040218620821237114727445339032748340983170043726204078294399158724field","scm":"4770864491977636023250964821557136659916626722028053364825452955964158018989field"}],"global_state_root":"sr1lvz9l8m0acqadrwc0nfkw9fxxhcnm443jfjc0pldxzkfzze9wv8qlc23tk","proof":"proof1qypsqqqqqqqqqqqpqqqqqqqqqqqqzqqqqqqqqqqqqyqqqqqqqqqqqyemwt2vt5zpe9mve4gut2yh0ctrywz6zh6cccdetca0lpxr576ca4yyy3plngnk8lq7t8hqdd6cqreu2uuxlrkprpwk4pp29r3tsrka9tkn9z2xzzzy37g8tzdqhkxhhmknrynenq5wu7crt66v2k4apqz4jd00emj6mw6r9kxwqpdyxu7q0faf8nplnqxw0sxqxtjy6xumf4ydmhmrjm547ehp397nu76xdjqsz3zc8eany3850gdeynw4z890up583xu4vrrp3kgg0smg7rv62hsmde0y0rhddvpg3c0dp0hyn8s9s9fqps9yme3f7jccj8t0su63sat9vx4qw9t5ym07r99mylhvxkgcp4f9r8zqsjm7spnf2dptmauw9qzgtav0dvcqugfh9fqm2p09qknvw4tm5wdkpl4nkfue4k9zkmutl89v26h2nj35gfzzeze2wyl07kqr0ksjhwms270qqn4et47c4pfxed8qnz3vkgaqama0w5ndj60trus9yqktwgjvfwenuh29r4p4w2upftq9g9hn6c27qe796pfcsxsltdc5asyr7sf9av0657wn03glcqujl68z7u29g9lhk29m3mk3m4tspa0jdfp559jwtd2k6nyqclzlke4dnemfawn8e5qe047hzxhfugk0hsaefq6uskmff2qxce5aywauqrl6klpn7lh6vp8cz9jp5dde4wx03a5pc3xklqjhxjyxe2h0293k6s6xwmmxl5sz3lzjrjcffh3esqt6jqldmh9zsmwhaf6zlqzpet6nz8dyvd4d4n6uc3wvqpujztwghrljjucqglwzarthqdwf760j42qlwnxfs0du9a29h4vrkwrp5fxq7grf7unsmvwr7qe8ygqw5u74xah7xd0w63n37azsphk7jayqqusqdvm5ljql79mcnasqhy8gxmr3jhkhtw0f8sj84k2vvme257fp9yglhrsgj46fem33lftjppxl7umcqeyl56qem4jwq2nwqwya85mytszc3n2k75uu2v6gev5wrqr3kf0zkpsepmsh6qzlhhg6w3a6gd9cqqn988y59zsvwnd9w8kvu9jrysx6h23yag0a0m62l9azfrrc5taapklyunp7qawqxw7rc44z6qtpaqg8tah0er4p2d9mrc2kyz28caf964kquw46qj52jyx88fw09sdzxdftvwqd09u52twvpur3uaznzsqmuqdmanmdfspghdzu9tq64q2hknakt4rkz6s5ex0x8z2rsmlqgk289zpz4ng2qcp6dd5kz9ap7fyqrwt59rdqvxhr2ju5u5rjxnvwfuh283xgacudpftk88sndyalmszdsfc9n77juzje8esv5rtmavc03nk3zxhe3f5kwn040m8gvlv9cyj5wfc62c8n6rjqxyfwhdsah0wyzxymkkr34uj9uethfkunwd6czy0a5y7enwv7c2lepzmxcxfv83a7dlgvv2q04r7g8yxgspf83sdqnrtf7ed05r4xf4qw0sgj0083x84jcsjv8lqcs0clqjq2uvqdagqpvsm9pzclg3q0hwne4hrtnpj6rd7hu3qmtf0p4tjet6k9n9zcngq2rs60lcgaf6eze73pve4fz9k93xv0x9tgxqan0wlz6ulzv3feur5jt02m40wphakhxqedyyuztznfdhr3h4xqzrgtkcdcsnu6a56mzrtdwaac9azfnlmxhzpvuj6ke3zl0tqyye8rgvxrf03htn4eq3lsh7l0qhgxc0zk5xgu0fx5wzluqx79uxq7w637edhpxv032dzpjesxd08x8eqdpk6xa6nvzp4584ev88m5f9z2vnzjk8javh5flhhv9yp97q02zt7xm2z82llsg3d6kfps380y4kpahzplnmjzrt20sx43jpy3cfctwcmyr47wxvtvfh9k9r4s0df8mjx3mpw98ywv0wafv0u3pxfms7set6jer5vdvuvqa6l62ememyzzfs3ytryx7k590z7h0wvqm97xv8gslzxf9f0vzs20uq7p550qzrhn35ar5vtujuu5keg7p8qdw3u942j0cnxpzdm3pp7vy8ahlvk99qpgw4ullujupdge3fv7wsx0ncg7g9aqaw5t8jnz4vgmv04sk4ynwvseaga6sl5kztm3sxz2qyvntcvlxsrsmrfe5hztrg22m39su7zqyvwsdn3h8pw60cv698x2zqxus3urhqssksp2hfgmvuqjh3xc06kqfrek8dj5g946txyarvks4ldy48wlhfkne4ple6zvn0arjxmfcp7pcq4frefyt60uf9dzhxs9tz97x5wdawppg5mny5ad0jrex82wncz6ak9qehz2cuasevfytyrrjuf28pvl6jvpu3884rytq340wv5zh6w62a5lp08cnl78dy99jrdp2yamyycvqn29nta5vg89z5jzdqdy2zd5u43qdf6ls4ldcmknq8m080tdzwu2nw6nya2t8ftlg352vu9nq9esn3g2c8mwxz0zxhpqtu5xet80jxqc7k86r0vjpdmwhrap8qppv6z996rl9d5jukrpfagf6t68mm2887yfe7e27l36w8wpq8car37642uwle638hp7yzj2pc8t43gwvuera6v4fuuxhm99flu7nt5uypkwp5cmdya7wvz4h9cv5q4rqltxftngmad6xw9axu9rkn8kzw45zzht85k590leunrze335zqpsqqqqqqqqqqxq6s3dtn9vpgztu8q6fyw0kvjx6gmashy4gynm8c5q5p8qcvp0mx9kh270cwzmunc25x6aycee2xqsqg5zuqy2slupw0qltjpxzttwcgmyuxch3jp7ym97lkj6mt5zf2xpjuqrylmsulealrjlk6tkkay2sqqcj8jtz65sru7erkmzdseevl3yh9wv8l9tzv76pnmptl77lnf5szqppw4ncm60d58lt3vrzqxarvngj92wmxm7mzjm2k0zhm5mm940uej706ya929any7drefxf5ytg7qqqlc6uad"},"fee":{"transition":{"id":"au1gtygsrxmf22em7j5mh6sg2rscum9dgweu99gy8gztcyhdlzqt5ystzqvkp","program":"credits.aleo","function":"fee_public","inputs":[{"type":"public","id":"565058561612187091590430756537718861231297140261119453182336708183252327185field","value":"2504u64"},{"type":"public","id":"3923509075713095666951581939340325490910369228885700745117577656137970067929field","value":"0u64"},{"type":"public","id":"2267947232810868303798989335631107759914673382653063896267038312763461377128field","value":"7208561054490115662302628656926376251823588653904856493314146423529501677198field"}],"outputs":[{"type":"future","id":"6205887229210898397144307159512239741303616429113015529381443775249088984862field","value":"{\\n program_id: credits.aleo,\\n function_name: fee_public,\\n arguments: [\\n aleo1cuw039pd2sgl68t4c9784l0lezdjaryzrh6uyffrj4ncxp66yugq5tdl9e,\\n 2504u64\\n ]\\n}"}],"tpk":"6568414782478685894354551223016110562622871789399363357187662757189925089965group","tcm":"5340188451709183846740354387794850346925814768759552938989405009057028613539field","scm":"7935408663466492418242164582143518657586669466689259426064984318642282370597field"},"global_state_root":"sr1lvz9l8m0acqadrwc0nfkw9fxxhcnm443jfjc0pldxzkfzze9wv8qlc23tk","proof":"proof1qyqsqqqqqqqqqqqpqqqqqqqqqqq2e7y2curs5gtlmvxd5wacn4yt2ehqy3tg3sm9aluvwy2leh3wmjus78hdc6qnnee24ewjqeftplvqq89rrzes8rzpcdayvq53jg60433vl55knxkjc59tscvdalr86w3g648ygkx5t65z4ur5wqfkl3pe8qtfq0ypkq572tjaa46zmdx88pr24zqskf8uvs5a38qdysmkzjdlhlyl7vx97plzanm9kmzdwgzmq2q8c0aunu46yswa4802dag0qvrdg6y6ru80whgl9ujzqm8xsv3keltp8y99aqxqmq6hs5re2lh2vuyp920842euvtlk8rr64y6rz88zdxwwx6gk0v2f6y9k3x7rewzrslpta7lzjm3wqq8ert7y74y8uvlcr0h28dglfk5f6sulr55zel7es3s2xp3xqwzu936w9qxnm3gvyeq0zk28hfvge2y30ys06lrnz3tesp4xlut45zwkxtjvkad243es3s8grksy838ya9mhne0a0uv2wa46qyhk8sujtwmah99nkh6qk4gy8qyy8nepw6yn5j9xlzxxd4m6mzh4pjardx5f076a5kjlkedxqmvg9vm577etm9xw8u7rncrgqtutkqqwl7f878xgwryagx7462u0mnszrr3tmfnd24l39l7stc62xeg0lfwqu3zgdp8zgmaa98c5hpl92zupl8l25460h3xngtz944kmnrrn40tawt5r7mxu6q9wxvn8p084j5rrdmmtfd6epfkqzmsrta7p52sw7s625l4c9pn8el6savr9tg0lyzaj2g3zmc4tgzcxgld8thn0lxfujc9nte80yzx08dygs72kflm4p0mlrkvgh5eal5gpqzu543l4967yptk3trszy0q9u4n3yyftm5u3q07znm6j7pdt5duzc3l6guynmvwfzdj6ey5zd3tphx22qf50cjs00tm649kvcughejj6ur069pzfl6jujmxfzlg6gz93utca674a8u9a6svy36h7ay4gftv4le5f7jq6mz2hqtqn2ge89sc9ygrh9538zr30cye7vetame6vqphszdj7yhsmnn7cgdlm5zgr2aux607v53ndqv7h5l5mtpnyaq2h5khzh69qtusz57ufrdf4fwyvtcu35e95ru9q4syj34tjp8gpxxgutxfrk2fz0cy9t299jrxsv6pd3gaar22n4zstqvqqqqqqqqqqphts6jh0pk0j3dx95dh9rj20ee6f8wc2r4ztrc6q0992pdlaw4k97khc2h6tnwft0a3pwpfpfmz3syqxrcrtkn3kjjlkzjnnr674d3t6ur2q7s04w5ywzftm8ndn22wdsfswevm52fqujvh4fu39rfjt6nyqqxf3hcxr9fs40q843e6hlqe6vdw50nl6qnt6x3yvnxch0fsec3tq30adsjf9afdm4gu5cszpttpqcqhrx9ytcw2auk9660aq397kxr8c3zg3qadsjqm020a45uegq6yyqyqq48ue9f"}}`; diff --git a/sdk/tests/dynamic-dispatch.test.ts b/sdk/tests/dynamic-dispatch.test.ts new file mode 100644 index 000000000..022231a67 --- /dev/null +++ b/sdk/tests/dynamic-dispatch.test.ts @@ -0,0 +1,302 @@ +import { expect } from "chai"; +import { + Transaction, + TransactionObject, + InputObject, + OutputObject, + ExecutionObject, + TransitionObject, +} from "@provablehq/sdk/%%NETWORK%%.js"; +import { + FIXTURE_DYNAMIC_TRANSFER_PUB_TO_PRIV, + FIXTURE_DYNAMIC_TRANSFER_PRIVATE, + FIXTURE_EXTERNAL_RECORD_DYNAMIC, + FIXTURE_UNIVERSAL_SWAP, +} from "./data/dynamic-dispatch.js"; + +describe("Dynamic Dispatch", () => { + describe("Fixture 1: dynamic_transfer_pub_to_priv", () => { + let transaction: Transaction; + let summary: TransactionObject; + + before(() => { + transaction = Transaction.fromString(FIXTURE_DYNAMIC_TRANSFER_PUB_TO_PRIV); + summary = transaction.summary(true) as TransactionObject; + }); + + it("should parse the transaction successfully", () => { + expect(transaction.id()).equal( + "at1jdfcgjg2rj557an4yp3jqd7l082lnaf8glwa9kms6udejm0rmuyscl23dj", + ); + expect(transaction.isExecute()).equal(true); + }); + + it("should have record_with_dynamic_id output in the inner transition", () => { + const execution = summary.execution as ExecutionObject; + const innerTransition = execution.transitions[0] as TransitionObject; + const outputs = innerTransition.outputs as OutputObject[]; + + expect(innerTransition.program).equal("credits.aleo"); + expect(innerTransition.function).equal("transfer_public_to_private"); + + // Output 0: record_with_dynamic_id + expect(outputs[0].type).equal("record_with_dynamic_id"); + expect(outputs[0].id).to.be.a("string"); + expect(outputs[0].checksum).to.be.a("string"); + expect(outputs[0].value).to.be.a("string"); + expect(outputs[0].dynamic_id).to.be.a("string"); + expect((outputs[0].dynamic_id as string)).to.match(/^\d+field$/); + + // Output 1: future (unchanged type) + expect(outputs[1].type).equal("future"); + }); + + it("should have record_dynamic output in the outer transition", () => { + const execution = summary.execution as ExecutionObject; + const outerTransition = execution.transitions[1] as TransitionObject; + const outputs = outerTransition.outputs as OutputObject[]; + + expect(outerTransition.program).equal("test_dcall.aleo"); + expect(outerTransition.function).equal("dynamic_transfer_pub_to_priv"); + + // Output 0: record_dynamic (opaque — id only) + expect(outputs[0].type).equal("record_dynamic"); + expect(outputs[0].id).to.be.a("string"); + expect((outputs[0].id as string)).to.match(/^\d+field$/); + expect(outputs[0].checksum).to.be.undefined; + expect(outputs[0].value).to.be.undefined; + expect(outputs[0].dynamic_id).to.be.undefined; + + // Output 1: future + expect(outputs[1].type).equal("future"); + }); + }); + + describe("Fixture 2: dynamic_transfer_private", () => { + let transaction: Transaction; + let summary: TransactionObject; + + before(() => { + transaction = Transaction.fromString(FIXTURE_DYNAMIC_TRANSFER_PRIVATE); + summary = transaction.summary(true) as TransactionObject; + }); + + it("should parse the transaction successfully", () => { + expect(transaction.id()).equal( + "at1v82rljem6grav07tum7sk7qtrk96sn24wa47s9y7hr9jsp8vzyzslrajv4", + ); + expect(transaction.isExecute()).equal(true); + }); + + it("should have record_with_dynamic_id input in the inner transition", () => { + const execution = summary.execution as ExecutionObject; + const innerTransition = execution.transitions[0] as TransitionObject; + const inputs = innerTransition.inputs as InputObject[]; + + expect(innerTransition.program).equal("credits.aleo"); + expect(innerTransition.function).equal("transfer_private"); + + // Input 0: record_with_dynamic_id + expect(inputs[0].type).equal("record_with_dynamic_id"); + expect(inputs[0].id).to.be.a("string"); + expect(inputs[0].tag).to.be.a("string"); + expect(inputs[0].dynamic_id).to.be.a("string"); + expect((inputs[0].dynamic_id as string)).to.match(/^\d+field$/); + }); + + it("should have record_with_dynamic_id outputs in the inner transition", () => { + const execution = summary.execution as ExecutionObject; + const innerTransition = execution.transitions[0] as TransitionObject; + const outputs = innerTransition.outputs as OutputObject[]; + + // Both outputs: record_with_dynamic_id + expect(outputs[0].type).equal("record_with_dynamic_id"); + expect(outputs[0].id).to.be.a("string"); + expect(outputs[0].checksum).to.be.a("string"); + expect(outputs[0].value).to.be.a("string"); + expect(outputs[0].dynamic_id).to.be.a("string"); + + expect(outputs[1].type).equal("record_with_dynamic_id"); + expect(outputs[1].dynamic_id).to.be.a("string"); + }); + + it("should have record_dynamic input in the outer transition", () => { + const execution = summary.execution as ExecutionObject; + const outerTransition = execution.transitions[1] as TransitionObject; + const inputs = outerTransition.inputs as InputObject[]; + + expect(outerTransition.program).equal("test_dcall.aleo"); + expect(outerTransition.function).equal("dynamic_transfer_private"); + + // Input 3: record_dynamic + expect(inputs[3].type).equal("record_dynamic"); + expect(inputs[3].id).to.be.a("string"); + expect(inputs[3].tag).to.be.undefined; + expect(inputs[3].dynamic_id).to.be.undefined; + }); + + it("should have record_dynamic outputs in the outer transition", () => { + const execution = summary.execution as ExecutionObject; + const outerTransition = execution.transitions[1] as TransitionObject; + const outputs = outerTransition.outputs as OutputObject[]; + + // Both outputs: record_dynamic (opaque) + expect(outputs[0].type).equal("record_dynamic"); + expect(outputs[0].id).to.be.a("string"); + expect(outputs[0].checksum).to.be.undefined; + expect(outputs[0].dynamic_id).to.be.undefined; + + expect(outputs[1].type).equal("record_dynamic"); + expect(outputs[1].id).to.be.a("string"); + }); + }); + + describe("Fixture 3: universal_swap (cross-program)", () => { + let transaction: Transaction; + let summary: TransactionObject; + + before(() => { + transaction = Transaction.fromString(FIXTURE_UNIVERSAL_SWAP); + summary = transaction.summary(true) as TransactionObject; + }); + + it("should parse the transaction successfully", () => { + expect(transaction.id()).equal( + "at1d0jkklkk0dhrshwz0rs79gphw5xsuy4579pd97ejwydgxwzmssrqxeq9m4", + ); + expect(transaction.isExecute()).equal(true); + }); + + it("should have record_with_dynamic_id input in credits_a transition", () => { + const execution = summary.execution as ExecutionObject; + const transition = execution.transitions[0] as TransitionObject; + const inputs = transition.inputs as InputObject[]; + + expect(transition.program).equal("credits_a.aleo"); + expect(transition.function).equal("transfer_private_to_public"); + + expect(inputs[0].type).equal("record_with_dynamic_id"); + expect(inputs[0].id).to.be.a("string"); + expect(inputs[0].tag).to.be.a("string"); + expect(inputs[0].dynamic_id).to.be.a("string"); + }); + + it("should have record_with_dynamic_id output in credits_a transition", () => { + const execution = summary.execution as ExecutionObject; + const transition = execution.transitions[0] as TransitionObject; + const outputs = transition.outputs as OutputObject[]; + + expect(outputs[0].type).equal("record_with_dynamic_id"); + expect(outputs[0].dynamic_id).to.be.a("string"); + }); + + it("should have record_with_dynamic_id output in credits_b transition", () => { + const execution = summary.execution as ExecutionObject; + const transition = execution.transitions[1] as TransitionObject; + const outputs = transition.outputs as OutputObject[]; + + expect(transition.program).equal("credits_b.aleo"); + expect(outputs[0].type).equal("record_with_dynamic_id"); + expect(outputs[0].dynamic_id).to.be.a("string"); + }); + + it("should have record_dynamic input in the AMM transition", () => { + const execution = summary.execution as ExecutionObject; + const ammTransition = execution.transitions[2] as TransitionObject; + const inputs = ammTransition.inputs as InputObject[]; + + expect(ammTransition.program).equal("amm.aleo"); + expect(ammTransition.function).equal("buy_token_b"); + + // Input 5: record_dynamic + expect(inputs[5].type).equal("record_dynamic"); + expect(inputs[5].id).to.be.a("string"); + expect(inputs[5].tag).to.be.undefined; + expect(inputs[5].dynamic_id).to.be.undefined; + }); + + it("should have record_dynamic outputs in the AMM transition", () => { + const execution = summary.execution as ExecutionObject; + const ammTransition = execution.transitions[2] as TransitionObject; + const outputs = ammTransition.outputs as OutputObject[]; + + expect(outputs[0].type).equal("record_dynamic"); + expect(outputs[0].id).to.be.a("string"); + expect(outputs[0].checksum).to.be.undefined; + + expect(outputs[1].type).equal("record_dynamic"); + expect(outputs[1].id).to.be.a("string"); + + // Output 2: future (dynamic future resolved to concrete) + expect(outputs[2].type).equal("future"); + }); + }); + + describe("Fixture 4: external_record_with_dynamic_id", () => { + let transaction: Transaction; + let summary: TransactionObject; + + before(() => { + transaction = Transaction.fromString(FIXTURE_EXTERNAL_RECORD_DYNAMIC); + summary = transaction.summary(true) as TransactionObject; + }); + + it("should parse the transaction successfully", () => { + expect(transaction.id()).equal( + "at1w47c2x8nqu60vlvkstr33dnss5cjcu6m67u3js62c0dhtf889sps5j4e7u", + ); + expect(transaction.isExecute()).equal(true); + }); + + it("should have external_record_with_dynamic_id input in the inner transition", () => { + const execution = summary.execution as ExecutionObject; + const innerTransition = execution.transitions[0] as TransitionObject; + const inputs = innerTransition.inputs as InputObject[]; + + expect(innerTransition.program).equal("flow_external.aleo"); + expect(innerTransition.function).equal("get_external_liters"); + + // Input 0: external_record_with_dynamic_id + expect(inputs[0].type).equal("external_record_with_dynamic_id"); + expect(inputs[0].id).to.be.a("string"); + expect((inputs[0].id as string)).to.match(/^\d+field$/); + expect(inputs[0].dynamic_id).to.be.a("string"); + expect((inputs[0].dynamic_id as string)).to.match(/^\d+field$/); + expect(inputs[0].tag).to.be.undefined; + }); + + it("should have public output in the inner transition", () => { + const execution = summary.execution as ExecutionObject; + const innerTransition = execution.transitions[0] as TransitionObject; + const outputs = innerTransition.outputs as OutputObject[]; + + expect(outputs[0].type).equal("public"); + expect(outputs[0].value).equal(BigInt(100)); + }); + + it("should have record_dynamic input in the outer transition", () => { + const execution = summary.execution as ExecutionObject; + const outerTransition = execution.transitions[1] as TransitionObject; + const inputs = outerTransition.inputs as InputObject[]; + + expect(outerTransition.program).equal("gas_manager_external.aleo"); + expect(outerTransition.function).equal("call_external_liters"); + + // Input 0: record_dynamic + expect(inputs[0].type).equal("record_dynamic"); + expect(inputs[0].id).to.be.a("string"); + expect((inputs[0].id as string)).to.match(/^\d+field$/); + expect(inputs[0].tag).to.be.undefined; + expect(inputs[0].dynamic_id).to.be.undefined; + }); + + it("should have public output in the outer transition", () => { + const execution = summary.execution as ExecutionObject; + const outerTransition = execution.transitions[1] as TransitionObject; + const outputs = outerTransition.outputs as OutputObject[]; + + expect(outputs[0].type).equal("public"); + expect(outputs[0].value).equal(BigInt(100)); + }); + }); +}); diff --git a/wasm/src/programs/data/helpers/future.rs b/wasm/src/programs/data/helpers/future.rs index d51fce0ea..391aeea30 100644 --- a/wasm/src/programs/data/helpers/future.rs +++ b/wasm/src/programs/data/helpers/future.rs @@ -40,6 +40,18 @@ pub fn future_to_js_value(argument: &FutureNative, convert_to_js: bool, id: &Fie } } ArgumentNative::Future(future) => future_to_js_value(future, convert_to_js, id), + ArgumentNative::DynamicFuture(dynamic_future) => { + if let Ok(future) = dynamic_future.to_future() { + future_to_js_value(&future, convert_to_js, id) + } else { + // arguments are absent — serialize the fixed-size fields directly + let obj = object! { + "type": "dynamic_future", + "checksum": if convert_to_js { JsValue::from(dynamic_future.checksum().to_string()) } else { JsValue::from(Field::from(dynamic_future.checksum())) }, + }; + JsValue::from(obj) + } + } }) .collect::<Array>(); let future_object = object! { diff --git a/wasm/src/programs/data/helpers/input.rs b/wasm/src/programs/data/helpers/input.rs index ba8244cd6..d846e657a 100644 --- a/wasm/src/programs/data/helpers/input.rs +++ b/wasm/src/programs/data/helpers/input.rs @@ -78,5 +78,66 @@ pub fn input_to_js_value(input: &InputNative, convert_to_js: bool) -> JsValue { }; JsValue::from(external_record) } + InputNative::DynamicRecord(input_commitment) => { + let dynamic_record = object! { + "type" : "record_dynamic", + "id" : if convert_to_js { JsValue::from(input_commitment.to_string()) } else { JsValue::from(Field::from(input_commitment)) }, + }; + JsValue::from(dynamic_record) + } + InputNative::RecordWithDynamicID(serial_number, tag, dynamic_id) => { + let record_with_dynamic_id = object! { + "type": "record_with_dynamic_id", + "id": if convert_to_js { JsValue::from(serial_number.to_string()) } else { JsValue::from(Field::from(serial_number)) }, + "tag": if convert_to_js { JsValue::from(Field::from(tag).to_string()) } else { JsValue::from(Field::from(tag)) }, + "dynamic_id": if convert_to_js { JsValue::from(Field::from(dynamic_id).to_string()) } else { JsValue::from(Field::from(dynamic_id)) }, + }; + JsValue::from(record_with_dynamic_id) + } + InputNative::ExternalRecordWithDynamicID(external_record_hash, dynamic_id) => { + let external_record_with_dynamic_id = object! { + "type": "external_record_with_dynamic_id", + "id": if convert_to_js { JsValue::from(external_record_hash.to_string()) } else { JsValue::from(Field::from(external_record_hash)) }, + "dynamic_id": if convert_to_js { JsValue::from(Field::from(dynamic_id).to_string()) } else { JsValue::from(Field::from(dynamic_id)) }, + }; + JsValue::from(external_record_with_dynamic_id) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::types::native::FieldNative; + use js_sys::Reflect; + use snarkvm_console::prelude::One; + use wasm_bindgen_test::*; + + fn get_type(js: &JsValue) -> String { + Reflect::get(js, &JsValue::from_str("type")).unwrap().as_string().unwrap() + } + + #[wasm_bindgen_test] + fn test_dynamic_record_input_type_matches_snarkvm() { + let field = FieldNative::one(); + let input = InputNative::DynamicRecord(field); + let js = input_to_js_value(&input, true); + assert_eq!(get_type(&js), "record_dynamic"); + } + + #[wasm_bindgen_test] + fn test_record_with_dynamic_id_input_type_matches_snarkvm() { + let field = FieldNative::one(); + let input = InputNative::RecordWithDynamicID(field, field, field); + let js = input_to_js_value(&input, true); + assert_eq!(get_type(&js), "record_with_dynamic_id"); + } + + #[wasm_bindgen_test] + fn test_external_record_with_dynamic_id_input_type_matches_snarkvm() { + let field = FieldNative::one(); + let input = InputNative::ExternalRecordWithDynamicID(field, field); + let js = input_to_js_value(&input, true); + assert_eq!(get_type(&js), "external_record_with_dynamic_id"); } } diff --git a/wasm/src/programs/data/helpers/output.rs b/wasm/src/programs/data/helpers/output.rs index f3eed296d..9b2783241 100644 --- a/wasm/src/programs/data/helpers/output.rs +++ b/wasm/src/programs/data/helpers/output.rs @@ -115,5 +115,92 @@ pub fn output_to_js_value(output: &OutputNative, convert_to_js: bool) -> JsValue }; JsValue::from(&value) } + OutputNative::DynamicRecord(output_commitment) => { + let dynamic_record_object = object! { + "type": "record_dynamic", + "id": if convert_to_js { JsValue::from(output_commitment.to_string()) } else { JsValue::from(Field::from(output_commitment)) }, + }; + JsValue::from(dynamic_record_object) + } + OutputNative::RecordWithDynamicID( + output_commitment, + checksum, + record_ciphertext, + sender_ciphertext, + dynamic_id, + ) => { + let value = if let Some(record_ciphertext) = record_ciphertext { + if convert_to_js { + JsValue::from(record_ciphertext.to_string()) + } else { + JsValue::from(RecordCiphertext::from(record_ciphertext)) + } + } else { + JsValue::UNDEFINED + }; + let sender_ciphertext = if let Some(sender_ciphertext) = sender_ciphertext { + if convert_to_js { + JsValue::from(sender_ciphertext.to_string()) + } else { + JsValue::from(Field::from(sender_ciphertext)) + } + } else { + JsValue::UNDEFINED + }; + let record_with_dynamic_id_object = object! { + "type": "record_with_dynamic_id", + "id": if convert_to_js { JsValue::from(output_commitment.to_string()) } else { JsValue::from(Field::from(output_commitment)) }, + "checksum": if convert_to_js { JsValue::from(checksum.to_string()) } else { JsValue::from(Field::from(checksum)) }, + "value": value, + "sender_ciphertext": sender_ciphertext, + "dynamic_id": if convert_to_js { JsValue::from(dynamic_id.to_string()) } else { JsValue::from(Field::from(dynamic_id)) }, + }; + JsValue::from(record_with_dynamic_id_object) + } + OutputNative::ExternalRecordWithDynamicID(external_record_hash, dynamic_id) => { + let external_record_with_dynamic_id = object! { + "type": "external_record_with_dynamic_id", + "id": if convert_to_js { JsValue::from(external_record_hash.to_string()) } else { JsValue::from(Field::from(external_record_hash)) }, + "dynamic_id": if convert_to_js { JsValue::from(Field::from(dynamic_id).to_string()) } else { JsValue::from(Field::from(dynamic_id)) }, + }; + JsValue::from(external_record_with_dynamic_id) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::types::native::FieldNative; + use js_sys::Reflect; + use snarkvm_console::prelude::One; + use wasm_bindgen_test::*; + + fn get_type(js: &JsValue) -> String { + Reflect::get(js, &JsValue::from_str("type")).unwrap().as_string().unwrap() + } + + #[wasm_bindgen_test] + fn test_dynamic_record_output_type_matches_snarkvm() { + let field = FieldNative::one(); + let output = OutputNative::DynamicRecord(field); + let js = output_to_js_value(&output, true); + assert_eq!(get_type(&js), "record_dynamic"); + } + + #[wasm_bindgen_test] + fn test_record_with_dynamic_id_output_type_matches_snarkvm() { + let field = FieldNative::one(); + let output = OutputNative::RecordWithDynamicID(field, field, None, None, field); + let js = output_to_js_value(&output, true); + assert_eq!(get_type(&js), "record_with_dynamic_id"); + } + + #[wasm_bindgen_test] + fn test_external_record_with_dynamic_id_output_type_matches_snarkvm() { + let field = FieldNative::one(); + let output = OutputNative::ExternalRecordWithDynamicID(field, field); + let js = output_to_js_value(&output, true); + assert_eq!(get_type(&js), "external_record_with_dynamic_id"); } } diff --git a/wasm/src/programs/manager/execute.rs b/wasm/src/programs/manager/execute.rs index af3fcc7e9..b11729b00 100644 --- a/wasm/src/programs/manager/execute.rs +++ b/wasm/src/programs/manager/execute.rs @@ -742,7 +742,7 @@ impl ProgramManager { let stack = process.get_stack(program.id()).map_err(|e| e.to_string())?; - cost_in_microcredits_v2(&stack, &function_id).map_err(|e| e.to_string()) + minimum_cost_in_microcredits_v2(&stack, &function_id).map_err(|e| e.to_string()) } } diff --git a/wasm/src/programs/manager/mod.rs b/wasm/src/programs/manager/mod.rs index 4313b0ec0..9b6900eea 100644 --- a/wasm/src/programs/manager/mod.rs +++ b/wasm/src/programs/manager/mod.rs @@ -47,7 +47,7 @@ use crate::{ }, }; use snarkvm_console::{network::Network, prelude::ToBytes}; -use snarkvm_synthesizer::process::{cost_in_microcredits_v2, deployment_cost}; +use snarkvm_synthesizer::process::{deployment_cost, minimum_cost_in_microcredits_v2}; use snarkvm_synthesizer_program::StackTrait; use std::panic::{AssertUnwindSafe, catch_unwind}; diff --git a/wasm/src/programs/program.rs b/wasm/src/programs/program.rs index 9f2a2bdd8..cb59d798c 100644 --- a/wasm/src/programs/program.rs +++ b/wasm/src/programs/program.rs @@ -182,6 +182,20 @@ impl Program { Reflect::set(&input, &"register".into(), &register).map_err(|_| "Failed to set property")?; function_inputs.set(index as u32, input.into()); } + ValueType::DynamicRecord => { + let input = Object::new(); + let value_type = JsValue::from_str("dynamic_record"); + Reflect::set(&input, &"type".into(), &value_type).map_err(|_| "Failed to set property")?; + Reflect::set(&input, &"register".into(), &register).map_err(|_| "Failed to set property")?; + function_inputs.set(index as u32, input.into()); + } + ValueType::DynamicFuture => { + let input = Object::new(); + let value_type = JsValue::from_str("dynamic_future"); + Reflect::set(&input, &"type".into(), &value_type).map_err(|_| "Failed to set property")?; + Reflect::set(&input, &"register".into(), &register).map_err(|_| "Failed to set property")?; + function_inputs.set(index as u32, input.into()); + } } } Ok(function_inputs) @@ -265,6 +279,7 @@ impl Program { } PlaintextType::ExternalStruct(struct_locator) => { let struct_name = struct_locator.name(); + if let Some(name) = name { Reflect::set(&input, &"name".into(), &name.into()).map_err(|_| "Failed to set property")?; } diff --git a/wasm/src/programs/request.rs b/wasm/src/programs/request.rs index b3b27d668..de6e64977 100644 --- a/wasm/src/programs/request.rs +++ b/wasm/src/programs/request.rs @@ -153,7 +153,9 @@ impl ExecutionRequest { /// @param {string[]} inputs The inputs to the function. /// @param {string[]} input_types The input types of the function. /// @param {Field | undefined} root_tvk The tvk of the function at the top of the call graph. This is undefined if this request is built for the top-level call or if there is only one function in the call graph. + /// @param {Field | undefined} program_checksum The checksum of the program. This is undefined if the call is not dynamic. /// @param {boolean} is_root Flag to indicate if this is the top level function in the call graph. + /// @param {boolean} is_dynamic Flag to indicate if this is a dynamic call. #[allow(clippy::too_many_arguments)] pub fn sign( private_key: PrivateKey, @@ -164,6 +166,7 @@ impl ExecutionRequest { root_tvk: Option<Field>, program_checksum: Option<Field>, is_root: bool, + is_dynamic: bool, ) -> Result<ExecutionRequest, String> { // Convert the ProgramID and function name to their native objects. let program_id = ProgramIDNative::from_str(&program_id).map_err(|e| e.to_string())?; @@ -198,6 +201,7 @@ impl ExecutionRequest { root_tvk, is_root, program_checksum, + is_dynamic, &mut rng, ) .map_err(|e| e.to_string())?; @@ -208,8 +212,9 @@ impl ExecutionRequest { /// Verify the input types within a request. /// - /// @param {string[]} The input_types within the request. - /// @param {boolean} Flag to indicate whether this request is the first function in the call graph. + /// @param {string[]} input_types The input types within the request. + /// @param {boolean} is_root Flag to indicate whether this request is the first function in the call graph. + /// @param {Field | undefined} program_checksum The checksum of the program. This is undefined if the call is not dynamic. pub fn verify(&self, input_types: Array, is_root: bool, program_checksum: Option<Field>) -> bool { let input_types = input_types .iter() From 367a7f2b7b0a31d7bbeedfa9b8f4184ad3db3f4f Mon Sep 17 00:00:00 2001 From: marshacb <cameron.marshall12@gmail.com> Date: Mon, 2 Mar 2026 18:50:04 -0500 Subject: [PATCH 03/17] fix: use dot-delimited type strings for dynamic.record and dynamic.future to match snarkVM (#1210) --- wasm/src/programs/program.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wasm/src/programs/program.rs b/wasm/src/programs/program.rs index cb59d798c..9d14240a1 100644 --- a/wasm/src/programs/program.rs +++ b/wasm/src/programs/program.rs @@ -184,14 +184,14 @@ impl Program { } ValueType::DynamicRecord => { let input = Object::new(); - let value_type = JsValue::from_str("dynamic_record"); + let value_type = JsValue::from_str("dynamic.record"); Reflect::set(&input, &"type".into(), &value_type).map_err(|_| "Failed to set property")?; Reflect::set(&input, &"register".into(), &register).map_err(|_| "Failed to set property")?; function_inputs.set(index as u32, input.into()); } ValueType::DynamicFuture => { let input = Object::new(); - let value_type = JsValue::from_str("dynamic_future"); + let value_type = JsValue::from_str("dynamic.future"); Reflect::set(&input, &"type".into(), &value_type).map_err(|_| "Failed to set property")?; Reflect::set(&input, &"register".into(), &register).map_err(|_| "Failed to set property")?; function_inputs.set(index as u32, input.into()); From 14946ce632d3f63b18a229c6e8304505ed873781 Mon Sep 17 00:00:00 2001 From: Mike Turner <mike@provable.com> Date: Tue, 10 Mar 2026 15:50:38 -0700 Subject: [PATCH 04/17] Handle Consensus V14 in deployments (#1230) * Handle Consensus V14 in deployments * Remove redundant .map_err in latest_stateroot call * Update getOrInitConsensusVersionTestHeights tests and doc comments to handle all currently active test version heights * Deallocate deployment cost tuple --- create-leo-app/template-devnode-js/index.js | 2 +- sdk/src/program-manager.ts | 4 +- sdk/tests/wasm.test.ts | 4 +- wasm/src/programs/manager/deploy.rs | 89 ++++++++++++++------- wasm/src/utilities/mod.rs | 4 +- 5 files changed, 66 insertions(+), 37 deletions(-) diff --git a/create-leo-app/template-devnode-js/index.js b/create-leo-app/template-devnode-js/index.js index 3312402e1..78220c043 100644 --- a/create-leo-app/template-devnode-js/index.js +++ b/create-leo-app/template-devnode-js/index.js @@ -32,7 +32,7 @@ constructor: async function main() { // Initialize multi-threading to allow WASM execution. await initThreadPool(); - const heights = getOrInitConsensusVersionTestHeights("0,1,2,3,4,5,6,7,8,9,10,11,12"); + const heights = getOrInitConsensusVersionTestHeights("0,1,2,3,4,5,6,7,8,9,10,11,12,13"); const privateKey = "APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH"; const account = new Account({privateKey}); diff --git a/sdk/src/program-manager.ts b/sdk/src/program-manager.ts index f10e4cbd3..9a13eb61d 100644 --- a/sdk/src/program-manager.ts +++ b/sdk/src/program-manager.ts @@ -3291,7 +3291,7 @@ class ProgramManager { * import { AleoKeyProvider, getOrInitConsensusVersionTestHeights, ProgramManager, NetworkRecordProvider } from "@provablehq/sdk/mainnet.js"; * * // Initialize the development consensus heights in order to work with devnode. - * getOrInitConsensusVersionTestHeights("0,1,2,3,4,5,6,7,8,9,10,11,12"); + * getOrInitConsensusVersionTestHeights("0,1,2,3,4,5,6,7,8,9,10,11,12,13"); * * // Create a new NetworkClient and RecordProvider. * const recordProvider = new NetworkRecordProvider(account, networkClient); @@ -3461,7 +3461,7 @@ class ProgramManager { * import { ProgramManager, NetworkRecordProvider, getOrInitConsensusVersionTestHeights } from "@provablehq/sdk/mainnet.js"; * * // Initialize the development consensus heights in order to work with a local devnode. - * getOrInitConsensusVersionTestHeights("0,1,2,3,4,5,6,7,8,9,10,11,12"); + * getOrInitConsensusVersionTestHeights("0,1,2,3,4,5,6,7,8,9,10,11,12,13"); * * // Create a new NetworkClient, and RecordProvider * const recordProvider = new NetworkRecordProvider(account, networkClient); diff --git a/sdk/tests/wasm.test.ts b/sdk/tests/wasm.test.ts index 8d525ddd9..d49828683 100644 --- a/sdk/tests/wasm.test.ts +++ b/sdk/tests/wasm.test.ts @@ -564,9 +564,9 @@ describe('WASM Objects', () => { describe('Set development consensus version heights', () => { it('Consensus version heights can be set externally', async () => { if (process.env["RUN_SKIPPED"]) { - const heights = getOrInitConsensusVersionTestHeights("0,1,2,3,4,5,6,7,8,9,10,11,12"); + const heights = getOrInitConsensusVersionTestHeights("0,1,2,3,4,5,6,7,8,9,10,11,12,13"); console.log(heights); - expect(heights).to.deep.equal([0,1,2,3,4,5,6,7,8,9,10,11,12]); + expect(heights).to.deep.equal([0,1,2,3,4,5,6,7,8,9,10,11,12,13]); } }); }); diff --git a/wasm/src/programs/manager/deploy.rs b/wasm/src/programs/manager/deploy.rs index c3ee4c970..fefb13c25 100644 --- a/wasm/src/programs/manager/deploy.rs +++ b/wasm/src/programs/manager/deploy.rs @@ -96,6 +96,8 @@ impl ProgramManager { log("Creating deployment"); let node_url = url.as_deref().unwrap_or(DEFAULT_URL); let mut deployment = process.deploy::<CurrentAleo, _>(&program, rng).map_err(|err| err.to_string())?; + + // Ensure the deployment is not empty. if deployment.program().functions().is_empty() { return Err("Attempted to create an empty transaction deployment".to_string()); } @@ -103,6 +105,8 @@ impl ProgramManager { log("Setting program checksum and owner"); let latest_height = latest_block_height(node_url).await.map_err(|err| err.to_string())?; let consensus_version = CurrentNetwork::CONSENSUS_VERSION(latest_height).map_err(|err| err.to_string())?; + + // Set the program owner for programs above consensus version V8. let private_key_native = PrivateKeyNative::from(private_key); if consensus_version < ConsensusVersion::V9 { deployment.set_program_checksum_raw(None); @@ -113,17 +117,23 @@ impl ProgramManager { .set_program_owner_raw(Some(AddressNative::try_from(private_key_native).map_err(|e| e.to_string())?)); } + // Before V14: remove record verifying keys from the deployment. + if consensus_version < ConsensusVersion::V14 { + let record_names: Vec<_> = deployment.program().records().keys().cloned().collect(); + deployment.remove_verifying_keys(&record_names); + } + log("Ensuring the fee is sufficient to pay for the deployment"); - let (minimum_deployment_cost, (_, _, _, _)) = - deployment_cost::<CurrentNetwork>(process, &deployment, consensus_version) - .map_err(|err| err.to_string())?; + let (minimum_deployment_cost, _) = deployment_cost::<CurrentNetwork>(process, &deployment, consensus_version) + .map_err(|err| err.to_string())?; // Check to see if the fee record has enough microcredits to pay for the deployment. let priority_fee_microcredits = (priority_fee_credits * 1_000_000.0) as u64; Self::validate_fee_record(&fee_record, minimum_deployment_cost, priority_fee_microcredits)?; + // Execute the fee. let deployment_id = deployment.to_deployment_id().map_err(|e| e.to_string())?; - + let owner = ProgramOwnerNative::new(private_key, deployment_id, rng).map_err(|err| err.to_string())?; let fee = execute_fee!( process, private_key, @@ -138,9 +148,6 @@ impl ProgramManager { minimum_deployment_cost ); - // Create the program owner - let owner = ProgramOwnerNative::new(private_key, deployment_id, rng).map_err(|err| err.to_string())?; - log("Creating deployment transaction"); Ok(Transaction::from( TransactionNative::from_deployment(owner, deployment, fee).map_err(|err| err.to_string())?, @@ -171,7 +178,7 @@ impl ProgramManager { ProgramManager::resolve_imports(process, &program, imports)?; log("Create sample deployment"); - let deployment = + let mut deployment = process.deploy::<CurrentAleo, _>(&program, &mut StdRng::from_entropy()).map_err(|err| err.to_string())?; if deployment.program().functions().is_empty() { return Err("Attempted to create an empty transaction deployment".to_string()); @@ -181,10 +188,16 @@ impl ProgramManager { let latest_height = latest_block_height(DEFAULT_URL).await.map_err(|err| err.to_string())?; let consensus_version = <CurrentNetwork as Network>::CONSENSUS_VERSION(latest_height).map_err(|err| err.to_string())?; + + // Before V14: remove record verifying keys from the deployment. + if consensus_version < ConsensusVersion::V14 { + let record_names: Vec<_> = deployment.program().records().keys().cloned().collect(); + deployment.remove_verifying_keys(&record_names); + } + log("Estimate the deployment fee"); - let (minimum_deployment_cost, (_, _, _, _)) = - deployment_cost::<CurrentNetwork>(process, &deployment, consensus_version) - .map_err(|err| err.to_string())?; + let (minimum_deployment_cost, _) = deployment_cost::<CurrentNetwork>(process, &deployment, consensus_version) + .map_err(|err| err.to_string())?; Ok(minimum_deployment_cost) } @@ -265,6 +278,7 @@ impl ProgramManager { return Err("Attempted to create an empty transaction deployment".to_string()); } + // Set the program owner for programs above consensus version V8. log("Setting program checksum and owner"); let latest_height = latest_block_height(node_url).await.map_err(|err| err.to_string())?; let consensus_version = CurrentNetwork::CONSENSUS_VERSION(latest_height).map_err(|err| err.to_string())?; @@ -278,17 +292,23 @@ impl ProgramManager { .set_program_owner_raw(Some(AddressNative::try_from(private_key_native).map_err(|e| e.to_string())?)); } + // Before V14: remove record verifying keys from the deployment. + if consensus_version < ConsensusVersion::V14 { + let record_names: Vec<_> = deployment.program().records().keys().cloned().collect(); + deployment.remove_verifying_keys(&record_names); + } + log("Ensuring the fee is sufficient to pay for the deployment"); - let (minimum_deployment_cost, (_, _, _, _)) = - deployment_cost::<CurrentNetwork>(process, &deployment, consensus_version) - .map_err(|err| err.to_string())?; + let (minimum_deployment_cost, _) = deployment_cost::<CurrentNetwork>(process, &deployment, consensus_version) + .map_err(|err| err.to_string())?; // Check to see if the fee record has enough microcredits to pay for the deployment. let priority_fee_microcredits = (priority_fee_credits * 1_000_000.0) as u64; Self::validate_fee_record(&fee_record, minimum_deployment_cost, priority_fee_microcredits)?; + // Execute the fee. let deployment_id = deployment.to_deployment_id().map_err(|e| e.to_string())?; - + let owner = ProgramOwnerNative::new(private_key, deployment_id, rng).map_err(|err| err.to_string())?; let fee = execute_fee!( process, private_key, @@ -303,9 +323,6 @@ impl ProgramManager { minimum_deployment_cost ); - // Create the program owner - let owner = ProgramOwnerNative::new(private_key, deployment_id, rng).map_err(|err| err.to_string())?; - log("Creating deployment transaction"); Ok(Transaction::from( TransactionNative::from_deployment(owner, deployment, fee).map_err(|err| err.to_string())?, @@ -374,18 +391,20 @@ impl ProgramManager { verifying_keys.push((*function_name, (verifying_key, certificate))); } + // Attempt to set the edition. let edition_response = latest_program_edition(node_url, &program_id.to_string()).await; let edition = match edition_response { Ok(edition) => edition + 1, Err(_) => 0, }; + // Construct the deployment without running all proofs. let mut deployment = DeploymentNative::new(edition, program.clone(), verifying_keys, None, None) .map_err(|err| err.to_string())?; + // Set the program owner for programs above consensus version V8. let latest_height = latest_block_height(node_url).await.map_err(|err| err.to_string())?; let consensus_version = CurrentNetwork::CONSENSUS_VERSION(latest_height).map_err(|err| err.to_string())?; - let private_key_native = PrivateKeyNative::from(private_key); if consensus_version < ConsensusVersion::V9 { deployment.set_program_checksum_raw(None); @@ -396,9 +415,11 @@ impl ProgramManager { .set_program_owner_raw(Some(AddressNative::try_from(private_key_native).map_err(|e| e.to_string())?)); } - let deployment_id = deployment.to_deployment_id().map_err(|e| e.to_string())?; - - let owner = ProgramOwnerNative::new(private_key, deployment_id, rng).map_err(|err| err.to_string())?; + // Before V14: remove record verifying keys from the deployment. + if consensus_version < ConsensusVersion::V14 { + let record_names: Vec<_> = deployment.program().records().keys().cloned().collect(); + deployment.remove_verifying_keys(&record_names); + } // Construct the fee authorization for the deployment let (minimum_deployment_cost, _) = deployment_cost::<CurrentNetwork>(process, &deployment, consensus_version) @@ -409,6 +430,9 @@ impl ProgramManager { Self::validate_fee_record(&fee_record, minimum_deployment_cost, priority_fee_microcredits) .map_err(|e| e.to_string())?; + // Execute the fee. + let deployment_id = deployment.to_deployment_id().map_err(|e| e.to_string())?; + let owner = ProgramOwnerNative::new(private_key, deployment_id, rng).map_err(|err| err.to_string())?; let fee_authorization = match fee_record { Some(record) => process .authorize_fee_private::<CurrentAleo, _>( @@ -432,8 +456,7 @@ impl ProgramManager { }; // Get the state root. - let state_root = latest_stateroot(node_url).await.map_err(|e| e.to_string()).map_err(|e| e.to_string())?; - + let state_root = latest_stateroot(node_url).await.map_err(|e| e.to_string())?; let fee = FeeNative::from(fee_authorization.transitions().into_iter().next().unwrap().1, state_root, None) .map_err(|err| err.to_string())?; @@ -515,18 +538,20 @@ impl ProgramManager { verifying_keys.push((*function_name, (verifying_key, certificate))); } + // Attempt to get the latest program edition. let edition_response = latest_program_edition(node_url, &program_id.to_string()).await; let edition = match edition_response { Ok(edition) => edition + 1, Err(_) => 0, }; + // Construct the deployment without running all proofs. let mut deployment = DeploymentNative::new(edition, program.clone(), verifying_keys, None, None) .map_err(|err| err.to_string())?; + // Set the program owner for programs above consensus version V8. let latest_height = latest_block_height(node_url).await.map_err(|err| err.to_string())?; let consensus_version = CurrentNetwork::CONSENSUS_VERSION(latest_height).map_err(|err| err.to_string())?; - let private_key_native = PrivateKeyNative::from(private_key); if consensus_version < ConsensusVersion::V9 { deployment.set_program_checksum_raw(None); @@ -537,9 +562,11 @@ impl ProgramManager { .set_program_owner_raw(Some(AddressNative::try_from(private_key_native).map_err(|e| e.to_string())?)); } - let deployment_id = deployment.to_deployment_id().map_err(|e| e.to_string())?; - - let owner = ProgramOwnerNative::new(private_key, deployment_id, rng).map_err(|err| err.to_string())?; + // Before V14: remove record verifying keys from the deployment. + if consensus_version < ConsensusVersion::V14 { + let record_names: Vec<_> = deployment.program().records().keys().cloned().collect(); + deployment.remove_verifying_keys(&record_names); + } // Construct the fee authorization for the deployment let (minimum_deployment_cost, _) = deployment_cost::<CurrentNetwork>(process, &deployment, consensus_version) @@ -550,6 +577,9 @@ impl ProgramManager { Self::validate_fee_record(&fee_record, minimum_deployment_cost, priority_fee_microcredits) .map_err(|e| e.to_string())?; + // Execute the fee. + let deployment_id = deployment.to_deployment_id().map_err(|e| e.to_string())?; + let owner = ProgramOwnerNative::new(private_key, deployment_id, rng).map_err(|err| err.to_string())?; let fee_authorization = match fee_record { Some(record) => process .authorize_fee_private::<CurrentAleo, _>( @@ -573,8 +603,7 @@ impl ProgramManager { }; // Get the state root. - let state_root = latest_stateroot(node_url).await.map_err(|e| e.to_string()).map_err(|e| e.to_string())?; - + let state_root = latest_stateroot(node_url).await.map_err(|e| e.to_string())?; let fee = FeeNative::from(fee_authorization.transitions().into_iter().next().unwrap().1, state_root, None) .map_err(|err| err.to_string())?; diff --git a/wasm/src/utilities/mod.rs b/wasm/src/utilities/mod.rs index 09dabfa24..39c1beeb1 100644 --- a/wasm/src/utilities/mod.rs +++ b/wasm/src/utilities/mod.rs @@ -40,10 +40,10 @@ pub mod test; /// @param {string | undefined} heights The block heights at which each consensus version applies. This input should be a simple csv list of block heights and there should be one number for each consensus version. If left undefined, the default test heights will be applied. /// /// @example -/// import { getOrInitConsensusVersionHeights } from @provablehq/sdk; +/// import { getOrInitConsensusVersionTestHeights } from '@provablehq/sdk'; /// /// Set the consensus version heights. -/// getOrInitConsensusVersionTestHeights("0,1,2,3,4,5,6,7,8,9,10,11,12"); +/// getOrInitConsensusVersionTestHeights("0,1,2,3,4,5,6,7,8,9,10,11,12,13"); #[wasm_bindgen::prelude::wasm_bindgen(js_name = getOrInitConsensusVersionTestHeights)] pub fn get_or_init_consensus_version_heights(heights: Option<String>) -> js_sys::Array { // Call the underlying Rust function that returns [(ConsensusVersion, u32); N] From 25423eb52799a3e3a7d357e24f200911a906aa5d Mon Sep 17 00:00:00 2001 From: Michael Turner <mike@provable.com> Date: Wed, 11 Mar 2026 14:35:23 -0500 Subject: [PATCH 05/17] Update SnarkVM rev for dynamic dispatch --- wasm/Cargo.toml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/wasm/Cargo.toml b/wasm/Cargo.toml index 3ce54766c..fd21f4db3 100644 --- a/wasm/Cargo.toml +++ b/wasm/Cargo.toml @@ -23,16 +23,16 @@ doctest = false [dependencies.snarkvm-algorithms] git = "https://github.com/ProvableHQ/snarkVM.git" -rev = "ed7b59031741f13f148f63d7cda4e1e1634399f6" +rev = "7296b3cc9cffb8b71eb15dad3a00603d21a5912f" [dependencies.snarkvm-circuit-network] git = "https://github.com/ProvableHQ/snarkVM.git" -rev = "ed7b59031741f13f148f63d7cda4e1e1634399f6" +rev = "7296b3cc9cffb8b71eb15dad3a00603d21a5912f" features = ["wasm"] [dependencies.snarkvm-console] git = "https://github.com/ProvableHQ/snarkVM.git" -rev = "ed7b59031741f13f148f63d7cda4e1e1634399f6" +rev = "7296b3cc9cffb8b71eb15dad3a00603d21a5912f" default-features = false features = [ "account", @@ -46,37 +46,37 @@ features = [ [dependencies.snarkvm-ledger-block] git = "https://github.com/ProvableHQ/snarkVM.git" -rev = "ed7b59031741f13f148f63d7cda4e1e1634399f6" +rev = "7296b3cc9cffb8b71eb15dad3a00603d21a5912f" features = ["wasm"] [dependencies.snarkvm-ledger-query] git = "https://github.com/ProvableHQ/snarkVM.git" -rev = "ed7b59031741f13f148f63d7cda4e1e1634399f6" +rev = "7296b3cc9cffb8b71eb15dad3a00603d21a5912f" features = ["async", "wasm"] [dependencies.snarkvm-ledger-store] git = "https://github.com/ProvableHQ/snarkVM.git" -rev = "ed7b59031741f13f148f63d7cda4e1e1634399f6" +rev = "7296b3cc9cffb8b71eb15dad3a00603d21a5912f" [dependencies.snarkvm-parameters] git = "https://github.com/ProvableHQ/snarkVM.git" -rev = "ed7b59031741f13f148f63d7cda4e1e1634399f6" +rev = "7296b3cc9cffb8b71eb15dad3a00603d21a5912f" default-features = false features = ["wasm"] [dependencies.snarkvm-synthesizer-program] git = "https://github.com/ProvableHQ/snarkVM.git" -rev = "ed7b59031741f13f148f63d7cda4e1e1634399f6" +rev = "7296b3cc9cffb8b71eb15dad3a00603d21a5912f" features = ["wasm"] [dependencies.snarkvm-synthesizer] git = "https://github.com/ProvableHQ/snarkVM.git" -rev = "ed7b59031741f13f148f63d7cda4e1e1634399f6" +rev = "7296b3cc9cffb8b71eb15dad3a00603d21a5912f" features = ["async", "wasm"] [dependencies.snarkvm-wasm] git = "https://github.com/ProvableHQ/snarkVM.git" -rev = "ed7b59031741f13f148f63d7cda4e1e1634399f6" +rev = "7296b3cc9cffb8b71eb15dad3a00603d21a5912f" features = ["fields", "utilities"] [dependencies.anyhow] From 02d19b4dafb68712db2d68927d5cb20b07f4517b Mon Sep 17 00:00:00 2001 From: Mike Turner <mike@provable.com> Date: Thu, 12 Mar 2026 09:51:52 -0700 Subject: [PATCH 06/17] [Feature] Create stringToField utility (#1237) * Add stringToField utility function for converting program and function names to fields for dynamic dispatch * Add exports of the stringToField utility in the js SDK --- sdk/src/browser.ts | 1 + sdk/src/wasm.ts | 1 + wasm/Cargo.lock | 220 +++++++++++----------- wasm/src/lib.rs | 2 + wasm/src/programs/data/helpers/literal.rs | 4 + wasm/src/utilities/mod.rs | 16 ++ 6 files changed, 134 insertions(+), 110 deletions(-) diff --git a/sdk/src/browser.ts b/sdk/src/browser.ts index 45569648c..5d5c20455 100644 --- a/sdk/src/browser.ts +++ b/sdk/src/browser.ts @@ -142,6 +142,7 @@ export { RecordPlaintext, Signature, Scalar, + stringToField, Transaction, Transition, U8, diff --git a/sdk/src/wasm.ts b/sdk/src/wasm.ts index 2762cf701..dfef0ea96 100644 --- a/sdk/src/wasm.ts +++ b/sdk/src/wasm.ts @@ -38,6 +38,7 @@ export { RecordPlaintext, Scalar, Signature, + stringToField, Transaction, Transition, U8, diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index aedd384b7..d4411e60b 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -2476,8 +2476,8 @@ dependencies = [ [[package]] name = "snarkvm-algorithms" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "aleo-std", "anyhow", @@ -2503,8 +2503,8 @@ dependencies = [ [[package]] name = "snarkvm-circuit" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "snarkvm-circuit-account", "snarkvm-circuit-algorithms", @@ -2517,8 +2517,8 @@ dependencies = [ [[package]] name = "snarkvm-circuit-account" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "snarkvm-circuit-network", "snarkvm-circuit-types", @@ -2527,8 +2527,8 @@ dependencies = [ [[package]] name = "snarkvm-circuit-algorithms" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "snarkvm-circuit-types", "snarkvm-console-algorithms", @@ -2537,8 +2537,8 @@ dependencies = [ [[package]] name = "snarkvm-circuit-collections" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "snarkvm-circuit-algorithms", "snarkvm-circuit-types", @@ -2547,8 +2547,8 @@ dependencies = [ [[package]] name = "snarkvm-circuit-environment" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "anyhow", "indexmap", @@ -2567,13 +2567,13 @@ dependencies = [ [[package]] name = "snarkvm-circuit-environment-witness" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" [[package]] name = "snarkvm-circuit-network" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "snarkvm-circuit-algorithms", "snarkvm-circuit-collections", @@ -2583,8 +2583,8 @@ dependencies = [ [[package]] name = "snarkvm-circuit-program" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "snarkvm-circuit-account", "snarkvm-circuit-algorithms", @@ -2597,8 +2597,8 @@ dependencies = [ [[package]] name = "snarkvm-circuit-types" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "snarkvm-circuit-environment", "snarkvm-circuit-types-address", @@ -2612,8 +2612,8 @@ dependencies = [ [[package]] name = "snarkvm-circuit-types-address" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "snarkvm-circuit-environment", "snarkvm-circuit-types-boolean", @@ -2625,8 +2625,8 @@ dependencies = [ [[package]] name = "snarkvm-circuit-types-boolean" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "snarkvm-circuit-environment", "snarkvm-console-types-boolean", @@ -2634,8 +2634,8 @@ dependencies = [ [[package]] name = "snarkvm-circuit-types-field" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "snarkvm-circuit-environment", "snarkvm-circuit-types-boolean", @@ -2644,8 +2644,8 @@ dependencies = [ [[package]] name = "snarkvm-circuit-types-group" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "snarkvm-circuit-environment", "snarkvm-circuit-types-boolean", @@ -2656,8 +2656,8 @@ dependencies = [ [[package]] name = "snarkvm-circuit-types-integers" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "snarkvm-circuit-environment", "snarkvm-circuit-types-boolean", @@ -2668,8 +2668,8 @@ dependencies = [ [[package]] name = "snarkvm-circuit-types-scalar" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "snarkvm-circuit-environment", "snarkvm-circuit-types-boolean", @@ -2679,8 +2679,8 @@ dependencies = [ [[package]] name = "snarkvm-circuit-types-string" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "snarkvm-circuit-environment", "snarkvm-circuit-types-boolean", @@ -2691,8 +2691,8 @@ dependencies = [ [[package]] name = "snarkvm-console" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "snarkvm-console-account", "snarkvm-console-algorithms", @@ -2704,8 +2704,8 @@ dependencies = [ [[package]] name = "snarkvm-console-account" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "bs58", "snarkvm-console-network", @@ -2715,8 +2715,8 @@ dependencies = [ [[package]] name = "snarkvm-console-algorithms" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "blake2s_simd", "hex", @@ -2731,10 +2731,11 @@ dependencies = [ [[package]] name = "snarkvm-console-collections" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "aleo-std", + "parking_lot", "rayon", "serde", "snarkvm-console-algorithms", @@ -2743,8 +2744,8 @@ dependencies = [ [[package]] name = "snarkvm-console-network" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "anyhow", "enum-iterator", @@ -2763,8 +2764,8 @@ dependencies = [ [[package]] name = "snarkvm-console-network-environment" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "anyhow", "bech32", @@ -2781,14 +2782,13 @@ dependencies = [ [[package]] name = "snarkvm-console-program" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "enum-iterator", "enum_index", "enum_index_derive", "indexmap", - "itertools", "num-derive", "num-traits", "seq-macro", @@ -2803,8 +2803,8 @@ dependencies = [ [[package]] name = "snarkvm-console-types" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "snarkvm-console-network-environment", "snarkvm-console-types-address", @@ -2818,8 +2818,8 @@ dependencies = [ [[package]] name = "snarkvm-console-types-address" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "snarkvm-console-network-environment", "snarkvm-console-types-boolean", @@ -2829,16 +2829,16 @@ dependencies = [ [[package]] name = "snarkvm-console-types-boolean" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "snarkvm-console-network-environment", ] [[package]] name = "snarkvm-console-types-field" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "snarkvm-console-network-environment", "snarkvm-console-types-boolean", @@ -2847,8 +2847,8 @@ dependencies = [ [[package]] name = "snarkvm-console-types-group" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "snarkvm-console-network-environment", "snarkvm-console-types-boolean", @@ -2858,8 +2858,8 @@ dependencies = [ [[package]] name = "snarkvm-console-types-integers" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "snarkvm-console-network-environment", "snarkvm-console-types-boolean", @@ -2869,8 +2869,8 @@ dependencies = [ [[package]] name = "snarkvm-console-types-scalar" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "snarkvm-console-network-environment", "snarkvm-console-types-boolean", @@ -2880,8 +2880,8 @@ dependencies = [ [[package]] name = "snarkvm-console-types-string" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "snarkvm-console-network-environment", "snarkvm-console-types-boolean", @@ -2891,8 +2891,8 @@ dependencies = [ [[package]] name = "snarkvm-curves" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "rand 0.8.5", "rustc_version", @@ -2904,8 +2904,8 @@ dependencies = [ [[package]] name = "snarkvm-fields" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "aleo-std", "anyhow", @@ -2921,8 +2921,8 @@ dependencies = [ [[package]] name = "snarkvm-ledger-authority" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "anyhow", "rand 0.8.5", @@ -2933,8 +2933,8 @@ dependencies = [ [[package]] name = "snarkvm-ledger-block" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "anyhow", "indexmap", @@ -2956,8 +2956,8 @@ dependencies = [ [[package]] name = "snarkvm-ledger-committee" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "indexmap", "rayon", @@ -2968,8 +2968,8 @@ dependencies = [ [[package]] name = "snarkvm-ledger-narwhal-batch-certificate" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "indexmap", "rayon", @@ -2981,8 +2981,8 @@ dependencies = [ [[package]] name = "snarkvm-ledger-narwhal-batch-header" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "indexmap", "rayon", @@ -2993,8 +2993,8 @@ dependencies = [ [[package]] name = "snarkvm-ledger-narwhal-data" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "bytes", "serde_json", @@ -3003,8 +3003,8 @@ dependencies = [ [[package]] name = "snarkvm-ledger-narwhal-subdag" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "indexmap", "rayon", @@ -3018,8 +3018,8 @@ dependencies = [ [[package]] name = "snarkvm-ledger-narwhal-transmission-id" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "snarkvm-console", "snarkvm-ledger-puzzle", @@ -3027,8 +3027,8 @@ dependencies = [ [[package]] name = "snarkvm-ledger-puzzle" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "aleo-std", "anyhow", @@ -3046,8 +3046,8 @@ dependencies = [ [[package]] name = "snarkvm-ledger-puzzle-epoch" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "aleo-std", "anyhow", @@ -3068,8 +3068,8 @@ dependencies = [ [[package]] name = "snarkvm-ledger-query" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "anyhow", "async-trait", @@ -3085,8 +3085,8 @@ dependencies = [ [[package]] name = "snarkvm-ledger-store" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "aleo-std-storage", "anyhow", @@ -3110,8 +3110,8 @@ dependencies = [ [[package]] name = "snarkvm-parameters" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "aleo-std", "anyhow", @@ -3135,8 +3135,8 @@ dependencies = [ [[package]] name = "snarkvm-synthesizer" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "aleo-std", "anyhow", @@ -3170,7 +3170,7 @@ dependencies = [ [[package]] name = "snarkvm-synthesizer-error" version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "anyhow", "snarkvm-circuit-environment", @@ -3180,8 +3180,8 @@ dependencies = [ [[package]] name = "snarkvm-synthesizer-process" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "aleo-std", "colored", @@ -3206,8 +3206,8 @@ dependencies = [ [[package]] name = "snarkvm-synthesizer-program" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "enum-iterator", "indexmap", @@ -3227,8 +3227,8 @@ dependencies = [ [[package]] name = "snarkvm-synthesizer-snark" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "bincode", "serde_json", @@ -3240,8 +3240,8 @@ dependencies = [ [[package]] name = "snarkvm-utilities" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "aleo-std", "anyhow", @@ -3263,8 +3263,8 @@ dependencies = [ [[package]] name = "snarkvm-utilities-derives" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "proc-macro2", "quote 1.0.42", @@ -3273,8 +3273,8 @@ dependencies = [ [[package]] name = "snarkvm-wasm" -version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=ed7b59031741f13f148f63d7cda4e1e1634399f6#ed7b59031741f13f148f63d7cda4e1e1634399f6" +version = "4.5.0" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" dependencies = [ "getrandom 0.2.16", "snarkvm-console", diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs index df6ba7680..70d73de33 100644 --- a/wasm/src/lib.rs +++ b/wasm/src/lib.rs @@ -182,12 +182,14 @@ pub use utilities::{ EncryptionToolkit, get, get_network, + get_or_init_consensus_version_heights, get_program_from_network, get_statepath_for_commitment, get_statepaths_for_commitments, latest_block_height, latest_program_edition, latest_stateroot, + string_to_field, }; #[cfg(test)] diff --git a/wasm/src/programs/data/helpers/literal.rs b/wasm/src/programs/data/helpers/literal.rs index 898e547be..8a9ce19be 100644 --- a/wasm/src/programs/data/helpers/literal.rs +++ b/wasm/src/programs/data/helpers/literal.rs @@ -86,5 +86,9 @@ pub fn literal_to_js_value(literal: &LiteralNative) -> JsValue { (&js_string).into() } LiteralNative::String(literal) => (&**literal).into(), + LiteralNative::Identifier(identifier) => { + let js_string = identifier.to_string(); + (&js_string).into() + } } } diff --git a/wasm/src/utilities/mod.rs b/wasm/src/utilities/mod.rs index 39c1beeb1..4e06f6a7b 100644 --- a/wasm/src/utilities/mod.rs +++ b/wasm/src/utilities/mod.rs @@ -21,6 +21,7 @@ pub mod encrypt; pub use encrypt::EncryptionToolkit; pub mod rest; +use crate::{Field, types::native::IdentifierNative}; pub use rest::{ get, get_network, @@ -31,6 +32,8 @@ pub use rest::{ latest_program_edition, latest_stateroot, }; +use snarkvm_console::prelude::ToField; +use std::str::FromStr; #[cfg(test)] pub mod test; @@ -53,6 +56,13 @@ pub fn get_or_init_consensus_version_heights(heights: Option<String>) -> js_sys: pairs.iter().map(|(_, height)| wasm_bindgen::JsValue::from_f64(*height as f64)).collect::<js_sys::Array>() } +#[wasm_bindgen::prelude::wasm_bindgen(js_name = stringToField)] +pub fn string_to_field(string: &str) -> Result<Field, String> { + Ok(Field::from( + IdentifierNative::from_str(string).map_err(|e| e.to_string())?.to_field().map_err(|e| e.to_string())?, + )) +} + #[cfg(test)] mod tests { use super::*; @@ -63,4 +73,10 @@ mod tests { fn test_set_genesis_block_non_zero_fails() { get_or_init_consensus_version_heights(Some("10,9,8,7,6,5,4,3,2,1,0,1,2".to_string())); } + + #[wasm_bindgen_test] + fn test_string_to_field() { + assert_eq!(string_to_field("transfer_a").unwrap().to_string(), "459830232632696923845236field"); + assert_eq!(string_to_field("transfer_b").unwrap().to_string(), "464552599115566569058932field"); + } } From a2d2ad777825675050366f01a032abf5ee4eff4f Mon Sep 17 00:00:00 2001 From: Mike Turner <mike@provable.com> Date: Thu, 12 Mar 2026 12:03:11 -0700 Subject: [PATCH 07/17] [Feature] Re-export dynamic record from SnarkVM (#1236) * Add the dynamic record type * Add JS side tests for the DynamicRecord type * Check visibility on record conversion roundtrip --------- Signed-off-by: Mike Turner <mike@provable.com> --- sdk/src/browser.ts | 1 + sdk/src/wasm.ts | 5 +- sdk/tests/wasm.test.ts | 67 +++++++- wasm/src/record/dynamic_record.rs | 244 ++++++++++++++++++++++++++++++ wasm/src/record/mod.rs | 3 + wasm/src/types/native/mod.rs | 2 + 6 files changed, 319 insertions(+), 3 deletions(-) create mode 100644 wasm/src/record/dynamic_record.rs diff --git a/sdk/src/browser.ts b/sdk/src/browser.ts index 5d5c20455..fad247007 100644 --- a/sdk/src/browser.ts +++ b/sdk/src/browser.ts @@ -113,6 +113,7 @@ export { BHP1024, Ciphertext, ComputeKey, + DynamicRecord, Execution as FunctionExecution, ExecutionRequest, ExecutionResponse, diff --git a/sdk/src/wasm.ts b/sdk/src/wasm.ts index dfef0ea96..e0e629e4c 100644 --- a/sdk/src/wasm.ts +++ b/sdk/src/wasm.ts @@ -8,6 +8,7 @@ export { BHP1024, Ciphertext, ComputeKey, + DynamicRecord, EncryptionToolkit, ExecutionRequest, Execution, @@ -15,8 +16,8 @@ export { Field, GraphKey, Group, - I8, - I16, + I8, + I16, I32, I64, I128, diff --git a/sdk/tests/wasm.test.ts b/sdk/tests/wasm.test.ts index d49828683..cffbac515 100644 --- a/sdk/tests/wasm.test.ts +++ b/sdk/tests/wasm.test.ts @@ -1,5 +1,5 @@ import { expect } from "chai"; -import { Address, AleoNetworkClient, CREDITS_PROGRAM_KEYS, Field, FunctionKeyPair, PrivateKey, ViewKey, Signature, RecordCiphertext, RecordPlaintext, PrivateKeyCiphertext, EncryptionToolkit, Transition, VerifyingKey, AleoKeyProvider, getOrInitConsensusVersionTestHeights} from "../src/node.js"; +import { Address, AleoNetworkClient, CREDITS_PROGRAM_KEYS, DynamicRecord, Field, FunctionKeyPair, PrivateKey, ViewKey, Signature, RecordCiphertext, RecordPlaintext, PrivateKeyCiphertext, EncryptionToolkit, Transition, VerifyingKey, AleoKeyProvider, getOrInitConsensusVersionTestHeights} from "../src/node.js"; import { seed, message, @@ -434,6 +434,71 @@ describe('WASM Objects', () => { }); }); + describe('DynamicRecord', () => { + // A credits record with _version: 1u8 (hiding variant) + const creditsRecord = RecordPlaintext.fromString(CREDITS_RECORD_V1); + const dynamicRecord = DynamicRecord.fromRecord(creditsRecord); + const dynamicRecordString = dynamicRecord.toString(); + + it('can be created from a RecordPlaintext', () => { + expect(dynamicRecord).instanceof(DynamicRecord); + }); + + it('round-trips through toString and fromString', () => { + const parsed = DynamicRecord.fromString(dynamicRecordString); + expect(parsed.toString()).equal(dynamicRecordString); + }); + + it('returns the correct owner address', () => { + expect(dynamicRecord.owner().to_string()).equal('aleo12a4wll9ax6w5355jph0dr5wt2vla5sss2t4cnch0tc3vzh643v8qcfvc7a'); + }); + + it('returns the correct nonce', () => { + expect(dynamicRecord.nonce().toString()).equal('3634848344765318974603121890869676775499130077229666060613233255327643175219group'); + }); + + it('returns a non-empty root field', () => { + const root = dynamicRecord.root(); + expect(root.toString()).to.be.a('string'); + expect(root.toString()).to.include('field'); + }); + + it('is a hiding variant because _version is 1', () => { + expect(dynamicRecord.isHiding()).equal(true); + }); + + it('round-trips through bytes', () => { + const bytes = dynamicRecord.toBytesLe(); + const recovered = DynamicRecord.fromBytesLe(bytes); + expect(recovered.owner().to_string()).equal(dynamicRecord.owner().to_string()); + expect(recovered.root().toString()).equal(dynamicRecord.root().toString()); + expect(recovered.nonce().toString()).equal(dynamicRecord.nonce().toString()); + }); + + it('returns a non-empty array of field elements', () => { + const fields = dynamicRecord.toFields(); + expect(fields).to.be.an('array'); + expect(fields.length).greaterThan(0); + }); + + it('returns a non-empty bit array', () => { + const bits = dynamicRecord.toBitsLe(); + expect(bits).to.be.an('array'); + expect(bits.length).greaterThan(0); + }); + + it('can convert back to a RecordPlaintext', () => { + const recovered = dynamicRecord.toRecord(true); + expect(recovered).instanceof(RecordPlaintext); + // Owner and nonce should round-trip + expect(recovered.toString()).to.include('aleo12a4wll9ax6w5355jph0dr5wt2vla5sss2t4cnch0tc3vzh643v8qcfvc7a.private'); + }); + + it('throws on an invalid string', () => { + expect(() => DynamicRecord.fromString('not a record')).throw(); + }); + }); + describe('Transition', () => { const transitionStringTestnet = `{"id":"au1u62jasyx78x9hktak24awyj38fz73aseq8g9cx98u8egd9pj9uxq3u6s2z","program":"hello_hello.aleo","function":"hello","inputs":[{"type":"public","id":"3748790614260807060977840590007893602934308327222309419419577452790958781330field","value":"1u32"},{"type":"private","id":"5954208307642819953251922459490586292095132973876550778604572231610245257004field","value":"ciphertext1qyq0m5mp0d2gzh2pv9p25z70gz2avhqdt3dp8y8thzwf3aq6g35zcqcuyptz3"}],"outputs":[{"type":"private","id":"1557506318887190915592751299113729867877933642317637206076176689093854281418field","value":"ciphertext1qyqzmhw8ln9r6uuyh0n5jrsqlt25wdggqp3d9yqyttpr3g7g00k2sysdf9rmv"}],"tpk":"7532444547840484531569841377269810017844130178606467837628364672670182422388group","tcm":"7292056195970541935877520517416922164990366931599720071937561392936678536563field","scm":"8283770351301010771186520129040704279224805960417079922462917369178354050332field"}`; const transitionTestnet = Transition.fromString(transitionStringTestnet); diff --git a/wasm/src/record/dynamic_record.rs b/wasm/src/record/dynamic_record.rs new file mode 100644 index 000000000..7066de116 --- /dev/null +++ b/wasm/src/record/dynamic_record.rs @@ -0,0 +1,244 @@ +// Copyright (C) 2019-2025 Provable Inc. +// This file is part of the Provable SDK library. + +// The Provable SDK library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Provable SDK library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Provable SDK library. If not, see <https://www.gnu.org/licenses/>. + +use crate::{ + Address, + RecordPlaintext, + js_array_from_fields, + to_bits_array_le, + types::{Field, Group, native::DynamicRecordNative}, +}; +use snarkvm_console::prelude::{FromBytes, ToBits, ToBytes, ToFields}; + +use js_sys::{Array, Uint8Array}; +use std::{ops::Deref, str::FromStr}; +use wasm_bindgen::prelude::*; + +/// A fixed-size representation of an Aleo record. Like static records, a dynamic record +/// contains an owner, nonce, and a version, but instead of storing the full data it only +/// stores the Merkle root of the data, ensuring all dynamic records have a constant size. +#[wasm_bindgen] +#[derive(Clone, Debug)] +pub struct DynamicRecord(DynamicRecordNative); + +#[wasm_bindgen] +impl DynamicRecord { + /// Creates a DynamicRecord from its string representation. + #[wasm_bindgen(js_name = "fromString")] + #[allow(clippy::should_implement_trait)] + pub fn from_string(s: &str) -> Result<DynamicRecord, String> { + Ok(Self(DynamicRecordNative::from_str(s).map_err(|e| e.to_string())?)) + } + + /// Returns the string representation of the dynamic record. + #[wasm_bindgen(js_name = "toString")] + #[allow(clippy::inherent_to_string)] + pub fn to_string(&self) -> String { + self.0.to_string() + } + + /// Returns the owner address of the dynamic record. + pub fn owner(&self) -> Address { + Address::from(self.0.owner()) + } + + /// Returns the Merkle root of the record data as a Field. + pub fn root(&self) -> Field { + Field::from(self.0.root()) + } + + /// Returns the nonce of the record as a Group. + pub fn nonce(&self) -> Group { + Group::from(self.0.nonce()) + } + + /// Returns `true` if the dynamic record is a hiding variant (version != 0). + #[wasm_bindgen(js_name = "isHiding")] + pub fn is_hiding(&self) -> bool { + self.0.is_hiding() + } + + /// Creates a DynamicRecord from a RecordPlaintext. + #[wasm_bindgen(js_name = "fromRecord")] + pub fn from_record(record: &RecordPlaintext) -> Result<DynamicRecord, String> { + Ok(Self(DynamicRecordNative::from_record(record.deref()).map_err(|e| e.to_string())?)) + } + + /// Converts this DynamicRecord back to a RecordPlaintext. + /// `owner_is_private` controls whether the owner field uses private or public visibility. + #[wasm_bindgen(js_name = "toRecord")] + pub fn to_record(&self, owner_is_private: bool) -> Result<RecordPlaintext, String> { + Ok(RecordPlaintext::from(self.0.to_record(owner_is_private).map_err(|e| e.to_string())?)) + } + + /// Deserializes a DynamicRecord from a little-endian byte array (Uint8Array). + #[wasm_bindgen(js_name = "fromBytesLe")] + pub fn from_bytes_le(bytes: &Uint8Array) -> Result<DynamicRecord, String> { + let bytes = bytes.to_vec(); + Ok(Self(DynamicRecordNative::from_bytes_le(&bytes).map_err(|e| e.to_string())?)) + } + + /// Serializes the dynamic record to a little-endian byte array (Uint8Array). + #[wasm_bindgen(js_name = "toBytesLe")] + pub fn to_bytes_le(&self) -> Result<Uint8Array, String> { + let bytes = self.0.to_bytes_le().map_err(|e| e.to_string())?; + Ok(Uint8Array::from(bytes.as_slice())) + } + + /// Returns the dynamic record as an array of field elements. + #[wasm_bindgen(js_name = "toFields")] + pub fn to_fields(&self) -> Result<Array, String> { + let fields = self.0.to_fields().map_err(|e| e.to_string())?; + Ok(js_array_from_fields!(&fields)) + } + + /// Returns the dynamic record as a little-endian bit array (JS Array of booleans). + #[wasm_bindgen(js_name = "toBitsLe")] + pub fn to_bits_le(&self) -> Array { + to_bits_array_le!(self) + } +} + +impl std::ops::Deref for DynamicRecord { + type Target = DynamicRecordNative; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From<DynamicRecordNative> for DynamicRecord { + fn from(native: DynamicRecordNative) -> Self { + Self(native) + } +} + +impl From<DynamicRecord> for DynamicRecordNative { + fn from(val: DynamicRecord) -> Self { + val.0 + } +} + +impl From<&DynamicRecordNative> for DynamicRecord { + fn from(native: &DynamicRecordNative) -> Self { + Self(native.clone()) + } +} + +impl From<&DynamicRecord> for DynamicRecordNative { + fn from(val: &DynamicRecord) -> Self { + val.0.clone() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use wasm_bindgen_test::*; + + // Credits record for testing. + const CREDITS_RECORD_V1: &str = "{ owner: aleo12a4wll9ax6w5355jph0dr5wt2vla5sss2t4cnch0tc3vzh643v8qcfvc7a.private, microcredits: 1000000u64.private, _nonce: 3634848344765318974603121890869676775499130077229666060613233255327643175219group.public, _version: 1u8.public }"; + + #[wasm_bindgen_test] + fn test_to_and_from_string() { + let dynamic_record_str = + DynamicRecord::from_record(&RecordPlaintext::from_string(CREDITS_RECORD_V1).unwrap()).unwrap().to_string(); + let record = DynamicRecord::from_string(&dynamic_record_str).unwrap(); + assert_eq!(record.to_string(), dynamic_record_str); + } + + #[wasm_bindgen_test] + fn test_clone() { + let dynamic_record_str = + DynamicRecord::from_record(&RecordPlaintext::from_string(CREDITS_RECORD_V1).unwrap()).unwrap().to_string(); + let record = DynamicRecord::from_string(&dynamic_record_str).unwrap(); + let cloned = record.clone(); + assert_eq!(record.to_string(), cloned.to_string()); + } + + #[wasm_bindgen_test] + fn test_owner() { + let dynamic_record_str = + DynamicRecord::from_record(&RecordPlaintext::from_string(CREDITS_RECORD_V1).unwrap()).unwrap().to_string(); + let record = DynamicRecord::from_string(&dynamic_record_str).unwrap(); + assert_eq!(record.owner().to_string(), "aleo12a4wll9ax6w5355jph0dr5wt2vla5sss2t4cnch0tc3vzh643v8qcfvc7a"); + } + + #[wasm_bindgen_test] + fn test_root() { + let dynamic_record_str = + DynamicRecord::from_record(&RecordPlaintext::from_string(CREDITS_RECORD_V1).unwrap()).unwrap().to_string(); + let record = DynamicRecord::from_string(&dynamic_record_str).unwrap(); + assert_eq!( + record.root().to_string(), + "3632128850012040781624982531669233558118256132998845003264257146683715587370field" + ); + } + + #[wasm_bindgen_test] + fn test_nonce() { + let dynamic_record_str = + DynamicRecord::from_record(&RecordPlaintext::from_string(CREDITS_RECORD_V1).unwrap()).unwrap().to_string(); + let record = DynamicRecord::from_string(&dynamic_record_str).unwrap(); + assert_eq!( + record.nonce().to_string(), + "3634848344765318974603121890869676775499130077229666060613233255327643175219group" + ); + } + + #[wasm_bindgen_test] + fn test_is_hiding_true() { + let dynamic_record_str = + DynamicRecord::from_record(&RecordPlaintext::from_string(CREDITS_RECORD_V1).unwrap()).unwrap().to_string(); + let record = DynamicRecord::from_string(&dynamic_record_str).unwrap(); + assert!(record.is_hiding()); + } + + #[wasm_bindgen_test] + fn test_bytes_round_trip() { + let dynamic_record_str = + DynamicRecord::from_record(&RecordPlaintext::from_string(CREDITS_RECORD_V1).unwrap()).unwrap().to_string(); + let record = DynamicRecord::from_string(&dynamic_record_str).unwrap(); + let bytes = record.to_bytes_le().unwrap(); + let recovered = DynamicRecord::from_bytes_le(&bytes).unwrap(); + assert_eq!(record.owner().to_string(), recovered.owner().to_string()); + assert_eq!(record.root().to_string(), recovered.root().to_string()); + assert_eq!(record.nonce().to_string(), recovered.nonce().to_string()); + } + + #[wasm_bindgen_test] + fn test_to_fields() { + let dynamic_record_str = + DynamicRecord::from_record(&RecordPlaintext::from_string(CREDITS_RECORD_V1).unwrap()).unwrap().to_string(); + let record = DynamicRecord::from_string(&dynamic_record_str).unwrap(); + let fields = record.to_fields().unwrap(); + assert!(fields.length() > 0); + } + + #[wasm_bindgen_test] + fn test_to_bits_le() { + let dynamic_record_str = + DynamicRecord::from_record(&RecordPlaintext::from_string(CREDITS_RECORD_V1).unwrap()).unwrap().to_string(); + let record = DynamicRecord::from_string(&dynamic_record_str).unwrap(); + let bits = record.to_bits_le(); + assert!(bits.length() > 0); + } + + #[wasm_bindgen_test] + fn test_from_string_invalid() { + assert!(DynamicRecord::from_string("not a record").is_err()); + } +} diff --git a/wasm/src/record/mod.rs b/wasm/src/record/mod.rs index d6c22fc62..42790260d 100644 --- a/wasm/src/record/mod.rs +++ b/wasm/src/record/mod.rs @@ -14,6 +14,9 @@ // You should have received a copy of the GNU General Public License // along with the Provable SDK library. If not, see <https://www.gnu.org/licenses/>. +pub mod dynamic_record; +pub use dynamic_record::*; + pub mod record_ciphertext; pub use record_ciphertext::*; diff --git a/wasm/src/types/native/mod.rs b/wasm/src/types/native/mod.rs index 978f3edb6..48c6e5956 100644 --- a/wasm/src/types/native/mod.rs +++ b/wasm/src/types/native/mod.rs @@ -21,6 +21,7 @@ use snarkvm_console::{ program::{ Argument, Ciphertext, + DynamicRecord, Entry, Future, Identifier, @@ -97,6 +98,7 @@ pub type PlaintextNative = Plaintext<CurrentNetwork>; pub type PlaintextEntryNative = Entry<CurrentNetwork, PlaintextNative>; pub type ProgramIDNative = ProgramID<CurrentNetwork>; pub type ProgramNative = Program<CurrentNetwork>; +pub type DynamicRecordNative = DynamicRecord<CurrentNetwork>; pub type RecordCiphertextNative = Record<CurrentNetwork, CiphertextNative>; pub type RecordPlaintextNative = Record<CurrentNetwork, PlaintextNative>; pub type ResponseNative = Response<CurrentNetwork>; From 78a336279aaa6894a75998374a8dfed214cedab7 Mon Sep 17 00:00:00 2001 From: d0cd <23022326+d0cd@users.noreply.github.com> Date: Mon, 23 Mar 2026 21:11:21 -0400 Subject: [PATCH 08/17] [Feature] Updates to make AMM tests work. (#1241) * Handle Consensus V14 in deployments (#1230) * Handle Consensus V14 in deployments * Remove redundant .map_err in latest_stateroot call * Update getOrInitConsensusVersionTestHeights tests and doc comments to handle all currently active test version heights * Deallocate deployment cost tuple * Updates to make AMM tests works * Also handle upgrade * Address feedback * Cleanup * Cargo fmt lints * Revert .gitignore changes unrelated to this PR * Change core rev to the testnet 4.6.0 candidate * Bump version update to v0.9.18 * Remove unecessary dependencies in the workspace root --------- Co-authored-by: Mike Turner <mike@provable.com> --- create-leo-app/template-devnode-js/index.js | 1 + wasm/Cargo.lock | 112 ++++++++++---------- wasm/Cargo.toml | 22 ++-- wasm/src/programs/manager/deploy.rs | 97 ++++++++--------- wasm/src/programs/manager/mod.rs | 5 +- 5 files changed, 118 insertions(+), 119 deletions(-) diff --git a/create-leo-app/template-devnode-js/index.js b/create-leo-app/template-devnode-js/index.js index 78220c043..aa5f9d9ff 100644 --- a/create-leo-app/template-devnode-js/index.js +++ b/create-leo-app/template-devnode-js/index.js @@ -33,6 +33,7 @@ async function main() { // Initialize multi-threading to allow WASM execution. await initThreadPool(); const heights = getOrInitConsensusVersionTestHeights("0,1,2,3,4,5,6,7,8,9,10,11,12,13"); + console.log(`Set development consensus heights to ${heights}`); const privateKey = "APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH"; const account = new Account({privateKey}); diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index d4411e60b..ccf21c9d4 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -83,7 +83,7 @@ checksum = "12aca1021aef2c476bad30d2f681e891b2be4f07dbc230a96df09cb693bfb3cb" [[package]] name = "aleo-wasm" -version = "0.9.17" +version = "0.9.18" dependencies = [ "anyhow", "async-trait", @@ -2477,7 +2477,7 @@ dependencies = [ [[package]] name = "snarkvm-algorithms" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "aleo-std", "anyhow", @@ -2504,7 +2504,7 @@ dependencies = [ [[package]] name = "snarkvm-circuit" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "snarkvm-circuit-account", "snarkvm-circuit-algorithms", @@ -2518,7 +2518,7 @@ dependencies = [ [[package]] name = "snarkvm-circuit-account" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "snarkvm-circuit-network", "snarkvm-circuit-types", @@ -2528,7 +2528,7 @@ dependencies = [ [[package]] name = "snarkvm-circuit-algorithms" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "snarkvm-circuit-types", "snarkvm-console-algorithms", @@ -2538,7 +2538,7 @@ dependencies = [ [[package]] name = "snarkvm-circuit-collections" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "snarkvm-circuit-algorithms", "snarkvm-circuit-types", @@ -2548,7 +2548,7 @@ dependencies = [ [[package]] name = "snarkvm-circuit-environment" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "anyhow", "indexmap", @@ -2568,12 +2568,12 @@ dependencies = [ [[package]] name = "snarkvm-circuit-environment-witness" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" [[package]] name = "snarkvm-circuit-network" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "snarkvm-circuit-algorithms", "snarkvm-circuit-collections", @@ -2584,7 +2584,7 @@ dependencies = [ [[package]] name = "snarkvm-circuit-program" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "snarkvm-circuit-account", "snarkvm-circuit-algorithms", @@ -2598,7 +2598,7 @@ dependencies = [ [[package]] name = "snarkvm-circuit-types" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "snarkvm-circuit-environment", "snarkvm-circuit-types-address", @@ -2613,7 +2613,7 @@ dependencies = [ [[package]] name = "snarkvm-circuit-types-address" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "snarkvm-circuit-environment", "snarkvm-circuit-types-boolean", @@ -2626,7 +2626,7 @@ dependencies = [ [[package]] name = "snarkvm-circuit-types-boolean" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "snarkvm-circuit-environment", "snarkvm-console-types-boolean", @@ -2635,7 +2635,7 @@ dependencies = [ [[package]] name = "snarkvm-circuit-types-field" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "snarkvm-circuit-environment", "snarkvm-circuit-types-boolean", @@ -2645,7 +2645,7 @@ dependencies = [ [[package]] name = "snarkvm-circuit-types-group" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "snarkvm-circuit-environment", "snarkvm-circuit-types-boolean", @@ -2657,7 +2657,7 @@ dependencies = [ [[package]] name = "snarkvm-circuit-types-integers" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "snarkvm-circuit-environment", "snarkvm-circuit-types-boolean", @@ -2669,7 +2669,7 @@ dependencies = [ [[package]] name = "snarkvm-circuit-types-scalar" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "snarkvm-circuit-environment", "snarkvm-circuit-types-boolean", @@ -2680,7 +2680,7 @@ dependencies = [ [[package]] name = "snarkvm-circuit-types-string" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "snarkvm-circuit-environment", "snarkvm-circuit-types-boolean", @@ -2692,7 +2692,7 @@ dependencies = [ [[package]] name = "snarkvm-console" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "snarkvm-console-account", "snarkvm-console-algorithms", @@ -2705,7 +2705,7 @@ dependencies = [ [[package]] name = "snarkvm-console-account" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "bs58", "snarkvm-console-network", @@ -2716,7 +2716,7 @@ dependencies = [ [[package]] name = "snarkvm-console-algorithms" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "blake2s_simd", "hex", @@ -2732,7 +2732,7 @@ dependencies = [ [[package]] name = "snarkvm-console-collections" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "aleo-std", "parking_lot", @@ -2745,7 +2745,7 @@ dependencies = [ [[package]] name = "snarkvm-console-network" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "anyhow", "enum-iterator", @@ -2765,7 +2765,7 @@ dependencies = [ [[package]] name = "snarkvm-console-network-environment" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "anyhow", "bech32", @@ -2783,7 +2783,7 @@ dependencies = [ [[package]] name = "snarkvm-console-program" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "enum-iterator", "enum_index", @@ -2804,7 +2804,7 @@ dependencies = [ [[package]] name = "snarkvm-console-types" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "snarkvm-console-network-environment", "snarkvm-console-types-address", @@ -2819,7 +2819,7 @@ dependencies = [ [[package]] name = "snarkvm-console-types-address" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "snarkvm-console-network-environment", "snarkvm-console-types-boolean", @@ -2830,7 +2830,7 @@ dependencies = [ [[package]] name = "snarkvm-console-types-boolean" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "snarkvm-console-network-environment", ] @@ -2838,7 +2838,7 @@ dependencies = [ [[package]] name = "snarkvm-console-types-field" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "snarkvm-console-network-environment", "snarkvm-console-types-boolean", @@ -2848,7 +2848,7 @@ dependencies = [ [[package]] name = "snarkvm-console-types-group" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "snarkvm-console-network-environment", "snarkvm-console-types-boolean", @@ -2859,7 +2859,7 @@ dependencies = [ [[package]] name = "snarkvm-console-types-integers" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "snarkvm-console-network-environment", "snarkvm-console-types-boolean", @@ -2870,7 +2870,7 @@ dependencies = [ [[package]] name = "snarkvm-console-types-scalar" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "snarkvm-console-network-environment", "snarkvm-console-types-boolean", @@ -2881,7 +2881,7 @@ dependencies = [ [[package]] name = "snarkvm-console-types-string" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "snarkvm-console-network-environment", "snarkvm-console-types-boolean", @@ -2892,7 +2892,7 @@ dependencies = [ [[package]] name = "snarkvm-curves" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "rand 0.8.5", "rustc_version", @@ -2905,7 +2905,7 @@ dependencies = [ [[package]] name = "snarkvm-fields" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "aleo-std", "anyhow", @@ -2922,7 +2922,7 @@ dependencies = [ [[package]] name = "snarkvm-ledger-authority" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "anyhow", "rand 0.8.5", @@ -2934,7 +2934,7 @@ dependencies = [ [[package]] name = "snarkvm-ledger-block" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "anyhow", "indexmap", @@ -2957,7 +2957,7 @@ dependencies = [ [[package]] name = "snarkvm-ledger-committee" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "indexmap", "rayon", @@ -2969,7 +2969,7 @@ dependencies = [ [[package]] name = "snarkvm-ledger-narwhal-batch-certificate" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "indexmap", "rayon", @@ -2982,7 +2982,7 @@ dependencies = [ [[package]] name = "snarkvm-ledger-narwhal-batch-header" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "indexmap", "rayon", @@ -2994,7 +2994,7 @@ dependencies = [ [[package]] name = "snarkvm-ledger-narwhal-data" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "bytes", "serde_json", @@ -3004,7 +3004,7 @@ dependencies = [ [[package]] name = "snarkvm-ledger-narwhal-subdag" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "indexmap", "rayon", @@ -3019,7 +3019,7 @@ dependencies = [ [[package]] name = "snarkvm-ledger-narwhal-transmission-id" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "snarkvm-console", "snarkvm-ledger-puzzle", @@ -3028,7 +3028,7 @@ dependencies = [ [[package]] name = "snarkvm-ledger-puzzle" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "aleo-std", "anyhow", @@ -3047,7 +3047,7 @@ dependencies = [ [[package]] name = "snarkvm-ledger-puzzle-epoch" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "aleo-std", "anyhow", @@ -3069,7 +3069,7 @@ dependencies = [ [[package]] name = "snarkvm-ledger-query" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "anyhow", "async-trait", @@ -3086,7 +3086,7 @@ dependencies = [ [[package]] name = "snarkvm-ledger-store" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "aleo-std-storage", "anyhow", @@ -3111,7 +3111,7 @@ dependencies = [ [[package]] name = "snarkvm-parameters" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "aleo-std", "anyhow", @@ -3136,7 +3136,7 @@ dependencies = [ [[package]] name = "snarkvm-synthesizer" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "aleo-std", "anyhow", @@ -3170,7 +3170,7 @@ dependencies = [ [[package]] name = "snarkvm-synthesizer-error" version = "4.4.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "anyhow", "snarkvm-circuit-environment", @@ -3181,7 +3181,7 @@ dependencies = [ [[package]] name = "snarkvm-synthesizer-process" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "aleo-std", "colored", @@ -3207,7 +3207,7 @@ dependencies = [ [[package]] name = "snarkvm-synthesizer-program" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "enum-iterator", "indexmap", @@ -3228,7 +3228,7 @@ dependencies = [ [[package]] name = "snarkvm-synthesizer-snark" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "bincode", "serde_json", @@ -3241,7 +3241,7 @@ dependencies = [ [[package]] name = "snarkvm-utilities" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "aleo-std", "anyhow", @@ -3264,7 +3264,7 @@ dependencies = [ [[package]] name = "snarkvm-utilities-derives" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "proc-macro2", "quote 1.0.42", @@ -3274,7 +3274,7 @@ dependencies = [ [[package]] name = "snarkvm-wasm" version = "4.5.0" -source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=7296b3cc9cffb8b71eb15dad3a00603d21a5912f#7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +source = "git+https://github.com/ProvableHQ/snarkVM.git?rev=b8e969bded035ca46b1e1e823731553e9d69231f#b8e969bded035ca46b1e1e823731553e9d69231f" dependencies = [ "getrandom 0.2.16", "snarkvm-console", diff --git a/wasm/Cargo.toml b/wasm/Cargo.toml index fd21f4db3..526f7c087 100644 --- a/wasm/Cargo.toml +++ b/wasm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aleo-wasm" -version = "0.9.17" +version = "0.9.18" authors = [ "The Provable Team" ] description = "WebAssembly based toolkit for developing zero-knowledge applications with Aleo" homepage = "https://provable.com" @@ -23,16 +23,16 @@ doctest = false [dependencies.snarkvm-algorithms] git = "https://github.com/ProvableHQ/snarkVM.git" -rev = "7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +rev = "b8e969bded035ca46b1e1e823731553e9d69231f" [dependencies.snarkvm-circuit-network] git = "https://github.com/ProvableHQ/snarkVM.git" -rev = "7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +rev = "b8e969bded035ca46b1e1e823731553e9d69231f" features = ["wasm"] [dependencies.snarkvm-console] git = "https://github.com/ProvableHQ/snarkVM.git" -rev = "7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +rev = "b8e969bded035ca46b1e1e823731553e9d69231f" default-features = false features = [ "account", @@ -46,37 +46,37 @@ features = [ [dependencies.snarkvm-ledger-block] git = "https://github.com/ProvableHQ/snarkVM.git" -rev = "7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +rev = "b8e969bded035ca46b1e1e823731553e9d69231f" features = ["wasm"] [dependencies.snarkvm-ledger-query] git = "https://github.com/ProvableHQ/snarkVM.git" -rev = "7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +rev = "b8e969bded035ca46b1e1e823731553e9d69231f" features = ["async", "wasm"] [dependencies.snarkvm-ledger-store] git = "https://github.com/ProvableHQ/snarkVM.git" -rev = "7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +rev = "b8e969bded035ca46b1e1e823731553e9d69231f" [dependencies.snarkvm-parameters] git = "https://github.com/ProvableHQ/snarkVM.git" -rev = "7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +rev = "b8e969bded035ca46b1e1e823731553e9d69231f" default-features = false features = ["wasm"] [dependencies.snarkvm-synthesizer-program] git = "https://github.com/ProvableHQ/snarkVM.git" -rev = "7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +rev = "b8e969bded035ca46b1e1e823731553e9d69231f" features = ["wasm"] [dependencies.snarkvm-synthesizer] git = "https://github.com/ProvableHQ/snarkVM.git" -rev = "7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +rev = "b8e969bded035ca46b1e1e823731553e9d69231f" features = ["async", "wasm"] [dependencies.snarkvm-wasm] git = "https://github.com/ProvableHQ/snarkVM.git" -rev = "7296b3cc9cffb8b71eb15dad3a00603d21a5912f" +rev = "b8e969bded035ca46b1e1e823731553e9d69231f" features = ["fields", "utilities"] [dependencies.anyhow] diff --git a/wasm/src/programs/manager/deploy.rs b/wasm/src/programs/manager/deploy.rs index fefb13c25..244453456 100644 --- a/wasm/src/programs/manager/deploy.rs +++ b/wasm/src/programs/manager/deploy.rs @@ -377,20 +377,6 @@ impl ProgramManager { log("Program must have at least one function"); Err("Program must have at least one function".to_string())?; } - let mut verifying_keys = Vec::with_capacity(program.functions().len()); - for function_name in program.functions().keys() { - let (verifying_key, certificate) = { - // Sample a dummy verifying key for each function. - let verifying_key = - VerifyingKeyNative::from_str(DEVNODE_VERIFIER_KEY).map_err(|err| err.to_string())?; - - // Sample a dummy certificate. - let certificate = CertificateNative::from_str(DEVNODE_CERTIFICATE).map_err(|err| err.to_string())?; - (verifying_key, certificate) - }; - verifying_keys.push((*function_name, (verifying_key, certificate))); - } - // Attempt to set the edition. let edition_response = latest_program_edition(node_url, &program_id.to_string()).await; let edition = match edition_response { @@ -398,13 +384,29 @@ impl ProgramManager { Err(_) => 0, }; + // Get the consensus version to determine which VKs are needed. + let latest_height = latest_block_height(node_url).await.map_err(|err| err.to_string())?; + let consensus_version = CurrentNetwork::CONSENSUS_VERSION(latest_height).map_err(|err| err.to_string())?; + + // Build dummy VKs for all functions. + let devnode_vk = VerifyingKeyNative::from_str(DEVNODE_VERIFIER_KEY).map_err(|err| err.to_string())?; + let devnode_cert = CertificateNative::from_str(DEVNODE_CERTIFICATE).map_err(|err| err.to_string())?; + let mut verifying_keys = Vec::with_capacity(program.functions().len() + program.records().len()); + for function_name in program.functions().keys() { + verifying_keys.push((*function_name, (devnode_vk.clone(), devnode_cert.clone()))); + } + // V14+: also include dummy VKs for records. + if consensus_version >= ConsensusVersion::V14 { + for record_name in program.records().keys() { + verifying_keys.push((*record_name, (devnode_vk.clone(), devnode_cert.clone()))); + } + } + // Construct the deployment without running all proofs. let mut deployment = DeploymentNative::new(edition, program.clone(), verifying_keys, None, None) .map_err(|err| err.to_string())?; // Set the program owner for programs above consensus version V8. - let latest_height = latest_block_height(node_url).await.map_err(|err| err.to_string())?; - let consensus_version = CurrentNetwork::CONSENSUS_VERSION(latest_height).map_err(|err| err.to_string())?; let private_key_native = PrivateKeyNative::from(private_key); if consensus_version < ConsensusVersion::V9 { deployment.set_program_checksum_raw(None); @@ -415,12 +417,6 @@ impl ProgramManager { .set_program_owner_raw(Some(AddressNative::try_from(private_key_native).map_err(|e| e.to_string())?)); } - // Before V14: remove record verifying keys from the deployment. - if consensus_version < ConsensusVersion::V14 { - let record_names: Vec<_> = deployment.program().records().keys().cloned().collect(); - deployment.remove_verifying_keys(&record_names); - } - // Construct the fee authorization for the deployment let (minimum_deployment_cost, _) = deployment_cost::<CurrentNetwork>(process, &deployment, consensus_version) .map_err(|err| err.to_string())?; @@ -457,14 +453,16 @@ impl ProgramManager { // Get the state root. let state_root = latest_stateroot(node_url).await.map_err(|e| e.to_string())?; - let fee = FeeNative::from(fee_authorization.transitions().into_iter().next().unwrap().1, state_root, None) - .map_err(|err| err.to_string())?; + let fee = FeeNative::from( + fee_authorization.transitions().into_iter().next().ok_or("No fee transition found".to_string())?.1, + state_root, + None, + ) + .map_err(|err| err.to_string())?; log("Creating deployment transaction"); Ok(Transaction::from( - TransactionNative::from_deployment(owner, deployment, fee) - .map_err(|err| err.to_string()) - .map_err(|e| e.to_string())?, + TransactionNative::from_deployment(owner, deployment, fee).map_err(|err| err.to_string())?, )) } @@ -519,23 +517,26 @@ impl ProgramManager { log("Adding deployed program to the process"); process.add_program_with_edition(&deployed_program, deployed_program_edition).map_err(|err| err.to_string())?; + // Get the consensus version to determine which VKs are needed. + let latest_height = latest_block_height(node_url).await.map_err(|err| err.to_string())?; + let consensus_version = CurrentNetwork::CONSENSUS_VERSION(latest_height).map_err(|err| err.to_string())?; + // Create deployment without synthesizing keys and generating certificates. if program.functions().is_empty() { log("Program must have at least one function"); Err("Program must have at least one function".to_string())?; } - let mut verifying_keys = Vec::with_capacity(program.functions().len()); + let devnode_vk = VerifyingKeyNative::from_str(DEVNODE_VERIFIER_KEY).map_err(|err| err.to_string())?; + let devnode_cert = CertificateNative::from_str(DEVNODE_CERTIFICATE).map_err(|err| err.to_string())?; + let mut verifying_keys = Vec::with_capacity(program.functions().len() + program.records().len()); for function_name in program.functions().keys() { - let (verifying_key, certificate) = { - // Sample a dummy verifying key for each function. - let verifying_key = - VerifyingKeyNative::from_str(DEVNODE_VERIFIER_KEY).map_err(|err| err.to_string())?; - - // Sample a dummy certificate. - let certificate = CertificateNative::from_str(DEVNODE_CERTIFICATE).map_err(|err| err.to_string())?; - (verifying_key, certificate) - }; - verifying_keys.push((*function_name, (verifying_key, certificate))); + verifying_keys.push((*function_name, (devnode_vk.clone(), devnode_cert.clone()))); + } + // V14+: also include dummy VKs for records. + if consensus_version >= ConsensusVersion::V14 { + for record_name in program.records().keys() { + verifying_keys.push((*record_name, (devnode_vk.clone(), devnode_cert.clone()))); + } } // Attempt to get the latest program edition. @@ -550,8 +551,6 @@ impl ProgramManager { .map_err(|err| err.to_string())?; // Set the program owner for programs above consensus version V8. - let latest_height = latest_block_height(node_url).await.map_err(|err| err.to_string())?; - let consensus_version = CurrentNetwork::CONSENSUS_VERSION(latest_height).map_err(|err| err.to_string())?; let private_key_native = PrivateKeyNative::from(private_key); if consensus_version < ConsensusVersion::V9 { deployment.set_program_checksum_raw(None); @@ -562,12 +561,6 @@ impl ProgramManager { .set_program_owner_raw(Some(AddressNative::try_from(private_key_native).map_err(|e| e.to_string())?)); } - // Before V14: remove record verifying keys from the deployment. - if consensus_version < ConsensusVersion::V14 { - let record_names: Vec<_> = deployment.program().records().keys().cloned().collect(); - deployment.remove_verifying_keys(&record_names); - } - // Construct the fee authorization for the deployment let (minimum_deployment_cost, _) = deployment_cost::<CurrentNetwork>(process, &deployment, consensus_version) .map_err(|err| err.to_string())?; @@ -604,14 +597,16 @@ impl ProgramManager { // Get the state root. let state_root = latest_stateroot(node_url).await.map_err(|e| e.to_string())?; - let fee = FeeNative::from(fee_authorization.transitions().into_iter().next().unwrap().1, state_root, None) - .map_err(|err| err.to_string())?; + let fee = FeeNative::from( + fee_authorization.transitions().into_iter().next().ok_or("No fee transition found".to_string())?.1, + state_root, + None, + ) + .map_err(|err| err.to_string())?; log("Creating deployment transaction"); Ok(Transaction::from( - TransactionNative::from_deployment(owner, deployment, fee) - .map_err(|err| err.to_string()) - .map_err(|e| e.to_string())?, + TransactionNative::from_deployment(owner, deployment, fee).map_err(|err| err.to_string())?, )) } } diff --git a/wasm/src/programs/manager/mod.rs b/wasm/src/programs/manager/mod.rs index 9b6900eea..cc4c955c4 100644 --- a/wasm/src/programs/manager/mod.rs +++ b/wasm/src/programs/manager/mod.rs @@ -25,7 +25,10 @@ mod transfer; pub const DEFAULT_URL: &str = "https://api.provable.com/v2"; pub const LOCAL_URL: &str = "http://localhost:3030"; -const DEVNODE_VERIFIER_KEY: &str = "verifier1qygqqqqqqqqqqqyvxgqqqqqqqqq87vsqqqqqqqqqhe7sqqqqqqqqqma4qqqqqqqqqq65yqqqqqqqqqqvqqqqqqqqqqqgtlaj49fmrk2d8slmselaj9tpucgxv6awu6yu4pfcn5xa0yy0tpxpc8wemasjvvxr9248vt3509vpk3u60ejyfd9xtvjmudpp7ljq2csk4yqz70ug3x8xp3xn3ul0yrrw0mvd2g8ju7rts50u3smue03gp99j88f0ky8h6fjlpvh58rmxv53mldmgrxa3fq6spsh8gt5whvsyu2rk4a2wmeyrgvvdf29pwp02srktxnvht3k6ff094usjtllggva2ym75xc4lzuqu9xx8ylfkm3qc7lf7ktk9uu9du5raukh828dzgq26hrarq5ajjl7pz7zk924kekjrp92r6jh9dpp05mxtuffwlmvew84dvnqrkre7lw29mkdzgdxwe7q8z0vnkv2vwwdraekw2va3plu7rkxhtnkuxvce0qkgxcxn5mtg9q2c3vxdf2r7jjse2g68dgvyh85q4mzfnvn07lletrpty3vypus00gfu9m47rzay4mh5w9f03z9zgzgzhkv0mupdqsk8naljqm9tc2qqzhf6yp3mnv2ey89xk7sw9pslzzlkndfd2upzmew4e4vnrkr556kexs9qrykkuhsr260mnrgh7uv0sp2meky0keeukaxgjdsnmy77kl48g3swcvqdjm50ejzr7x04vy7hn7anhd0xeetclxunnl7pd6e52qxdlr3nmutz4zr8f2xqa57a2zkl59a28w842cj4783zpy9hxw03k6vz4a3uu7sm072uqknpxjk8fyq4vxtqd08kd93c2mt40lj9ag35nm4rwcfjayejk57m9qqu83qnkrj3sz90pw808srmf705n2yu6gvqazpvu2mwm8x6mgtlsntxfhr0qas43rqxnccft36z4ygty86390t7vrt08derz8368z8ekn3yywxgp4uq24gm6e58tpp0lcvtpsm3nkwpnmzztx4qvkaf6vk38wg787h8mfpqqqqqqqqqqt49m8x"; +// Dummy verifying key for devnode deployments. Encodes num_public_inputs=64 to accommodate +// functions with many inputs/outputs (e.g. register_token needs 17). Generated by patching +// the original 16-input key's first field (bytes 1-8, u64 LE) to 64. +const DEVNODE_VERIFIER_KEY: &str = "verifier1q9qqqqqqqqqqqqyvxgqqqqqqqqq87vsqqqqqqqqqhe7sqqqqqqqqqma4qqqqqqqqqq65yqqqqqqqqqqvqqqqqqqqqqqgtlaj49fmrk2d8slmselaj9tpucgxv6awu6yu4pfcn5xa0yy0tpxpc8wemasjvvxr9248vt3509vpk3u60ejyfd9xtvjmudpp7ljq2csk4yqz70ug3x8xp3xn3ul0yrrw0mvd2g8ju7rts50u3smue03gp99j88f0ky8h6fjlpvh58rmxv53mldmgrxa3fq6spsh8gt5whvsyu2rk4a2wmeyrgvvdf29pwp02srktxnvht3k6ff094usjtllggva2ym75xc4lzuqu9xx8ylfkm3qc7lf7ktk9uu9du5raukh828dzgq26hrarq5ajjl7pz7zk924kekjrp92r6jh9dpp05mxtuffwlmvew84dvnqrkre7lw29mkdzgdxwe7q8z0vnkv2vwwdraekw2va3plu7rkxhtnkuxvce0qkgxcxn5mtg9q2c3vxdf2r7jjse2g68dgvyh85q4mzfnvn07lletrpty3vypus00gfu9m47rzay4mh5w9f03z9zgzgzhkv0mupdqsk8naljqm9tc2qqzhf6yp3mnv2ey89xk7sw9pslzzlkndfd2upzmew4e4vnrkr556kexs9qrykkuhsr260mnrgh7uv0sp2meky0keeukaxgjdsnmy77kl48g3swcvqdjm50ejzr7x04vy7hn7anhd0xeetclxunnl7pd6e52qxdlr3nmutz4zr8f2xqa57a2zkl59a28w842cj4783zpy9hxw03k6vz4a3uu7sm072uqknpxjk8fyq4vxtqd08kd93c2mt40lj9ag35nm4rwcfjayejk57m9qqu83qnkrj3sz90pw808srmf705n2yu6gvqazpvu2mwm8x6mgtlsntxfhr0qas43rqxnccft36z4ygty86390t7vrt08derz8368z8ekn3yywxgp4uq24gm6e58tpp0lcvtpsm3nkwpnmzztx4qvkaf6vk38wg787h8mfpqqqqqqqqqqffkful"; const DEVNODE_CERTIFICATE: &str = "certificate1qyqsqqqqqqqqqqxvwszp09v860w62s2l4g6eqf0kzppyax5we36957ywqm2dplzwvvlqg0kwlnmhzfatnax7uaqt7yqqqw0sc4u"; From 30249f00b36f854927d9860bed3ec1581fd4862b Mon Sep 17 00:00:00 2001 From: Cameron Marshall <cameron.marshall12@gmail.com> Date: Wed, 25 Feb 2026 23:53:40 -0500 Subject: [PATCH 09/17] feat: Add dynamic dispatch import resolution across all WASM entry points with comprehensive test coverage --- e2e/dynamic/index.js | 112 ++++++++++++++++ sdk/tests/data/dynamic-dispatch.ts | 62 +++++++++ sdk/tests/dynamic-dispatch.test.ts | 94 +++++++++++++ wasm/src/programs/manager/authorize.rs | 6 +- wasm/src/programs/manager/deploy.rs | 15 ++- wasm/src/programs/manager/execute.rs | 85 +++++++++++- wasm/src/programs/manager/mod.rs | 134 +++++++++++++++++++ wasm/src/programs/manager/proving_request.rs | 1 + 8 files changed, 496 insertions(+), 13 deletions(-) diff --git a/e2e/dynamic/index.js b/e2e/dynamic/index.js index fdb80832e..979313688 100644 --- a/e2e/dynamic/index.js +++ b/e2e/dynamic/index.js @@ -60,3 +60,115 @@ const start = Date.now(); console.log("Starting execute!"); await localProgramExecution(hello_hello_program, programName, "hello", ["5u32", "5u32"]); console.log("Execute finished!", Date.now() - start); + +// Dynamic dispatch execution tests + +const DD_CONSTANTS_PROGRAM = `program dd_constants.aleo; + +function get_value: + output 42u128 as u128.private; + +constructor: + assert.eq true true; +`; + +const DD_CALLER_PROGRAM = `program dd_caller.aleo; + +function call_and_increment: + input r0 as field.public; + input r1 as field.public; + input r2 as field.public; + call.dynamic r0 r1 r2 into r3 (as u128.private); + add r3 1u128 into r4; + output r4 as u128.public; + +constructor: + assert.eq true true; +`; + +const DD_TEN_PROGRAM = `program dd_ten.aleo; + +function get_ten: + output 10u128 as u128.private; + +constructor: + assert.eq true true; +`; + +const DD_MULTI_CALLER_PROGRAM = `program dd_multi_caller.aleo; + +function call_two_and_add: + input r0 as field.public; + input r1 as field.public; + input r2 as field.public; + input r3 as field.public; + input r4 as field.public; + call.dynamic r0 r1 r2 into r5 (as u128.private); + call.dynamic r3 r1 r4 into r6 (as u128.private); + add r5 r6 into r7; + output r7 as u128.public; + +constructor: + assert.eq true true; +`; + +const DD_CONSTANTS_FIELD = "35731532782568442653824738404field"; +const DD_ALEO_FIELD = "1868917857field"; +const DD_GET_VALUE_FIELD = "1871582396405622531431field"; +const DD_TEN_FIELD = "121382023160932field"; +const DD_GET_TEN_FIELD = "31073797930247527field"; + +async function dynamicDispatchSingleImport() { + const keyProvider = new mainnet.AleoKeyProvider(); + const programManager = new mainnet.ProgramManager(undefined, keyProvider); + programManager.setAccount(new mainnet.Account()); + + const imports = { "dd_constants.aleo": DD_CONSTANTS_PROGRAM }; + + const result = await programManager.run( + DD_CALLER_PROGRAM, + "call_and_increment", + [DD_CONSTANTS_FIELD, DD_ALEO_FIELD, DD_GET_VALUE_FIELD], + false, + imports, + ); + + const outputs = result.getOutputs(); + if (outputs.length !== 1 || outputs[0] !== "43u128") { + throw new Error(`Single-import dynamic dispatch failed: expected ["43u128"], got ${JSON.stringify(outputs)}`); + } +} + +async function dynamicDispatchMultiImport() { + const keyProvider = new mainnet.AleoKeyProvider(); + const programManager = new mainnet.ProgramManager(undefined, keyProvider); + programManager.setAccount(new mainnet.Account()); + + const imports = { + "dd_constants.aleo": DD_CONSTANTS_PROGRAM, + "dd_ten.aleo": DD_TEN_PROGRAM, + }; + + const result = await programManager.run( + DD_MULTI_CALLER_PROGRAM, + "call_two_and_add", + [DD_CONSTANTS_FIELD, DD_ALEO_FIELD, DD_GET_VALUE_FIELD, DD_TEN_FIELD, DD_GET_TEN_FIELD], + false, + imports, + ); + + const outputs = result.getOutputs(); + if (outputs.length !== 1 || outputs[0] !== "52u128") { + throw new Error(`Multi-import dynamic dispatch failed: expected ["52u128"], got ${JSON.stringify(outputs)}`); + } +} + +console.log("Starting dynamic dispatch single-import execution!"); +let ddStart = Date.now(); +await dynamicDispatchSingleImport(); +console.log("Dynamic dispatch single-import execution passed!", Date.now() - ddStart); + +console.log("Starting dynamic dispatch multi-import execution!"); +ddStart = Date.now(); +await dynamicDispatchMultiImport(); +console.log("Dynamic dispatch multi-import execution passed!", Date.now() - ddStart); diff --git a/sdk/tests/data/dynamic-dispatch.ts b/sdk/tests/data/dynamic-dispatch.ts index c7d02af02..0bd17b678 100644 --- a/sdk/tests/data/dynamic-dispatch.ts +++ b/sdk/tests/data/dynamic-dispatch.ts @@ -1,3 +1,65 @@ +/// A simple program that returns a constant value. Used as a dynamic dispatch target. +export const DD_CONSTANTS_PROGRAM = `program dd_constants.aleo; + +function get_value: + output 42u128 as u128.private; + +constructor: + assert.eq true true; +`; + +/// A program that calls another program via call.dynamic. +export const DD_CALLER_PROGRAM = `program dd_caller.aleo; + +function call_and_increment: + input r0 as field.public; + input r1 as field.public; + input r2 as field.public; + call.dynamic r0 r1 r2 into r3 (as u128.private); + add r3 1u128 into r4; + output r4 as u128.public; + +constructor: + assert.eq true true; +`; + +/// A second simple program that returns a constant value. Used as a second dynamic dispatch target. +export const DD_TEN_PROGRAM = `program dd_ten.aleo; + +function get_ten: + output 10u128 as u128.private; + +constructor: + assert.eq true true; +`; + +/// A program that calls TWO different programs via call.dynamic and adds the results. +/// Calls dd_constants/get_value -> 42u128, then dd_ten/get_ten -> 10u128, returns 52u128. +export const DD_MULTI_CALLER_PROGRAM = `program dd_multi_caller.aleo; + +function call_two_and_add: + input r0 as field.public; + input r1 as field.public; + input r2 as field.public; + input r3 as field.public; + input r4 as field.public; + call.dynamic r0 r1 r2 into r5 (as u128.private); + call.dynamic r3 r1 r4 into r6 (as u128.private); + add r5 r6 into r7; + output r7 as u128.public; + +constructor: + assert.eq true true; +`; + +/// Pre-computed field-encoded identifiers for dynamic dispatch. +/// These are Identifier::to_field() values computed from the Rust/WASM tests. +export const DD_CONSTANTS_FIELD = "35731532782568442653824738404field"; +export const DD_ALEO_FIELD = "1868917857field"; +export const DD_GET_VALUE_FIELD = "1871582396405622531431field"; +export const DD_TEN_FIELD = "121382023160932field"; +export const DD_GET_TEN_FIELD = "31073797930247527field"; + /// Fixture 1: dynamic_transfer_pub_to_priv /// Covers: record_with_dynamic_id (output), record_dynamic (output) /// diff --git a/sdk/tests/dynamic-dispatch.test.ts b/sdk/tests/dynamic-dispatch.test.ts index 022231a67..db99c3ffb 100644 --- a/sdk/tests/dynamic-dispatch.test.ts +++ b/sdk/tests/dynamic-dispatch.test.ts @@ -1,5 +1,10 @@ import { expect } from "chai"; import { + Account, + AleoKeyProvider, + Authorization, + ProgramManager, + ProvingRequest, Transaction, TransactionObject, InputObject, @@ -8,6 +13,11 @@ import { TransitionObject, } from "@provablehq/sdk/%%NETWORK%%.js"; import { + DD_CALLER_PROGRAM, + DD_CONSTANTS_PROGRAM, + DD_CONSTANTS_FIELD, + DD_ALEO_FIELD, + DD_GET_VALUE_FIELD, FIXTURE_DYNAMIC_TRANSFER_PUB_TO_PRIV, FIXTURE_DYNAMIC_TRANSFER_PRIVATE, FIXTURE_EXTERNAL_RECORD_DYNAMIC, @@ -299,4 +309,88 @@ describe("Dynamic Dispatch", () => { expect(outputs[0].value).equal(BigInt(100)); }); }); + + describe("Dynamic Dispatch Authorization", () => { + let programManager: ProgramManager; + const imports = { + "dd_constants.aleo": DD_CONSTANTS_PROGRAM, + }; + + before(() => { + const keyProvider = new AleoKeyProvider(); + programManager = new ProgramManager( + "https://api.provable.com/v2", + keyProvider, + ); + programManager.setAccount(new Account()); + }); + + it("should build an authorization for a call.dynamic program via buildAuthorization", async () => { + const authorization = await programManager.buildAuthorization({ + programName: "dd_caller.aleo", + functionName: "call_and_increment", + inputs: [DD_CONSTANTS_FIELD, DD_ALEO_FIELD, DD_GET_VALUE_FIELD], + programSource: DD_CALLER_PROGRAM, + programImports: imports, + edition: 0, + }); + + // The authorization should have 2 transitions: dd_constants/get_value + dd_caller/call_and_increment. + expect(authorization.transitions().length).equal(2); + + // Verify serialization round-trips. + const fromString = Authorization.fromString(authorization.toString()); + const fromBytes = Authorization.fromBytesLe(authorization.toBytesLe()); + expect(fromString.equals(authorization)).equal(true); + expect(fromBytes.equals(authorization)).equal(true); + }); + + it("should build an unchecked authorization for a call.dynamic program via buildAuthorizationUnchecked", async () => { + const authorization = await programManager.buildAuthorizationUnchecked({ + programName: "dd_caller.aleo", + functionName: "call_and_increment", + inputs: [DD_CONSTANTS_FIELD, DD_ALEO_FIELD, DD_GET_VALUE_FIELD], + programSource: DD_CALLER_PROGRAM, + programImports: imports, + edition: 0, + }); + + // The authorization should have 2 transitions: dd_constants/get_value + dd_caller/call_and_increment. + expect(authorization.transitions().length).equal(2); + + // Verify serialization round-trips. + const fromString = Authorization.fromString(authorization.toString()); + const fromBytes = Authorization.fromBytesLe(authorization.toBytesLe()); + expect(fromString.equals(authorization)).equal(true); + expect(fromBytes.equals(authorization)).equal(true); + }); + + it("should build a proving request for a call.dynamic program via provingRequest", async () => { + const provingRequest = await programManager.provingRequest({ + programName: "dd_caller.aleo", + functionName: "call_and_increment", + priorityFee: 0, + privateFee: false, + inputs: [DD_CONSTANTS_FIELD, DD_ALEO_FIELD, DD_GET_VALUE_FIELD], + programSource: DD_CALLER_PROGRAM, + programImports: imports, + edition: 0, + broadcast: false, + useFeeMaster: true, + }); + + // The proving request should have an authorization with 2 transitions. + const authorization = provingRequest.authorization(); + expect(authorization.transitions().length).equal(2); + + // No fee authorization when useFeeMaster is true. + expect(provingRequest.feeAuthorization()).equal(undefined); + + // Verify serialization round-trips. + const fromString = ProvingRequest.fromString(provingRequest.toString()); + const fromBytes = ProvingRequest.fromBytesLe(provingRequest.toBytesLe()); + expect(fromString.equals(provingRequest)).equal(true); + expect(fromBytes.equals(provingRequest)).equal(true); + }); + }); }); diff --git a/wasm/src/programs/manager/authorize.rs b/wasm/src/programs/manager/authorize.rs index 18e11046b..4aec4977c 100644 --- a/wasm/src/programs/manager/authorize.rs +++ b/wasm/src/programs/manager/authorize.rs @@ -55,7 +55,8 @@ impl ProgramManager { log("Check program imports are valid and add them to the process"); let program_native = ProgramNative::from_str(program).map_err(|e| e.to_string())?; log(&format!("Creating proving request for {}:{function_name}", program_native.id())); - ProgramManager::resolve_imports(process, &program_native, imports)?; + ProgramManager::resolve_imports(process, &program_native, imports.clone())?; + ProgramManager::resolve_dynamic_imports(process, imports)?; let rng = &mut StdRng::from_entropy(); // Authorize the main program. @@ -97,7 +98,8 @@ impl ProgramManager { log("Check program imports are valid and add them to the process"); let program_native = ProgramNative::from_str(program).map_err(|e| e.to_string())?; log(&format!("Creating proving request for {}:{function_name}", program_native.id())); - ProgramManager::resolve_imports(process, &program_native, imports)?; + ProgramManager::resolve_imports(process, &program_native, imports.clone())?; + ProgramManager::resolve_dynamic_imports(process, imports)?; let rng = &mut StdRng::from_entropy(); // Authorize the main program. diff --git a/wasm/src/programs/manager/deploy.rs b/wasm/src/programs/manager/deploy.rs index 244453456..a512f934a 100644 --- a/wasm/src/programs/manager/deploy.rs +++ b/wasm/src/programs/manager/deploy.rs @@ -90,7 +90,8 @@ impl ProgramManager { let program = ProgramNative::from_str(program).map_err(|err| err.to_string())?; log("Checking program imports are valid and add them to the process"); - ProgramManager::resolve_imports(process, &program, imports)?; + ProgramManager::resolve_imports(process, &program, imports.clone())?; + ProgramManager::resolve_dynamic_imports(process, imports)?; let rng = &mut StdRng::from_entropy(); log("Creating deployment"); @@ -175,7 +176,8 @@ impl ProgramManager { let program = ProgramNative::from_str(program).map_err(|err| err.to_string())?; log("Check program imports are valid and add them to the process"); - ProgramManager::resolve_imports(process, &program, imports)?; + ProgramManager::resolve_imports(process, &program, imports.clone())?; + ProgramManager::resolve_dynamic_imports(process, imports)?; log("Create sample deployment"); let mut deployment = @@ -269,7 +271,8 @@ impl ProgramManager { process.add_program_with_edition(&deployed_program, deployed_program_edition).map_err(|err| err.to_string())?; log("Checking program imports are valid and add them to the process"); - ProgramManager::resolve_imports(process, &program, imports)?; + ProgramManager::resolve_imports(process, &program, imports.clone())?; + ProgramManager::resolve_dynamic_imports(process, imports)?; let rng = &mut StdRng::from_entropy(); log("Creating deployment"); @@ -366,7 +369,8 @@ impl ProgramManager { let program_id = program.id(); log("Checking program imports are valid and add them to the process"); - ProgramManager::resolve_imports(process, &program, imports)?; + ProgramManager::resolve_imports(process, &program, imports.clone())?; + ProgramManager::resolve_dynamic_imports(process, imports)?; let rng = &mut StdRng::from_entropy(); log("Creating deployment"); @@ -500,7 +504,8 @@ impl ProgramManager { log("Checking program imports are valid and add them to the process"); let program = ProgramNative::from_str(&program).map_err(|err| err.to_string())?; - ProgramManager::resolve_imports(process, &program, imports)?; + ProgramManager::resolve_imports(process, &program, imports.clone())?; + ProgramManager::resolve_dynamic_imports(process, imports)?; let rng = &mut StdRng::from_entropy(); log("Creating deployment"); diff --git a/wasm/src/programs/manager/execute.rs b/wasm/src/programs/manager/execute.rs index b11729b00..bb97ab064 100644 --- a/wasm/src/programs/manager/execute.rs +++ b/wasm/src/programs/manager/execute.rs @@ -106,7 +106,8 @@ impl ProgramManager { log("Check program imports are valid and add them to the process"); let program_native = ProgramNative::from_str(program).map_err(|e| e.to_string())?; - ProgramManager::resolve_imports(process, &program_native, imports)?; + ProgramManager::resolve_imports(process, &program_native, imports.clone())?; + ProgramManager::resolve_dynamic_imports(process, imports)?; let edition = edition.unwrap_or(1); let (response, mut trace) = execute_program!( @@ -205,7 +206,8 @@ impl ProgramManager { log("Check program imports are valid and add them to the process"); let program_native = ProgramNative::from_str(program).map_err(|e| e.to_string())?; let program_id = program_native.id().to_string(); - ProgramManager::resolve_imports(process, &program_native, imports)?; + ProgramManager::resolve_imports(process, &program_native, imports.clone())?; + ProgramManager::resolve_dynamic_imports(process, imports)?; let rng = &mut StdRng::from_entropy(); log(&format!("Executing function: {program_id}/{function} on-chain")); @@ -340,7 +342,8 @@ impl ProgramManager { let program_id = program_native.id().to_string(); // Insert the program and its imports. - ProgramManager::resolve_imports(process, &program_native, imports)?; + ProgramManager::resolve_imports(process, &program_native, imports.clone())?; + ProgramManager::resolve_dynamic_imports(process, imports)?; if program_id != "credits.aleo" && !process.contains_program(program_native.id()) { process.add_program(&program_native).map_err(|e| e.to_string())?; } @@ -513,7 +516,8 @@ impl ProgramManager { log("Check program imports are valid and add them to the process"); let program_native = ProgramNative::from_str(program).map_err(|e| e.to_string())?; let program_id = program_native.id().to_string(); - ProgramManager::resolve_imports(process, &program_native, imports)?; + ProgramManager::resolve_imports(process, &program_native, imports.clone())?; + ProgramManager::resolve_dynamic_imports(process, imports)?; let edition = edition.unwrap_or(1); let inputs = process_inputs!(inputs); @@ -624,7 +628,8 @@ impl ProgramManager { .map_err(|e| e.to_string())?; // Resolve program imports. - ProgramManager::resolve_imports(process, &program_native, imports)?; + ProgramManager::resolve_imports(process, &program_native, imports.clone())?; + ProgramManager::resolve_dynamic_imports(process, imports)?; // Add the program to the process. let program_id = program_native.id(); @@ -697,7 +702,8 @@ impl ProgramManager { let program_native = ProgramNative::from_str(program).map_err(|e| e.to_string())?; // Resolve program imports. - ProgramManager::resolve_imports(process, &program_native, imports)?; + ProgramManager::resolve_imports(process, &program_native, imports.clone())?; + ProgramManager::resolve_dynamic_imports(process, imports)?; // Add the program to the process. let program_id = program_native.id(); @@ -763,4 +769,71 @@ mod tests { assert_eq!(authorization_estimate, cost); } } + + /// Test that a program using `call.dynamic` can be authorized after + /// `resolve_dynamic_imports` loads the target into the process. + /// + /// This validates: + /// 1. `resolve_dynamic_imports` loads the target program into the process + /// 2. The caller program with `call.dynamic` can be added to the process + /// 3. `process.authorize()` succeeds for the dynamic dispatch caller + /// + /// Note: Full execution (process.execute) is tested at the TS/SDK level because + /// WASM tests run via `wasm-pack test --node` which lacks XMLHttpRequest, + /// preventing the universal SRS download needed when `call.dynamic` synthesizes + /// the target program's circuit at runtime. + #[wasm_bindgen_test] + pub async fn test_authorize_dynamic_dispatch() { + use crate::{ + PrivateKey, + programs::manager::tests::{DD_CALLER_PROGRAM, DD_CONSTANTS_PROGRAM}, + types::native::{CurrentAleo, IdentifierNative, ProcessNative, ProgramNative}, + }; + use js_sys::{Object, Reflect}; + use rand::SeedableRng; + use snarkvm_console::program::ToField; + use std::str::FromStr; + use wasm_bindgen::JsValue; + + // Compute field-encoded identifiers for the dynamic call target. + let dd_constants_field = IdentifierNative::from_str("dd_constants").unwrap().to_field().unwrap(); + let aleo_field = IdentifierNative::from_str("aleo").unwrap().to_field().unwrap(); + let get_value_field = IdentifierNative::from_str("get_value").unwrap().to_field().unwrap(); + + // Set up the process with both programs loaded. + let mut process = ProcessNative::load_web().unwrap(); + let caller_program = ProgramNative::from_str(DD_CALLER_PROGRAM).unwrap(); + let constants_program = ProgramNative::from_str(DD_CONSTANTS_PROGRAM).unwrap(); + + // Build imports object and resolve dynamic imports. + let imports = Object::new(); + Reflect::set(&imports, &JsValue::from_str("dd_constants.aleo"), &JsValue::from_str(DD_CONSTANTS_PROGRAM)) + .unwrap(); + ProgramManager::resolve_imports(&mut process, &caller_program, Some(imports.clone())).unwrap(); + ProgramManager::resolve_dynamic_imports(&mut process, Some(imports)).unwrap(); + + // Add the caller program to the process. + process.add_program_with_edition(&caller_program, 1).unwrap(); + + // Verify both programs are in the process. + assert!(process.contains_program(constants_program.id()), "dd_constants.aleo should be in the process"); + assert!(process.contains_program(caller_program.id()), "dd_caller.aleo should be in the process"); + + // Build the inputs for authorization. + // Field::fmt() already includes the "field" suffix. + let inputs = vec![format!("{dd_constants_field}"), format!("{aleo_field}"), format!("{get_value_field}")]; + let private_key = PrivateKey::new(); + let rng = &mut rand::rngs::StdRng::from_entropy(); + + // Authorize should succeed — this validates that the process can construct + // an authorization for a program that uses call.dynamic. + let authorization = process + .authorize::<CurrentAleo, _>(&private_key, caller_program.id(), "call_and_increment", inputs.iter(), rng) + .expect("Authorization should succeed for dynamic dispatch caller"); + + // Verify the authorization has the expected structure. + let request = authorization.peek_next().unwrap(); + assert_eq!(request.program_id().to_string(), "dd_caller.aleo"); + assert_eq!(request.function_name().to_string(), "call_and_increment"); + } } diff --git a/wasm/src/programs/manager/mod.rs b/wasm/src/programs/manager/mod.rs index cc4c955c4..5ecfb97a5 100644 --- a/wasm/src/programs/manager/mod.rs +++ b/wasm/src/programs/manager/mod.rs @@ -161,6 +161,32 @@ impl ProgramManager { } } + /// Resolve dynamic dispatch imports — loads ALL programs from the imports object + /// that are not already in the process. This handles programs called via `call.dynamic` + /// which are not declared in the caller's static `import` block. + pub(crate) fn resolve_dynamic_imports(process: &mut ProcessNative, imports: Option<Object>) -> Result<(), String> { + if let Some(imports) = imports { + let keys = Object::keys(&imports); + for i in 0..keys.length() { + let key = keys.get(i).as_string().unwrap_or_default(); + if key == "credits.aleo" { + continue; + } + if let Some(source) = Reflect::get(&imports, &key.as_str().into()).ok().and_then(|v| v.as_string()) { + let program = ProgramNative::from_str(&source).map_err(|e| e.to_string())?; + if !process.contains_program(program.id()) { + log(&format!("Loading dynamic dispatch target: {key}")); + // Resolve this program's own static imports first. + Self::resolve_imports(process, &program, Some(imports.clone()))?; + log(&format!("Adding dynamic target {key} to the process")); + process.add_program_with_edition(&program, 1).map_err(|e| e.to_string())?; + } + } + } + } + Ok(()) + } + pub(crate) fn validate_fee_record( fee_record: &Option<RecordPlaintext>, minimum_execution_cost: u64, @@ -267,4 +293,112 @@ function add_and_double: let key = ProvingKey::from_bytes(&bytes).unwrap(); ProgramManager::load_inclusion_prover(key); } + + /// A simple program that returns a constant value. Used as a dynamic dispatch target. + pub const DD_CONSTANTS_PROGRAM: &str = r#"program dd_constants.aleo; + +function get_value: + output 42u128 as u128.private; + +constructor: + assert.eq true true; +"#; + + /// A program that calls another program via `call.dynamic`. The first three inputs + /// are field-encoded identifiers for (program_name, network, function_name). + /// It calls the target, adds 1 to the result, and returns it. + pub const DD_CALLER_PROGRAM: &str = r#"program dd_caller.aleo; + +function call_and_increment: + input r0 as field.public; + input r1 as field.public; + input r2 as field.public; + call.dynamic r0 r1 r2 into r3 (as u128.private); + add r3 1u128 into r4; + output r4 as u128.public; + +constructor: + assert.eq true true; +"#; + + /// A second simple program that returns a constant value. Used as a second dynamic dispatch target. + pub const DD_TEN_PROGRAM: &str = r#"program dd_ten.aleo; + +function get_ten: + output 10u128 as u128.private; + +constructor: + assert.eq true true; +"#; + + /// A program that calls TWO different programs via `call.dynamic` and adds the results. + /// Calls dd_constants/get_value → 42u128, then dd_ten/get_ten → 10u128, returns 52u128. + pub const DD_MULTI_CALLER_PROGRAM: &str = r#"program dd_multi_caller.aleo; + +function call_two_and_add: + input r0 as field.public; + input r1 as field.public; + input r2 as field.public; + input r3 as field.public; + input r4 as field.public; + call.dynamic r0 r1 r2 into r5 (as u128.private); + call.dynamic r3 r1 r4 into r6 (as u128.private); + add r5 r6 into r7; + output r7 as u128.public; + +constructor: + assert.eq true true; +"#; + + /// Test that resolve_dynamic_imports loads non-statically-imported programs + /// into the process, and that resolve_imports alone does NOT. + #[wasm_bindgen_test] + fn test_resolve_dynamic_imports() { + let dd_constants = ProgramNative::from_str(DD_CONSTANTS_PROGRAM).unwrap(); + let dd_caller = ProgramNative::from_str(DD_CALLER_PROGRAM).unwrap(); + + // Build imports object containing the dynamic target. + let imports = Object::new(); + Reflect::set(&imports, &JsValue::from_str("dd_constants.aleo"), &JsValue::from_str(DD_CONSTANTS_PROGRAM)) + .unwrap(); + + // resolve_imports alone should NOT load dd_constants (it's not a static import of dd_caller). + let mut process = ProcessNative::load_web().unwrap(); + ProgramManager::resolve_imports(&mut process, &dd_caller, Some(imports.clone())).unwrap(); + assert!( + !process.contains_program(dd_constants.id()), + "resolve_imports should not load dynamically-dispatched programs" + ); + + // resolve_dynamic_imports should load it. + ProgramManager::resolve_dynamic_imports(&mut process, Some(imports)).unwrap(); + assert!(process.contains_program(dd_constants.id()), "resolve_dynamic_imports should load dd_constants.aleo"); + } + + /// Test that resolve_dynamic_imports correctly loads multiple dynamic targets + /// from a single imports object. + #[wasm_bindgen_test] + fn test_resolve_multiple_dynamic_imports() { + let dd_constants = ProgramNative::from_str(DD_CONSTANTS_PROGRAM).unwrap(); + let dd_ten = ProgramNative::from_str(DD_TEN_PROGRAM).unwrap(); + let dd_multi_caller = ProgramNative::from_str(DD_MULTI_CALLER_PROGRAM).unwrap(); + + // Build imports object containing both dynamic targets. + let imports = Object::new(); + Reflect::set(&imports, &JsValue::from_str("dd_constants.aleo"), &JsValue::from_str(DD_CONSTANTS_PROGRAM)) + .unwrap(); + Reflect::set(&imports, &JsValue::from_str("dd_ten.aleo"), &JsValue::from_str(DD_TEN_PROGRAM)).unwrap(); + + let mut process = ProcessNative::load_web().unwrap(); + + // resolve_imports should not load either (they are not static imports of dd_multi_caller). + ProgramManager::resolve_imports(&mut process, &dd_multi_caller, Some(imports.clone())).unwrap(); + assert!(!process.contains_program(dd_constants.id())); + assert!(!process.contains_program(dd_ten.id())); + + // resolve_dynamic_imports should load both. + ProgramManager::resolve_dynamic_imports(&mut process, Some(imports)).unwrap(); + assert!(process.contains_program(dd_constants.id()), "resolve_dynamic_imports should load dd_constants.aleo"); + assert!(process.contains_program(dd_ten.id()), "resolve_dynamic_imports should load dd_ten.aleo"); + } } diff --git a/wasm/src/programs/manager/proving_request.rs b/wasm/src/programs/manager/proving_request.rs index ccaad79ef..3dc69e052 100644 --- a/wasm/src/programs/manager/proving_request.rs +++ b/wasm/src/programs/manager/proving_request.rs @@ -71,6 +71,7 @@ impl ProgramManager { log("Check program imports are valid and add them to the process"); let program_native = ProgramNative::from_str(program).map_err(|e| e.to_string())?; ProgramManager::resolve_imports(process, &program_native, imports.clone())?; + ProgramManager::resolve_dynamic_imports(process, imports.clone())?; let rng = &mut StdRng::from_entropy(); // Convert the fee to microcredits. From 180498b0ef0a56861486d8a6175de0d74d92771e Mon Sep 17 00:00:00 2001 From: Cameron Marshall <cameron.marshall12@gmail.com> Date: Tue, 3 Mar 2026 12:41:04 -0500 Subject: [PATCH 10/17] refactor: Unify resolve_imports to handle both static and dynamic dispatch imports in a single pass --- wasm/src/programs/execution.rs | 2 +- wasm/src/programs/manager/authorize.rs | 6 +- wasm/src/programs/manager/deploy.rs | 15 +-- wasm/src/programs/manager/execute.rs | 27 ++--- wasm/src/programs/manager/mod.rs | 114 ++++++++----------- wasm/src/programs/manager/proving_request.rs | 3 +- 6 files changed, 65 insertions(+), 102 deletions(-) diff --git a/wasm/src/programs/execution.rs b/wasm/src/programs/execution.rs index 35b474c08..07b2c1813 100644 --- a/wasm/src/programs/execution.rs +++ b/wasm/src/programs/execution.rs @@ -131,7 +131,7 @@ pub fn verify_function_execution( let program_native = ProgramNative::from(program); // First resolve the program's imports. - ProgramManager::resolve_imports(&mut process, program, imports)?; + ProgramManager::resolve_imports(&mut process, imports)?; // Secondly, get the verifying keys and insert them into the process object. if let Some(imported_verifying_keys) = imported_verifying_keys { diff --git a/wasm/src/programs/manager/authorize.rs b/wasm/src/programs/manager/authorize.rs index 4aec4977c..387871ac1 100644 --- a/wasm/src/programs/manager/authorize.rs +++ b/wasm/src/programs/manager/authorize.rs @@ -55,8 +55,7 @@ impl ProgramManager { log("Check program imports are valid and add them to the process"); let program_native = ProgramNative::from_str(program).map_err(|e| e.to_string())?; log(&format!("Creating proving request for {}:{function_name}", program_native.id())); - ProgramManager::resolve_imports(process, &program_native, imports.clone())?; - ProgramManager::resolve_dynamic_imports(process, imports)?; + ProgramManager::resolve_imports(process, imports)?; let rng = &mut StdRng::from_entropy(); // Authorize the main program. @@ -98,8 +97,7 @@ impl ProgramManager { log("Check program imports are valid and add them to the process"); let program_native = ProgramNative::from_str(program).map_err(|e| e.to_string())?; log(&format!("Creating proving request for {}:{function_name}", program_native.id())); - ProgramManager::resolve_imports(process, &program_native, imports.clone())?; - ProgramManager::resolve_dynamic_imports(process, imports)?; + ProgramManager::resolve_imports(process, imports)?; let rng = &mut StdRng::from_entropy(); // Authorize the main program. diff --git a/wasm/src/programs/manager/deploy.rs b/wasm/src/programs/manager/deploy.rs index a512f934a..19926f525 100644 --- a/wasm/src/programs/manager/deploy.rs +++ b/wasm/src/programs/manager/deploy.rs @@ -90,8 +90,7 @@ impl ProgramManager { let program = ProgramNative::from_str(program).map_err(|err| err.to_string())?; log("Checking program imports are valid and add them to the process"); - ProgramManager::resolve_imports(process, &program, imports.clone())?; - ProgramManager::resolve_dynamic_imports(process, imports)?; + ProgramManager::resolve_imports(process, imports)?; let rng = &mut StdRng::from_entropy(); log("Creating deployment"); @@ -176,8 +175,7 @@ impl ProgramManager { let program = ProgramNative::from_str(program).map_err(|err| err.to_string())?; log("Check program imports are valid and add them to the process"); - ProgramManager::resolve_imports(process, &program, imports.clone())?; - ProgramManager::resolve_dynamic_imports(process, imports)?; + ProgramManager::resolve_imports(process, imports)?; log("Create sample deployment"); let mut deployment = @@ -271,8 +269,7 @@ impl ProgramManager { process.add_program_with_edition(&deployed_program, deployed_program_edition).map_err(|err| err.to_string())?; log("Checking program imports are valid and add them to the process"); - ProgramManager::resolve_imports(process, &program, imports.clone())?; - ProgramManager::resolve_dynamic_imports(process, imports)?; + ProgramManager::resolve_imports(process, imports)?; let rng = &mut StdRng::from_entropy(); log("Creating deployment"); @@ -369,8 +366,7 @@ impl ProgramManager { let program_id = program.id(); log("Checking program imports are valid and add them to the process"); - ProgramManager::resolve_imports(process, &program, imports.clone())?; - ProgramManager::resolve_dynamic_imports(process, imports)?; + ProgramManager::resolve_imports(process, imports)?; let rng = &mut StdRng::from_entropy(); log("Creating deployment"); @@ -504,8 +500,7 @@ impl ProgramManager { log("Checking program imports are valid and add them to the process"); let program = ProgramNative::from_str(&program).map_err(|err| err.to_string())?; - ProgramManager::resolve_imports(process, &program, imports.clone())?; - ProgramManager::resolve_dynamic_imports(process, imports)?; + ProgramManager::resolve_imports(process, imports)?; let rng = &mut StdRng::from_entropy(); log("Creating deployment"); diff --git a/wasm/src/programs/manager/execute.rs b/wasm/src/programs/manager/execute.rs index bb97ab064..ed56d1243 100644 --- a/wasm/src/programs/manager/execute.rs +++ b/wasm/src/programs/manager/execute.rs @@ -106,8 +106,7 @@ impl ProgramManager { log("Check program imports are valid and add them to the process"); let program_native = ProgramNative::from_str(program).map_err(|e| e.to_string())?; - ProgramManager::resolve_imports(process, &program_native, imports.clone())?; - ProgramManager::resolve_dynamic_imports(process, imports)?; + ProgramManager::resolve_imports(process, imports)?; let edition = edition.unwrap_or(1); let (response, mut trace) = execute_program!( @@ -206,8 +205,7 @@ impl ProgramManager { log("Check program imports are valid and add them to the process"); let program_native = ProgramNative::from_str(program).map_err(|e| e.to_string())?; let program_id = program_native.id().to_string(); - ProgramManager::resolve_imports(process, &program_native, imports.clone())?; - ProgramManager::resolve_dynamic_imports(process, imports)?; + ProgramManager::resolve_imports(process, imports)?; let rng = &mut StdRng::from_entropy(); log(&format!("Executing function: {program_id}/{function} on-chain")); @@ -342,8 +340,7 @@ impl ProgramManager { let program_id = program_native.id().to_string(); // Insert the program and its imports. - ProgramManager::resolve_imports(process, &program_native, imports.clone())?; - ProgramManager::resolve_dynamic_imports(process, imports)?; + ProgramManager::resolve_imports(process, imports)?; if program_id != "credits.aleo" && !process.contains_program(program_native.id()) { process.add_program(&program_native).map_err(|e| e.to_string())?; } @@ -516,8 +513,7 @@ impl ProgramManager { log("Check program imports are valid and add them to the process"); let program_native = ProgramNative::from_str(program).map_err(|e| e.to_string())?; let program_id = program_native.id().to_string(); - ProgramManager::resolve_imports(process, &program_native, imports.clone())?; - ProgramManager::resolve_dynamic_imports(process, imports)?; + ProgramManager::resolve_imports(process, imports)?; let edition = edition.unwrap_or(1); let inputs = process_inputs!(inputs); @@ -628,8 +624,7 @@ impl ProgramManager { .map_err(|e| e.to_string())?; // Resolve program imports. - ProgramManager::resolve_imports(process, &program_native, imports.clone())?; - ProgramManager::resolve_dynamic_imports(process, imports)?; + ProgramManager::resolve_imports(process, imports)?; // Add the program to the process. let program_id = program_native.id(); @@ -702,8 +697,7 @@ impl ProgramManager { let program_native = ProgramNative::from_str(program).map_err(|e| e.to_string())?; // Resolve program imports. - ProgramManager::resolve_imports(process, &program_native, imports.clone())?; - ProgramManager::resolve_dynamic_imports(process, imports)?; + ProgramManager::resolve_imports(process, imports)?; // Add the program to the process. let program_id = program_native.id(); @@ -771,10 +765,10 @@ mod tests { } /// Test that a program using `call.dynamic` can be authorized after - /// `resolve_dynamic_imports` loads the target into the process. + /// `resolve_imports` loads the target into the process. /// /// This validates: - /// 1. `resolve_dynamic_imports` loads the target program into the process + /// 1. `resolve_imports` loads the dynamic dispatch target program into the process /// 2. The caller program with `call.dynamic` can be added to the process /// 3. `process.authorize()` succeeds for the dynamic dispatch caller /// @@ -805,12 +799,11 @@ mod tests { let caller_program = ProgramNative::from_str(DD_CALLER_PROGRAM).unwrap(); let constants_program = ProgramNative::from_str(DD_CONSTANTS_PROGRAM).unwrap(); - // Build imports object and resolve dynamic imports. + // Build imports object and resolve imports. let imports = Object::new(); Reflect::set(&imports, &JsValue::from_str("dd_constants.aleo"), &JsValue::from_str(DD_CONSTANTS_PROGRAM)) .unwrap(); - ProgramManager::resolve_imports(&mut process, &caller_program, Some(imports.clone())).unwrap(); - ProgramManager::resolve_dynamic_imports(&mut process, Some(imports)).unwrap(); + ProgramManager::resolve_imports(&mut process, Some(imports)).unwrap(); // Add the caller program to the process. process.add_program_with_edition(&caller_program, 1).unwrap(); diff --git a/wasm/src/programs/manager/mod.rs b/wasm/src/programs/manager/mod.rs index 5ecfb97a5..b2f5ea954 100644 --- a/wasm/src/programs/manager/mod.rs +++ b/wasm/src/programs/manager/mod.rs @@ -128,43 +128,8 @@ impl ProgramManager { ) } - /// Resolve imports for a program in depth first search order - pub(crate) fn resolve_imports( - process: &mut ProcessNative, - program: &ProgramNative, - imports: Option<Object>, - ) -> Result<(), String> { - if let Some(imports) = imports { - program.imports().keys().try_for_each(|program_id| { - // Get the program string - let program_id = program_id.to_string(); - if let Some(import_string) = Reflect::get(&imports, &program_id.as_str().into()) - .map_err(|_| "Program import not found in imports provided".to_string())? - .as_string() - { - if &program_id != "credits.aleo" { - let import = ProgramNative::from_str(&import_string).map_err(|err| err.to_string())?; - // Only process if the program is not already in the process - if !process.contains_program(import.id()) { - log(&format!("Importing program: {program_id}")); - // If the program has imports, add them first (depth-first). - Self::resolve_imports(process, &import, Some(imports.clone()))?; - log(&format!("Adding {program_id} to the process")); - process.add_program_with_edition(&import, 1).map_err(|err| err.to_string())?; - } - } - } - Ok::<(), String>(()) - }) - } else { - Ok(()) - } - } - - /// Resolve dynamic dispatch imports — loads ALL programs from the imports object - /// that are not already in the process. This handles programs called via `call.dynamic` - /// which are not declared in the caller's static `import` block. - pub(crate) fn resolve_dynamic_imports(process: &mut ProcessNative, imports: Option<Object>) -> Result<(), String> { + /// Resolve all imports from the imports object and load them into the process. + pub(crate) fn resolve_imports(process: &mut ProcessNative, imports: Option<Object>) -> Result<(), String> { if let Some(imports) = imports { let keys = Object::keys(&imports); for i in 0..keys.length() { @@ -173,13 +138,12 @@ impl ProgramManager { continue; } if let Some(source) = Reflect::get(&imports, &key.as_str().into()).ok().and_then(|v| v.as_string()) { - let program = ProgramNative::from_str(&source).map_err(|e| e.to_string())?; - if !process.contains_program(program.id()) { - log(&format!("Loading dynamic dispatch target: {key}")); - // Resolve this program's own static imports first. - Self::resolve_imports(process, &program, Some(imports.clone()))?; - log(&format!("Adding dynamic target {key} to the process")); - process.add_program_with_edition(&program, 1).map_err(|e| e.to_string())?; + let import = ProgramNative::from_str(&source).map_err(|e| e.to_string())?; + if !process.contains_program(import.id()) { + log(&format!("Importing program: {key}")); + Self::resolve_program_imports(process, &import, &imports)?; + log(&format!("Adding {key} to the process")); + process.add_program_with_edition(&import, 1).map_err(|e| e.to_string())?; } } } @@ -187,6 +151,32 @@ impl ProgramManager { Ok(()) } + /// Recursively resolve a program's static imports in depth-first order. + fn resolve_program_imports( + process: &mut ProcessNative, + program: &ProgramNative, + imports: &Object, + ) -> Result<(), String> { + program.imports().keys().try_for_each(|program_id| { + let program_id = program_id.to_string(); + if program_id == "credits.aleo" { + return Ok(()); + } + if let Some(import_string) = + Reflect::get(imports, &program_id.as_str().into()).ok().and_then(|v| v.as_string()) + { + let import = ProgramNative::from_str(&import_string).map_err(|err| err.to_string())?; + if !process.contains_program(import.id()) { + log(&format!("Importing program: {program_id}")); + Self::resolve_program_imports(process, &import, imports)?; + log(&format!("Adding {program_id} to the process")); + process.add_program_with_edition(&import, 1).map_err(|err| err.to_string())?; + } + } + Ok::<(), String>(()) + }) + } + pub(crate) fn validate_fee_record( fee_record: &Option<RecordPlaintext>, minimum_execution_cost: u64, @@ -272,12 +262,11 @@ function add_and_double: .unwrap(); let mut process = ProcessNative::load_web().unwrap(); - let program = ProgramNative::from_str(NESTED_IMPORT_PROGRAM).unwrap(); let add_program = ProgramNative::from_str(ADDITION_PROGRAM).unwrap(); let multiply_program = ProgramNative::from_str(MULTIPLY_PROGRAM).unwrap(); let double_program = ProgramNative::from_str(MULTIPLY_IMPORT_PROGRAM).unwrap(); - ProgramManager::resolve_imports(&mut process, &program, Some(imports)).unwrap(); + ProgramManager::resolve_imports(&mut process, Some(imports)).unwrap(); assert!(process.contains_program(add_program.id())); assert!(process.contains_program(multiply_program.id())); @@ -350,38 +339,32 @@ constructor: assert.eq true true; "#; - /// Test that resolve_dynamic_imports loads non-statically-imported programs - /// into the process, and that resolve_imports alone does NOT. + /// Test that resolve_imports loads programs from the imports object that are + /// not statically imported by the caller (i.e. dynamic dispatch targets). #[wasm_bindgen_test] fn test_resolve_dynamic_imports() { let dd_constants = ProgramNative::from_str(DD_CONSTANTS_PROGRAM).unwrap(); - let dd_caller = ProgramNative::from_str(DD_CALLER_PROGRAM).unwrap(); // Build imports object containing the dynamic target. let imports = Object::new(); Reflect::set(&imports, &JsValue::from_str("dd_constants.aleo"), &JsValue::from_str(DD_CONSTANTS_PROGRAM)) .unwrap(); - // resolve_imports alone should NOT load dd_constants (it's not a static import of dd_caller). + // resolve_imports should load dd_constants even though it's not a static import of dd_caller. let mut process = ProcessNative::load_web().unwrap(); - ProgramManager::resolve_imports(&mut process, &dd_caller, Some(imports.clone())).unwrap(); + ProgramManager::resolve_imports(&mut process, Some(imports)).unwrap(); assert!( - !process.contains_program(dd_constants.id()), - "resolve_imports should not load dynamically-dispatched programs" + process.contains_program(dd_constants.id()), + "resolve_imports should load all programs from the imports object" ); - - // resolve_dynamic_imports should load it. - ProgramManager::resolve_dynamic_imports(&mut process, Some(imports)).unwrap(); - assert!(process.contains_program(dd_constants.id()), "resolve_dynamic_imports should load dd_constants.aleo"); } - /// Test that resolve_dynamic_imports correctly loads multiple dynamic targets + /// Test that resolve_imports correctly loads multiple dynamic targets /// from a single imports object. #[wasm_bindgen_test] fn test_resolve_multiple_dynamic_imports() { let dd_constants = ProgramNative::from_str(DD_CONSTANTS_PROGRAM).unwrap(); let dd_ten = ProgramNative::from_str(DD_TEN_PROGRAM).unwrap(); - let dd_multi_caller = ProgramNative::from_str(DD_MULTI_CALLER_PROGRAM).unwrap(); // Build imports object containing both dynamic targets. let imports = Object::new(); @@ -391,14 +374,9 @@ constructor: let mut process = ProcessNative::load_web().unwrap(); - // resolve_imports should not load either (they are not static imports of dd_multi_caller). - ProgramManager::resolve_imports(&mut process, &dd_multi_caller, Some(imports.clone())).unwrap(); - assert!(!process.contains_program(dd_constants.id())); - assert!(!process.contains_program(dd_ten.id())); - - // resolve_dynamic_imports should load both. - ProgramManager::resolve_dynamic_imports(&mut process, Some(imports)).unwrap(); - assert!(process.contains_program(dd_constants.id()), "resolve_dynamic_imports should load dd_constants.aleo"); - assert!(process.contains_program(dd_ten.id()), "resolve_dynamic_imports should load dd_ten.aleo"); + // resolve_imports should load both dynamic targets. + ProgramManager::resolve_imports(&mut process, Some(imports)).unwrap(); + assert!(process.contains_program(dd_constants.id()), "resolve_imports should load dd_constants.aleo"); + assert!(process.contains_program(dd_ten.id()), "resolve_imports should load dd_ten.aleo"); } } diff --git a/wasm/src/programs/manager/proving_request.rs b/wasm/src/programs/manager/proving_request.rs index 3dc69e052..c49fe4145 100644 --- a/wasm/src/programs/manager/proving_request.rs +++ b/wasm/src/programs/manager/proving_request.rs @@ -70,8 +70,7 @@ impl ProgramManager { log("Check program imports are valid and add them to the process"); let program_native = ProgramNative::from_str(program).map_err(|e| e.to_string())?; - ProgramManager::resolve_imports(process, &program_native, imports.clone())?; - ProgramManager::resolve_dynamic_imports(process, imports.clone())?; + ProgramManager::resolve_imports(process, imports.clone())?; let rng = &mut StdRng::from_entropy(); // Convert the fee to microcredits. From fa5d4c6c36355ee17fe60116cafd7b8869b4dcd2 Mon Sep 17 00:00:00 2001 From: Cameron Marshall <cameron.marshall12@gmail.com> Date: Wed, 4 Mar 2026 19:06:31 -0500 Subject: [PATCH 11/17] Add ProgramImportsBuilder for zero-cost proving/verifying key passing to imported programs --- sdk/src/browser.ts | 3 +- sdk/src/models/imports.ts | 2 +- sdk/src/program-manager.ts | 70 +++-- sdk/src/wasm.ts | 2 + sdk/tests/dynamic-dispatch.test.ts | 71 +++++ wasm/src/programs/manager/authorize.rs | 12 +- wasm/src/programs/manager/deploy.rs | 30 +- wasm/src/programs/manager/execute.rs | 34 ++- wasm/src/programs/manager/imports.rs | 275 +++++++++++++++++++ wasm/src/programs/manager/mod.rs | 164 ++++++++++- wasm/src/programs/manager/proving_request.rs | 8 +- 11 files changed, 631 insertions(+), 40 deletions(-) create mode 100644 wasm/src/programs/manager/imports.rs diff --git a/sdk/src/browser.ts b/sdk/src/browser.ts index fad247007..37bcfd6b2 100644 --- a/sdk/src/browser.ts +++ b/sdk/src/browser.ts @@ -99,7 +99,7 @@ async function initializeWasm() { console.warn("initializeWasm is deprecated, you no longer need to use it"); } -export { ProgramManager, ProvingRequestOptions, ExecuteOptions, FeeAuthorizationOptions, AuthorizationOptions } from "./program-manager.js"; +export { ProgramManager, ProvingRequestOptions, ExecuteOptions, FeeEstimateOptions, FeeAuthorizationOptions, AuthorizationOptions, ExecuteAuthorizationOptions } from "./program-manager.js"; export { logAndThrow } from "./utils.js"; @@ -136,6 +136,7 @@ export { PrivateKey, PrivateKeyCiphertext, Program, + ProgramImportsBuilder, ProgramManager as ProgramManagerBase, ProvingKey, ProvingRequest, diff --git a/sdk/src/models/imports.ts b/sdk/src/models/imports.ts index 11f2b4d16..e6c3b0e48 100644 --- a/sdk/src/models/imports.ts +++ b/sdk/src/models/imports.ts @@ -6,4 +6,4 @@ interface ImportedPrograms { [key: string]: string; // This allows for arbitrary keys with any type values } -export { ImportedVerifyingKeys, ImportedPrograms } \ No newline at end of file +export { ImportedVerifyingKeys, ImportedPrograms } diff --git a/sdk/src/program-manager.ts b/sdk/src/program-manager.ts index 9a13eb61d..3d5cb062c 100644 --- a/sdk/src/program-manager.ts +++ b/sdk/src/program-manager.ts @@ -26,6 +26,7 @@ import { RecordPlaintext, PrivateKey, Program, + ProgramImportsBuilder, ProvingKey, ProvingRequest, VerifyingKey, @@ -81,6 +82,19 @@ interface DeployOptions { * @property {string | Program} [program] - Program source code to use for the transaction. * @property {ProgramImports} [imports] - Programs that the program being executed imports. * @property {number} [edition] - Edition of the program to execute the function in. + * @property {ProgramImportsBuilder} [programImportsBuilder] - A builder carrying imported programs + * with optional pre-computed proving/verifying keys. When provided, takes precedence over `imports` + * and avoids key serialization overhead. + * + * Example — providing keys for an imported program: + * ```ts + * const [provingKey, verifyingKey] = await pm.synthesizeKeys(importSource, "fn_name", sampleInputs); + * const builder = new ProgramImportsBuilder(); + * builder.addProgram("my_import.aleo", importSource); + * builder.addProvingKey("my_import.aleo", "fn_name", provingKey); + * builder.addVerifyingKey("my_import.aleo", "fn_name", verifyingKey); + * await pm.buildExecutionTransaction({ ..., programImportsBuilder: builder }); + * ``` */ interface ExecuteOptions { programName: string; @@ -98,6 +112,7 @@ interface ExecuteOptions { program?: string | Program; imports?: ProgramImports; edition?: number, + programImportsBuilder?: ProgramImportsBuilder; } /** @@ -110,6 +125,9 @@ interface ExecuteOptions { * @property {PrivateKey} [privateKey] Optional private key to use to build the authorization. * @property {ProgramImports} [programImports] The other programs the program imports. * @property {edition} [edition] + * @property {ProgramImportsBuilder} [programImportsBuilder] A builder carrying imported programs + * with optional pre-computed proving/verifying keys. Takes precedence over `programImports`. + * See {@link ExecuteOptions.programImportsBuilder} for a usage example. */ interface AuthorizationOptions { programName: string; @@ -119,6 +137,7 @@ interface AuthorizationOptions { privateKey?: PrivateKey; programImports?: ProgramImports; edition?: number, + programImportsBuilder?: ProgramImportsBuilder; } /** @@ -159,6 +178,7 @@ interface ExecuteAuthorizationOptions { offlineQuery?: OfflineQuery; program?: string | Program; imports?: ProgramImports; + programImportsBuilder?: ProgramImportsBuilder; } /** @@ -199,6 +219,7 @@ interface ProvingRequestOptions { unchecked?: boolean; edition?: number; useFeeMaster?: boolean; + programImportsBuilder?: ProgramImportsBuilder; } /** @@ -218,6 +239,7 @@ interface FeeEstimateOptions { imports?: ProgramImports; edition?: number, authorization?: Authorization; + programImportsBuilder?: ProgramImportsBuilder; } /** @@ -808,6 +830,7 @@ class ProgramManager { let programName = options.programName; let imports = options.imports; let edition = options.edition; + const programImportsBuilder = options.programImportsBuilder; let programObject; // Ensure the function exists on the network @@ -955,7 +978,8 @@ class ProgramManager { feeProvingKey, feeVerifyingKey, offlineQuery, - edition + edition, + programImportsBuilder, ); } @@ -1041,6 +1065,7 @@ class ProgramManager { const feeAuthorization = options.feeAuthorization; const keySearchParams = options.keySearchParams; const offlineQuery = options.offlineQuery; + const programImportsBuilder = options.programImportsBuilder; let provingKey = options.provingKey; let verifyingKey = options.verifyingKey; let program = options.program; @@ -1128,7 +1153,8 @@ class ProgramManager { feeVerifyingKey, imports, this.host, - offlineQuery + offlineQuery, + programImportsBuilder, ) } @@ -1170,6 +1196,7 @@ class ProgramManager { } = options; const privateKey = options.privateKey; + const programImportsBuilder = options.programImportsBuilder; let program = options.programSource; let programName = options.programName; let imports = options.programImports; @@ -1238,7 +1265,8 @@ class ProgramManager { functionName, inputs, imports, - edition + edition, + programImportsBuilder, ); } @@ -1280,6 +1308,7 @@ class ProgramManager { } = options; const privateKey = options.privateKey; + const programImportsBuilder = options.programImportsBuilder; let program = options.programSource; let programName = options.programName; let imports = options.programImports; @@ -1348,7 +1377,8 @@ class ProgramManager { functionName, inputs, imports, - edition + edition, + programImportsBuilder, ); } @@ -1400,6 +1430,7 @@ class ProgramManager { const baseFee = options.baseFee ? options.baseFee : 0; const privateKey = options.privateKey; const useFeeMaster = options.useFeeMaster ? options.useFeeMaster : false; + const programImportsBuilder = options.programImportsBuilder; let program = options.programSource; let programName = options.programName; let feeRecord = options.feeRecord; @@ -1502,7 +1533,8 @@ class ProgramManager { broadcast, unchecked, edition, - useFeeMaster + useFeeMaster, + programImportsBuilder, ); } @@ -1673,7 +1705,8 @@ class ProgramManager { verifyingKey?: VerifyingKey, privateKey?: PrivateKey, offlineQuery?: OfflineQuery, - edition?: number + edition?: number, + programImportsBuilder?: ProgramImportsBuilder, ): Promise<ExecutionResponse> { // Get the private key from the account if it is not provided in the parameters let executionPrivateKey = privateKey; @@ -1717,7 +1750,8 @@ class ProgramManager { verifyingKey, this.host, offlineQuery, - edition + edition, + programImportsBuilder, ); } @@ -3182,7 +3216,8 @@ class ProgramManager { programName, program, imports, - edition + edition, + programImportsBuilder, } = options; if (!authorization) { throw new Error("Authorization must be provided if estimating fee for Authorization.") @@ -3191,9 +3226,9 @@ class ProgramManager { const programImports = imports ? imports : await this.networkClient.getProgramImports(programSource); console.log(JSON.stringify(programImports)); if (Object.keys(programImports)) { - return WasmProgramManager.estimateFeeForAuthorization(authorization, programSource, programImports, edition); + return WasmProgramManager.estimateFeeForAuthorization(authorization, programSource, programImports, edition, programImportsBuilder); } - return WasmProgramManager.estimateFeeForAuthorization(authorization, programSource, imports, edition); + return WasmProgramManager.estimateFeeForAuthorization(authorization, programSource, imports, edition, programImportsBuilder); } /** @@ -3230,7 +3265,8 @@ class ProgramManager { programName, program, imports, - edition + edition, + programImportsBuilder, } = options; if (!functionName) { throw new Error("Function name must be specified when estimating fee."); @@ -3238,9 +3274,9 @@ class ProgramManager { const programSource = program ? program.toString() : await this.networkClient.getProgram(programName, edition); const programImports = imports ? imports : await this.networkClient.getProgramImports(programSource); if (Object.keys(programImports)) { - return WasmProgramManager.estimateExecutionFee(programSource, functionName, programImports, edition); + return WasmProgramManager.estimateExecutionFee(programSource, functionName, programImports, edition, programImportsBuilder); } - return WasmProgramManager.estimateExecutionFee(programSource, functionName, imports, edition); + return WasmProgramManager.estimateExecutionFee(programSource, functionName, imports, edition, programImportsBuilder); } // Internal utility function for getting a credits.aleo record @@ -3336,6 +3372,7 @@ class ProgramManager { let programName = options.programName; let imports = options.imports; let edition = options.edition; + const programImportsBuilder = options.programImportsBuilder; let programObject; // Ensure the function exists on the network @@ -3444,10 +3481,11 @@ class ProgramManager { feeRecord, this.host, imports, - edition + edition, + programImportsBuilder, ); } - + /** * Builds a deployment transaction with placeholder certificates and verifying keys for each function in the program. * Intended for use with a local devnode. @@ -3729,4 +3767,4 @@ function validateTransferType(transferType: string): string { ); } -export { ProgramManager, AuthorizationOptions, FeeAuthorizationOptions, ExecuteOptions, ProvingRequestOptions }; +export { ProgramManager, AuthorizationOptions, FeeAuthorizationOptions, FeeEstimateOptions, ExecuteOptions, ExecuteAuthorizationOptions, ProvingRequestOptions }; diff --git a/sdk/src/wasm.ts b/sdk/src/wasm.ts index e0e629e4c..523167761 100644 --- a/sdk/src/wasm.ts +++ b/sdk/src/wasm.ts @@ -32,6 +32,8 @@ export { PrivateKey, PrivateKeyCiphertext, Program, + // Aliased to avoid collision with the SDK's ProgramImports type (network-client.ts). + ProgramImports as ProgramImportsBuilder, ProgramManager, ProvingKey, ProvingRequest, diff --git a/sdk/tests/dynamic-dispatch.test.ts b/sdk/tests/dynamic-dispatch.test.ts index db99c3ffb..3b33d3b8a 100644 --- a/sdk/tests/dynamic-dispatch.test.ts +++ b/sdk/tests/dynamic-dispatch.test.ts @@ -3,6 +3,9 @@ import { Account, AleoKeyProvider, Authorization, + PrivateKey, + ProgramImportsBuilder, + ProgramManagerBase, ProgramManager, ProvingRequest, Transaction, @@ -365,6 +368,74 @@ describe("Dynamic Dispatch", () => { expect(fromBytes.equals(authorization)).equal(true); }); + it("should build an authorization using ProgramImportsBuilder instead of plain object", async () => { + // Create a ProgramImportsBuilder and add the import via the builder API. + const builder = new ProgramImportsBuilder(); + builder.addProgram("dd_constants.aleo", DD_CONSTANTS_PROGRAM); + + const authorization = await programManager.buildAuthorization({ + programName: "dd_caller.aleo", + functionName: "call_and_increment", + inputs: [DD_CONSTANTS_FIELD, DD_ALEO_FIELD, DD_GET_VALUE_FIELD], + programSource: DD_CALLER_PROGRAM, + programImportsBuilder: builder, + edition: 0, + }); + + // Same result as the plain-object path: 2 transitions. + expect(authorization.transitions().length).equal(2); + + // Verify serialization round-trips. + const fromString = Authorization.fromString(authorization.toString()); + const fromBytes = Authorization.fromBytesLe(authorization.toBytesLe()); + expect(fromString.equals(authorization)).equal(true); + expect(fromBytes.equals(authorization)).equal(true); + }); + + it("should build an authorization with pre-computed keys via ProgramImportsBuilder", async function() { + // This test synthesizes keys which is expensive. + this.timeout(120_000); + + // Synthesize keys for the imported program's function. + const privateKey = new PrivateKey(); + const keyPair = await ProgramManagerBase.synthesizeKeyPair( + privateKey, + DD_CONSTANTS_PROGRAM, + "get_value", + [], // get_value takes no inputs + undefined, // no legacy imports + 0, // edition + undefined, // no program_imports builder + ); + const provingKey = keyPair.provingKey(); + const verifyingKey = keyPair.verifyingKey(); + + // Build a ProgramImportsBuilder with the program source AND pre-computed keys. + const builder = new ProgramImportsBuilder(); + builder.addProgram("dd_constants.aleo", DD_CONSTANTS_PROGRAM); + builder.addProvingKey("dd_constants.aleo", "get_value", provingKey); + builder.addVerifyingKey("dd_constants.aleo", "get_value", verifyingKey); + + // Build the authorization using the builder with keys. + const authorization = await programManager.buildAuthorization({ + programName: "dd_caller.aleo", + functionName: "call_and_increment", + inputs: [DD_CONSTANTS_FIELD, DD_ALEO_FIELD, DD_GET_VALUE_FIELD], + programSource: DD_CALLER_PROGRAM, + programImportsBuilder: builder, + edition: 0, + }); + + // Same result as the plain-object path: 2 transitions. + expect(authorization.transitions().length).equal(2); + + // Verify serialization round-trips. + const fromString = Authorization.fromString(authorization.toString()); + const fromBytes = Authorization.fromBytesLe(authorization.toBytesLe()); + expect(fromString.equals(authorization)).equal(true); + expect(fromBytes.equals(authorization)).equal(true); + }); + it("should build a proving request for a call.dynamic program via provingRequest", async () => { const provingRequest = await programManager.provingRequest({ programName: "dd_caller.aleo", diff --git a/wasm/src/programs/manager/authorize.rs b/wasm/src/programs/manager/authorize.rs index 387871ac1..5c929def5 100644 --- a/wasm/src/programs/manager/authorize.rs +++ b/wasm/src/programs/manager/authorize.rs @@ -41,6 +41,8 @@ impl ProgramManager { /// @param function_name The function to authorize. /// @param inputs A javascript array of inputs to the function. /// @param imports The imports to the program in the format {"programname.aleo":"aleo instructions source code"}. + /// @param {ProgramImports | undefined} program_imports (optional) A ProgramImports builder with + /// pre-computed proving and verifying keys for imported programs. pub async fn authorize( private_key: &PrivateKey, program: &str, @@ -48,6 +50,7 @@ impl ProgramManager { inputs: Array, imports: Option<Object>, edition: Option<u16>, + program_imports: Option<ProgramImports>, ) -> Result<Authorization, String> { let mut process_native = ProcessNative::load_web().map_err(|err| err.to_string())?; let process = &mut process_native; @@ -55,7 +58,7 @@ impl ProgramManager { log("Check program imports are valid and add them to the process"); let program_native = ProgramNative::from_str(program).map_err(|e| e.to_string())?; log(&format!("Creating proving request for {}:{function_name}", program_native.id())); - ProgramManager::resolve_imports(process, imports)?; + ProgramManager::resolve_imports_or_builder(process, imports, program_imports.as_ref())?; let rng = &mut StdRng::from_entropy(); // Authorize the main program. @@ -82,6 +85,8 @@ impl ProgramManager { /// @param function_name The function to authorize. /// @param inputs A javascript array of inputs to the function. /// @param imports The imports to the program in the format {"programname.aleo":"aleo instructions source code"}. + /// @param {ProgramImports | undefined} program_imports (optional) A ProgramImports builder with + /// pre-computed proving and verifying keys for imported programs. #[wasm_bindgen(js_name = buildAuthorizationUnchecked)] pub async fn authorize_unchecked( private_key: &PrivateKey, @@ -90,6 +95,7 @@ impl ProgramManager { inputs: Array, imports: Option<Object>, edition: Option<u16>, + program_imports: Option<ProgramImports>, ) -> Result<Authorization, String> { let mut process_native = ProcessNative::load_web().map_err(|err| err.to_string())?; let process = &mut process_native; @@ -97,7 +103,7 @@ impl ProgramManager { log("Check program imports are valid and add them to the process"); let program_native = ProgramNative::from_str(program).map_err(|e| e.to_string())?; log(&format!("Creating proving request for {}:{function_name}", program_native.id())); - ProgramManager::resolve_imports(process, imports)?; + ProgramManager::resolve_imports_or_builder(process, imports, program_imports.as_ref())?; let rng = &mut StdRng::from_entropy(); // Authorize the main program. @@ -177,7 +183,7 @@ mod tests { // Create the puzzle spinner authorization and ensure it has the correct amount of transitions. let authorization = - ProgramManager::authorize(&private_key, PUZZLE_SPINNER_V002, function_name, inputs, imports, None) + ProgramManager::authorize(&private_key, PUZZLE_SPINNER_V002, function_name, inputs, imports, None, None) .await .unwrap(); console_log!("{authorization:?}"); diff --git a/wasm/src/programs/manager/deploy.rs b/wasm/src/programs/manager/deploy.rs index 19926f525..07d30a9ba 100644 --- a/wasm/src/programs/manager/deploy.rs +++ b/wasm/src/programs/manager/deploy.rs @@ -68,6 +68,8 @@ impl ProgramManager { /// are a string representing the program source code \{ "hello.aleo": "hello.aleo source code" \} /// @param fee_proving_key (optional) Provide a proving key to use for the fee execution /// @param fee_verifying_key (optional) Provide a verifying key to use for the fee execution + /// @param {ProgramImports | undefined} program_imports (optional) A ProgramImports builder with + /// pre-computed proving and verifying keys for imported programs. /// @returns {Transaction} #[wasm_bindgen(js_name = buildDeploymentTransaction)] #[allow(clippy::too_many_arguments)] @@ -81,6 +83,7 @@ impl ProgramManager { fee_proving_key: Option<ProvingKey>, fee_verifying_key: Option<VerifyingKey>, offline_query: Option<OfflineQuery>, + program_imports: Option<ProgramImports>, ) -> Result<Transaction, String> { log("Creating deployment transaction"); let mut process_native = ProcessNative::load_web().map_err(|err| err.to_string())?; @@ -90,7 +93,7 @@ impl ProgramManager { let program = ProgramNative::from_str(program).map_err(|err| err.to_string())?; log("Checking program imports are valid and add them to the process"); - ProgramManager::resolve_imports(process, imports)?; + ProgramManager::resolve_imports_or_builder(process, imports, program_imports.as_ref())?; let rng = &mut StdRng::from_entropy(); log("Creating deployment"); @@ -162,9 +165,15 @@ impl ProgramManager { /// @param imports (optional) Provide a list of imports to use for the deployment fee estimation /// in the form of a javascript object where the keys are a string of the program name and the values /// are a string representing the program source code \{ "hello.aleo": "hello.aleo source code" \} + /// @param {ProgramImports | undefined} program_imports (optional) A ProgramImports builder with + /// pre-computed proving and verifying keys for imported programs. /// @returns {u64} #[wasm_bindgen(js_name = estimateDeploymentFee)] - pub async fn estimate_deployment_fee(program: &str, imports: Option<Object>) -> Result<u64, String> { + pub async fn estimate_deployment_fee( + program: &str, + imports: Option<Object>, + program_imports: Option<ProgramImports>, + ) -> Result<u64, String> { log( "Disclaimer: Fee estimation is experimental and may not represent a correct estimate on any current or future network", ); @@ -175,7 +184,7 @@ impl ProgramManager { let program = ProgramNative::from_str(program).map_err(|err| err.to_string())?; log("Check program imports are valid and add them to the process"); - ProgramManager::resolve_imports(process, imports)?; + ProgramManager::resolve_imports_or_builder(process, imports, program_imports.as_ref())?; log("Create sample deployment"); let mut deployment = @@ -237,6 +246,8 @@ impl ProgramManager { /// for the deployment to succeed /// @param fee_proving_key (optional) Provide a proving key to use for the fee execution /// @param fee_verifying_key (optional) Provide a verifying key to use for the fee execution + /// @param {ProgramImports | undefined} program_imports (optional) A ProgramImports builder with + /// pre-computed proving and verifying keys for imported programs. /// @returns {Transaction} #[wasm_bindgen(js_name = buildUpgradeTransaction)] #[allow(clippy::too_many_arguments)] @@ -250,6 +261,7 @@ impl ProgramManager { fee_proving_key: Option<ProvingKey>, fee_verifying_key: Option<VerifyingKey>, offline_query: Option<OfflineQuery>, + program_imports: Option<ProgramImports>, ) -> Result<Transaction, String> { log("Creating deployment transaction"); let mut process_native = ProcessNative::load_web().map_err(|err| err.to_string())?; @@ -269,7 +281,7 @@ impl ProgramManager { process.add_program_with_edition(&deployed_program, deployed_program_edition).map_err(|err| err.to_string())?; log("Checking program imports are valid and add them to the process"); - ProgramManager::resolve_imports(process, imports)?; + ProgramManager::resolve_imports_or_builder(process, imports, program_imports.as_ref())?; let rng = &mut StdRng::from_entropy(); log("Creating deployment"); @@ -346,6 +358,8 @@ impl ProgramManager { /// are a string representing the program source code \{ "hello.aleo": "hello.aleo source code" \} /// @param fee_proving_key (optional) Provide a proving key to use for the fee execution /// @param fee_verifying_key (optional) Provide a verifying key to use for the fee execution + /// @param {ProgramImports | undefined} program_imports (optional) A ProgramImports builder with + /// pre-computed proving and verifying keys for imported programs. /// @returns {Transaction} #[wasm_bindgen(js_name = buildDevnodeDeploymentTransaction)] #[allow(clippy::too_many_arguments)] @@ -356,6 +370,7 @@ impl ProgramManager { fee_record: Option<RecordPlaintext>, url: Option<String>, imports: Option<Object>, + program_imports: Option<ProgramImports>, ) -> Result<Transaction, String> { log("Creating deployment transaction"); let mut process_native = ProcessNative::load_web().map_err(|err| err.to_string())?; @@ -366,7 +381,7 @@ impl ProgramManager { let program_id = program.id(); log("Checking program imports are valid and add them to the process"); - ProgramManager::resolve_imports(process, imports)?; + ProgramManager::resolve_imports_or_builder(process, imports, program_imports.as_ref())?; let rng = &mut StdRng::from_entropy(); log("Creating deployment"); @@ -483,6 +498,8 @@ impl ProgramManager { /// are a string representing the program source code \{ "hello.aleo": "hello.aleo source code" \} /// @param fee_proving_key (optional) Provide a proving key to use for the fee execution /// @param fee_verifying_key (optional) Provide a verifying key to use for the fee execution + /// @param {ProgramImports | undefined} program_imports (optional) A ProgramImports builder with + /// pre-computed proving and verifying keys for imported programs. /// @returns {Transaction} #[wasm_bindgen(js_name = buildDevnodeUpgradeTransaction)] #[allow(clippy::too_many_arguments)] @@ -493,6 +510,7 @@ impl ProgramManager { fee_record: Option<RecordPlaintext>, url: Option<String>, imports: Option<Object>, + program_imports: Option<ProgramImports>, ) -> Result<Transaction, String> { log("Creating deployment transaction"); let mut process_native = ProcessNative::load_web().map_err(|err| err.to_string())?; @@ -500,7 +518,7 @@ impl ProgramManager { log("Checking program imports are valid and add them to the process"); let program = ProgramNative::from_str(&program).map_err(|err| err.to_string())?; - ProgramManager::resolve_imports(process, imports)?; + ProgramManager::resolve_imports_or_builder(process, imports, program_imports.as_ref())?; let rng = &mut StdRng::from_entropy(); log("Creating deployment"); diff --git a/wasm/src/programs/manager/execute.rs b/wasm/src/programs/manager/execute.rs index ed56d1243..ea86ae80b 100644 --- a/wasm/src/programs/manager/execute.rs +++ b/wasm/src/programs/manager/execute.rs @@ -81,6 +81,9 @@ impl ProgramManager { /// are a string representing the program source code \{ "hello.aleo": "hello.aleo source code" \} /// @param {ProvingKey | undefined} proving_key (optional) Provide a verifying key to use for the function execution /// @param {VerifyingKey | undefined} verifying_key (optional) Provide a verifying key to use for the function execution + /// @param {ProgramImports | undefined} program_imports (optional) A ProgramImports builder with + /// pre-computed proving and verifying keys for imported programs. When provided, this takes + /// precedence over the `imports` parameter and avoids key serialization overhead. #[wasm_bindgen(js_name = executeFunctionOffline)] #[allow(clippy::too_many_arguments)] pub async fn execute_function_offline( @@ -96,6 +99,7 @@ impl ProgramManager { url: Option<String>, offline_query: Option<OfflineQuery>, edition: Option<u16>, + program_imports: Option<ProgramImports>, ) -> Result<ExecutionResponse, String> { let node_url = url.as_deref().unwrap_or(DEFAULT_URL); let inputs = inputs.to_vec(); @@ -106,7 +110,7 @@ impl ProgramManager { log("Check program imports are valid and add them to the process"); let program_native = ProgramNative::from_str(program).map_err(|e| e.to_string())?; - ProgramManager::resolve_imports(process, imports)?; + ProgramManager::resolve_imports_or_builder(process, imports, program_imports.as_ref())?; let edition = edition.unwrap_or(1); let (response, mut trace) = execute_program!( @@ -179,6 +183,8 @@ impl ProgramManager { /// @param fee_verifying_key (optional) Provide a verifying key to use for the fee execution /// @param offline_query An offline query object to use if building a transaction without an internet connection. /// @param edition The edition of the program to execute. Defaults to the latest found on the network, or 1 if the program does not exist on the network. + /// @param {ProgramImports | undefined} program_imports (optional) A ProgramImports builder with + /// pre-computed proving and verifying keys for imported programs. /// @returns {Transaction} #[wasm_bindgen(js_name = buildExecutionTransaction)] #[allow(clippy::too_many_arguments)] @@ -197,6 +203,7 @@ impl ProgramManager { fee_verifying_key: Option<VerifyingKey>, offline_query: Option<OfflineQuery>, edition: Option<u16>, + program_imports: Option<ProgramImports>, ) -> Result<Transaction, String> { let mut process_native = ProcessNative::load_web().map_err(|err| err.to_string())?; let process = &mut process_native; @@ -205,7 +212,7 @@ impl ProgramManager { log("Check program imports are valid and add them to the process"); let program_native = ProgramNative::from_str(program).map_err(|e| e.to_string())?; let program_id = program_native.id().to_string(); - ProgramManager::resolve_imports(process, imports)?; + ProgramManager::resolve_imports_or_builder(process, imports, program_imports.as_ref())?; let rng = &mut StdRng::from_entropy(); log(&format!("Executing function: {program_id}/{function} on-chain")); @@ -303,7 +310,10 @@ impl ProgramManager { /// @param imports The imports of the program being executed. /// @param url The url to get the inclusion proving information from. /// @param offline_query Optional offline query object if building a Transaction offline. + /// @param {ProgramImports | undefined} program_imports (optional) A ProgramImports builder with + /// pre-computed proving and verifying keys for imported programs. #[wasm_bindgen(js_name = executeAuthorization)] + #[allow(clippy::too_many_arguments)] pub async fn execute_authorization( authorization: Authorization, fee_authorization: Option<Authorization>, @@ -315,6 +325,7 @@ impl ProgramManager { imports: Option<Object>, url: Option<String>, offline_query: Option<OfflineQuery>, + program_imports: Option<ProgramImports>, ) -> Result<Transaction, String> { // Create a process and insert the program and its imports. log("Loading the SnarkVM process"); @@ -340,7 +351,7 @@ impl ProgramManager { let program_id = program_native.id().to_string(); // Insert the program and its imports. - ProgramManager::resolve_imports(process, imports)?; + ProgramManager::resolve_imports_or_builder(process, imports, program_imports.as_ref())?; if program_id != "credits.aleo" && !process.contains_program(program_native.id()) { process.add_program(&program_native).map_err(|e| e.to_string())?; } @@ -488,6 +499,8 @@ impl ProgramManager { /// @param fee_verifying_key (optional) Provide a verifying key to use for the fee execution /// @param offline_query An offline query object to use if building a transaction without an internet connection. /// @param edition The edition of the program to execute. Defaults to the latest found on the network, or 1 if the program does not exist on the network. + /// @param {ProgramImports | undefined} program_imports (optional) A ProgramImports builder with + /// pre-computed proving and verifying keys for imported programs. /// @returns {Transaction} #[wasm_bindgen(js_name = buildDevnodeExecutionTransaction)] #[allow(clippy::too_many_arguments)] @@ -501,6 +514,7 @@ impl ProgramManager { url: Option<String>, imports: Option<Object>, edition: Option<u16>, + program_imports: Option<ProgramImports>, ) -> Result<Transaction, String> { log("Loading the SnarkVM process"); let mut process_native = ProcessNative::load_web().map_err(|err| err.to_string())?; @@ -513,7 +527,7 @@ impl ProgramManager { log("Check program imports are valid and add them to the process"); let program_native = ProgramNative::from_str(program).map_err(|e| e.to_string())?; let program_id = program_native.id().to_string(); - ProgramManager::resolve_imports(process, imports)?; + ProgramManager::resolve_imports_or_builder(process, imports, program_imports.as_ref())?; let edition = edition.unwrap_or(1); let inputs = process_inputs!(inputs); @@ -597,6 +611,8 @@ impl ProgramManager { /// form of a javascript object where the keys are a string of the program name and the values /// are a string representing the program source code \{ "hello.aleo": "hello.aleo source code" \} /// @param edition { + /// @param {ProgramImports | undefined} program_imports (optional) A ProgramImports builder with + /// pre-computed proving and verifying keys for imported programs. /// @returns {u64} Fee in microcredits #[wasm_bindgen(js_name = estimateExecutionFee)] #[allow(clippy::too_many_arguments)] @@ -605,6 +621,7 @@ impl ProgramManager { function: &str, imports: Option<Object>, edition: Option<u16>, + program_imports: Option<ProgramImports>, ) -> Result<u64, String> { let mut process_native = ProcessNative::load_web().map_err(|err| err.to_string())?; let process = &mut process_native; @@ -624,7 +641,7 @@ impl ProgramManager { .map_err(|e| e.to_string())?; // Resolve program imports. - ProgramManager::resolve_imports(process, imports)?; + ProgramManager::resolve_imports_or_builder(process, imports, program_imports.as_ref())?; // Add the program to the process. let program_id = program_native.id(); @@ -681,6 +698,8 @@ impl ProgramManager { /// @param offline_query The offline query object used to insert the global state root and state paths needed to create /// a valid inclusion proof offline. /// @param edition: Optional edition to estimate the fee for. + /// @param {ProgramImports | undefined} program_imports (optional) A ProgramImports builder with + /// pre-computed proving and verifying keys for imported programs. /// @returns {u64} Fee in microcredits #[wasm_bindgen(js_name = estimateFeeForAuthorization)] #[allow(clippy::too_many_arguments)] @@ -689,6 +708,7 @@ impl ProgramManager { program: &str, imports: Option<Object>, edition: Option<u16>, + program_imports: Option<ProgramImports>, ) -> Result<u64, String> { let mut process_native = ProcessNative::load_web().map_err(|err| err.to_string())?; let process = &mut process_native; @@ -697,7 +717,7 @@ impl ProgramManager { let program_native = ProgramNative::from_str(program).map_err(|e| e.to_string())?; // Resolve program imports. - ProgramManager::resolve_imports(process, imports)?; + ProgramManager::resolve_imports_or_builder(process, imports, program_imports.as_ref())?; // Add the program to the process. let program_id = program_native.id(); @@ -758,7 +778,7 @@ mod tests { let program = ProgramNative::credits().unwrap(); let authorization_estimate = - ProgramManager::estimate_execution_fee(&program.to_string(), function, None, None).unwrap(); + ProgramManager::estimate_execution_fee(&program.to_string(), function, None, None, None).unwrap(); assert_eq!(authorization_estimate, cost); } diff --git a/wasm/src/programs/manager/imports.rs b/wasm/src/programs/manager/imports.rs new file mode 100644 index 000000000..00ba31baf --- /dev/null +++ b/wasm/src/programs/manager/imports.rs @@ -0,0 +1,275 @@ +// Copyright (C) 2019-2025 Provable Inc. +// This file is part of the Provable SDK library. + +// The Provable SDK library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Provable SDK library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Provable SDK library. If not, see <https://www.gnu.org/licenses/>. + +use crate::{ + ProvingKey, + VerifyingKey, + log, + types::native::{ + IdentifierNative, + ProcessNative, + ProgramIDNative, + ProgramNative, + ProvingKeyNative, + VerifyingKeyNative, + }, +}; + +use js_sys::{Object, Reflect}; +use snarkvm_synthesizer_program::StackTrait; +use std::{collections::HashMap, str::FromStr}; +use wasm_bindgen::prelude::wasm_bindgen; + +use super::ProgramManager; + +/// Internal storage for a single imported program's source and optional keys. +#[derive(Clone)] +struct ProgramEntry { + source: String, + proving_keys: HashMap<String, ProvingKeyNative>, + verifying_keys: HashMap<String, VerifyingKeyNative>, +} + +/// A builder for specifying program imports with optional proving and verifying keys. +/// +/// This type allows callers to provide pre-computed proving and verifying keys +/// alongside imported program source code. Keys are accepted as native WASM +/// `ProvingKey` / `VerifyingKey` objects — no serialization is required. +/// +/// Keys are indexed by identifier which can be either a function name (for +/// function keys) or a record name (for translation keys used by `call.dynamic`). +/// +/// # Example (JavaScript) +/// ```js +/// const imports = new ProgramImports(); +/// imports.addProgram("my_program.aleo", programSource); +/// imports.addProvingKey("my_program.aleo", "my_function", provingKey); +/// imports.addVerifyingKey("my_program.aleo", "my_function", verifyingKey); +/// ``` +#[wasm_bindgen] +#[derive(Clone)] +pub struct ProgramImports { + entries: HashMap<String, ProgramEntry>, +} + +#[wasm_bindgen] +impl ProgramImports { + /// Create a new empty ProgramImports builder. + #[wasm_bindgen(constructor)] + pub fn new() -> Self { + Self { entries: HashMap::new() } + } + + /// Create a ProgramImports from a plain JavaScript object. + /// + /// Accepts the legacy format where keys are program names and values are + /// either source code strings or objects with a `source` property: + /// ```js + /// { "my_program.aleo": "program source..." } + /// // or + /// { "my_program.aleo": { source: "program source..." } } + /// ``` + /// + /// @param {Object} object A plain JavaScript object mapping program names to source code. + /// @returns {ProgramImports} + #[wasm_bindgen(js_name = "fromObject")] + pub fn from_object(object: Object) -> Self { + let mut imports = Self::new(); + let keys = Object::keys(&object); + for i in 0..keys.length() { + let name = keys.get(i).as_string().unwrap_or_default(); + let value = Reflect::get(&object, &name.as_str().into()).ok(); + if let Some(source) = extract_source(&value) { + imports.add_program(&name, &source); + } + } + imports + } + + /// Add a program's source code to the imports. + /// + /// @param {string} name The program name (e.g., "my_program.aleo"). + /// @param {string} source The program source code. + #[wasm_bindgen(js_name = "addProgram")] + pub fn add_program(&mut self, name: &str, source: &str) { + self.entries.entry(name.to_string()).and_modify(|e| e.source = source.to_string()).or_insert_with(|| { + ProgramEntry { source: source.to_string(), proving_keys: HashMap::new(), verifying_keys: HashMap::new() } + }); + } + + /// Add a proving key for a function or record within an imported program. + /// + /// The key is transferred directly from the WASM `ProvingKey` type with no + /// serialization overhead. + /// + /// @param {string} program_name The program name (e.g., "my_program.aleo"). + /// @param {string} identifier The function name or record name the key belongs to. + /// @param {ProvingKey} key The proving key. + #[wasm_bindgen(js_name = "addProvingKey")] + pub fn add_proving_key(&mut self, program_name: &str, identifier: &str, key: ProvingKey) { + let entry = self.entries.entry(program_name.to_string()).or_insert_with(|| ProgramEntry { + source: String::new(), + proving_keys: HashMap::new(), + verifying_keys: HashMap::new(), + }); + entry.proving_keys.insert(identifier.to_string(), ProvingKeyNative::from(key)); + } + + /// Add a verifying key for a function or record within an imported program. + /// + /// The key is transferred directly from the WASM `VerifyingKey` type with no + /// serialization overhead. + /// + /// @param {string} program_name The program name (e.g., "my_program.aleo"). + /// @param {string} identifier The function name or record name the key belongs to. + /// @param {VerifyingKey} key The verifying key. + #[wasm_bindgen(js_name = "addVerifyingKey")] + pub fn add_verifying_key(&mut self, program_name: &str, identifier: &str, key: VerifyingKey) { + let entry = self.entries.entry(program_name.to_string()).or_insert_with(|| ProgramEntry { + source: String::new(), + proving_keys: HashMap::new(), + verifying_keys: HashMap::new(), + }); + entry.verifying_keys.insert(identifier.to_string(), VerifyingKeyNative::from(key)); + } + + /// Convert this ProgramImports to a plain JavaScript object containing only + /// program sources (no keys). Useful for interop with APIs that accept the + /// legacy `{ "name.aleo": "source" }` format. + /// + /// @returns {Object} + #[wasm_bindgen(js_name = "toObject")] + pub fn to_object(&self) -> Object { + let obj = Object::new(); + for (name, entry) in &self.entries { + if !entry.source.is_empty() { + Reflect::set(&obj, &name.as_str().into(), &entry.source.as_str().into()).unwrap(); + } + } + obj + } + + /// Check whether any programs have been added to this builder. + /// + /// @returns {boolean} + #[wasm_bindgen(js_name = "hasPrograms")] + pub fn has_programs(&self) -> bool { + !self.entries.is_empty() + } + + /// Check whether a specific program has been added. + /// + /// @param {string} name The program name. + /// @returns {boolean} + #[wasm_bindgen(js_name = "hasProgram")] + pub fn has_program(&self, name: &str) -> bool { + self.entries.contains_key(name) + } +} + +/// Extract source code from a JS value (plain string or object with `.source`). +/// +/// Supports both legacy format (`"source code"`) and structured format +/// (`{ source: "source code", ... }`). +pub(crate) fn extract_source(value: &Option<wasm_bindgen::JsValue>) -> Option<String> { + let value = value.as_ref()?; + // Plain string format: "source code" + if let Some(source) = value.as_string() { + return Some(source); + } + // Structured object format: { source: "source code", ... } + Reflect::get(value, &"source".into()).ok().and_then(|v| v.as_string()) +} + +// Internal methods (not exported to JS). +impl ProgramImports { + /// Resolve all programs and their keys into the given process. + /// + /// This performs the following steps: + /// 1. Iterates all entries and loads program source code into the process + /// (respecting transitive static imports via depth-first resolution). + /// 2. Inserts any pre-provided proving and verifying keys directly into + /// the process, avoiding expensive on-demand key synthesis. + pub(crate) fn resolve_into(&self, process: &mut ProcessNative) -> Result<(), String> { + // Phase 1: Load all programs into the process. + for (name, entry) in &self.entries { + if name == "credits.aleo" || entry.source.is_empty() { + continue; + } + let program = ProgramNative::from_str(&entry.source).map_err(|e| e.to_string())?; + if !process.contains_program(program.id()) { + log(&format!("Importing program: {name}")); + // Resolve transitive static imports first. + self.resolve_program_imports(process, &program)?; + log(&format!("Adding {name} to the process")); + process.add_program_with_edition(&program, 1).map_err(|e| e.to_string())?; + } + } + + // Phase 2: Insert keys into the process. + for (name, entry) in &self.entries { + if name == "credits.aleo" { + continue; + } + let program_id = ProgramIDNative::from_str(name).map_err(|e| e.to_string())?; + + for (fn_name, pk) in &entry.proving_keys { + let fn_id = IdentifierNative::from_str(fn_name).map_err(|e| e.to_string())?; + if ProgramManager::contains_key(process, &program_id, &fn_id) { + log(&format!("Key already exists for {name}/{fn_name}, skipping")); + continue; + } + log(&format!("Inserting proving key for {name}/{fn_name}")); + process.insert_proving_key(&program_id, &fn_id, pk.clone()).map_err(|e| e.to_string())?; + } + + for (fn_name, vk) in &entry.verifying_keys { + let fn_id = IdentifierNative::from_str(fn_name).map_err(|e| e.to_string())?; + let has_vk = process.get_stack(&program_id).map_or(false, |stack| stack.contains_verifying_key(&fn_id)); + if has_vk { + log(&format!("Verifying key already exists for {name}/{fn_name}, skipping")); + continue; + } + log(&format!("Inserting verifying key for {name}/{fn_name}")); + process.insert_verifying_key(&program_id, &fn_id, vk.clone()).map_err(|e| e.to_string())?; + } + } + + Ok(()) + } + + /// Recursively resolve a program's static imports in depth-first order. + fn resolve_program_imports(&self, process: &mut ProcessNative, program: &ProgramNative) -> Result<(), String> { + program.imports().keys().try_for_each(|program_id| { + let program_id_str = program_id.to_string(); + if program_id_str == "credits.aleo" { + return Ok(()); + } + if let Some(entry) = self.entries.get(&program_id_str) { + if !entry.source.is_empty() { + let import = ProgramNative::from_str(&entry.source).map_err(|e| e.to_string())?; + if !process.contains_program(import.id()) { + log(&format!("Importing program: {program_id_str}")); + self.resolve_program_imports(process, &import)?; + log(&format!("Adding {program_id_str} to the process")); + process.add_program_with_edition(&import, 1).map_err(|e| e.to_string())?; + } + } + } + Ok::<(), String>(()) + }) + } +} diff --git a/wasm/src/programs/manager/mod.rs b/wasm/src/programs/manager/mod.rs index b2f5ea954..6a615e0d5 100644 --- a/wasm/src/programs/manager/mod.rs +++ b/wasm/src/programs/manager/mod.rs @@ -17,11 +17,15 @@ mod authorize; mod deploy; mod execute; +pub mod imports; mod join; mod proving_request; mod split; mod transfer; +pub use imports::ProgramImports; +use imports::extract_source; + pub const DEFAULT_URL: &str = "https://api.provable.com/v2"; pub const LOCAL_URL: &str = "http://localhost:3030"; @@ -70,6 +74,8 @@ impl ProgramManager { /// @param {string} function_id The function to synthesize keys for /// @param {Array} inputs The inputs to the function /// @param {Object | undefined} imports The imports for the program + /// @param {ProgramImports | undefined} program_imports (optional) A ProgramImports builder with + /// pre-computed proving and verifying keys for imported programs. #[wasm_bindgen(js_name = "synthesizeKeyPair")] pub async fn synthesize_keypair( private_key: &PrivateKey, @@ -78,6 +84,7 @@ impl ProgramManager { inputs: js_sys::Array, imports: Option<Object>, edition: Option<u16>, + program_imports: Option<ProgramImports>, ) -> Result<KeyPair, String> { ProgramManager::execute_function_offline( private_key, @@ -92,6 +99,7 @@ impl ProgramManager { None, None, edition, + program_imports, ) .await? .get_keys() @@ -129,6 +137,11 @@ impl ProgramManager { } /// Resolve all imports from the imports object and load them into the process. + /// Accepts plain string values (`{ "name.aleo": "source" }`) or objects with a + /// `source` property (`{ "name.aleo": { source: "..." } }`). + /// + /// To provide proving/verifying keys for imported programs, use `ProgramImports` + /// (the builder) instead of this legacy Object path. pub(crate) fn resolve_imports(process: &mut ProcessNative, imports: Option<Object>) -> Result<(), String> { if let Some(imports) = imports { let keys = Object::keys(&imports); @@ -137,7 +150,8 @@ impl ProgramManager { if key == "credits.aleo" { continue; } - if let Some(source) = Reflect::get(&imports, &key.as_str().into()).ok().and_then(|v| v.as_string()) { + let value = Reflect::get(&imports, &key.as_str().into()).ok(); + if let Some(source) = extract_source(&value) { let import = ProgramNative::from_str(&source).map_err(|e| e.to_string())?; if !process.contains_program(import.id()) { log(&format!("Importing program: {key}")); @@ -162,10 +176,9 @@ impl ProgramManager { if program_id == "credits.aleo" { return Ok(()); } - if let Some(import_string) = - Reflect::get(imports, &program_id.as_str().into()).ok().and_then(|v| v.as_string()) - { - let import = ProgramNative::from_str(&import_string).map_err(|err| err.to_string())?; + let value = Reflect::get(imports, &program_id.as_str().into()).ok(); + if let Some(source) = extract_source(&value) { + let import = ProgramNative::from_str(&source).map_err(|err| err.to_string())?; if !process.contains_program(import.id()) { log(&format!("Importing program: {program_id}")); Self::resolve_program_imports(process, &import, imports)?; @@ -177,6 +190,17 @@ impl ProgramManager { }) } + /// Resolve imports using either the zero-cost `ProgramImports` builder or the + /// legacy `Object` format. If `program_imports` is provided, it takes precedence + /// over `imports`. + pub(crate) fn resolve_imports_or_builder( + process: &mut ProcessNative, + imports: Option<Object>, + program_imports: Option<&ProgramImports>, + ) -> Result<(), String> { + if let Some(pi) = program_imports { pi.resolve_into(process) } else { Self::resolve_imports(process, imports) } + } + pub(crate) fn validate_fee_record( fee_record: &Option<RecordPlaintext>, minimum_execution_cost: u64, @@ -379,4 +403,134 @@ constructor: assert!(process.contains_program(dd_constants.id()), "resolve_imports should load dd_constants.aleo"); assert!(process.contains_program(dd_ten.id()), "resolve_imports should load dd_ten.aleo"); } + + /// Test that resolve_imports correctly handles structured import entries + /// (objects with a `source` property) alongside plain string entries. + #[wasm_bindgen_test] + fn test_resolve_structured_imports() { + let multiply_program = ProgramNative::from_str(MULTIPLY_PROGRAM).unwrap(); + let addition_program = ProgramNative::from_str(ADDITION_PROGRAM).unwrap(); + + let imports = Object::new(); + + // Structured entry: object with `source` field. + let structured_entry = Object::new(); + Reflect::set(&structured_entry, &JsValue::from_str("source"), &JsValue::from_str(MULTIPLY_PROGRAM)).unwrap(); + Reflect::set(&imports, &JsValue::from_str("multiply_test.aleo"), &structured_entry.into()).unwrap(); + + // Plain string entry (backward compat). + Reflect::set(&imports, &JsValue::from_str("addition_test.aleo"), &JsValue::from_str(ADDITION_PROGRAM)).unwrap(); + + let mut process = ProcessNative::load_web().unwrap(); + ProgramManager::resolve_imports(&mut process, Some(imports)).unwrap(); + + assert!(process.contains_program(multiply_program.id()), "Structured import entry should load the program"); + assert!(process.contains_program(addition_program.id()), "Plain string import entry should still work"); + } + + /// Test that resolve_imports handles mixed structured and transitive imports. + #[wasm_bindgen_test] + fn test_resolve_structured_imports_with_transitive_deps() { + let multiply_program = ProgramNative::from_str(MULTIPLY_PROGRAM).unwrap(); + let double_program = ProgramNative::from_str(MULTIPLY_IMPORT_PROGRAM).unwrap(); + + let imports = Object::new(); + + // multiply_test.aleo as plain string (will be resolved as transitive dep). + Reflect::set(&imports, &JsValue::from_str("multiply_test.aleo"), &JsValue::from_str(MULTIPLY_PROGRAM)).unwrap(); + + // double_test.aleo as structured entry (imports multiply_test.aleo statically). + let structured_entry = Object::new(); + Reflect::set(&structured_entry, &JsValue::from_str("source"), &JsValue::from_str(MULTIPLY_IMPORT_PROGRAM)) + .unwrap(); + Reflect::set(&imports, &JsValue::from_str("double_test.aleo"), &structured_entry.into()).unwrap(); + + let mut process = ProcessNative::load_web().unwrap(); + ProgramManager::resolve_imports(&mut process, Some(imports)).unwrap(); + + assert!(process.contains_program(multiply_program.id()), "Transitive dependency should be loaded"); + assert!(process.contains_program(double_program.id()), "Structured import should be loaded"); + } + + // ─────────────────────────────────────────────────────────────────── + // ProgramImports builder tests + // ─────────────────────────────────────────────────────────────────── + + /// Test basic ProgramImports builder operations: new, add_program, has_programs, has_program. + #[wasm_bindgen_test] + fn test_program_imports_builder_basic() { + let mut builder = ProgramImports::new(); + assert!(!builder.has_programs(), "New builder should have no programs"); + assert!(!builder.has_program("multiply_test.aleo")); + + builder.add_program("multiply_test.aleo", MULTIPLY_PROGRAM); + assert!(builder.has_programs(), "Builder should have programs after add"); + assert!(builder.has_program("multiply_test.aleo")); + assert!(!builder.has_program("addition_test.aleo")); + + builder.add_program("addition_test.aleo", ADDITION_PROGRAM); + assert!(builder.has_program("addition_test.aleo")); + } + + /// Test that resolve_into loads programs into the process via the builder path. + #[wasm_bindgen_test] + fn test_program_imports_resolve_into() { + let mut builder = ProgramImports::new(); + builder.add_program("multiply_test.aleo", MULTIPLY_PROGRAM); + builder.add_program("addition_test.aleo", ADDITION_PROGRAM); + + let multiply_program = ProgramNative::from_str(MULTIPLY_PROGRAM).unwrap(); + let addition_program = ProgramNative::from_str(ADDITION_PROGRAM).unwrap(); + + let mut process = ProcessNative::load_web().unwrap(); + builder.resolve_into(&mut process).unwrap(); + + assert!(process.contains_program(multiply_program.id()), "multiply_test.aleo should be in process"); + assert!(process.contains_program(addition_program.id()), "addition_test.aleo should be in process"); + } + + /// Test that resolve_into handles transitive dependencies via the builder's own recursive resolution. + #[wasm_bindgen_test] + fn test_program_imports_resolve_into_transitive() { + let mut builder = ProgramImports::new(); + builder.add_program("multiply_test.aleo", MULTIPLY_PROGRAM); + builder.add_program("double_test.aleo", MULTIPLY_IMPORT_PROGRAM); + + let multiply_program = ProgramNative::from_str(MULTIPLY_PROGRAM).unwrap(); + let double_program = ProgramNative::from_str(MULTIPLY_IMPORT_PROGRAM).unwrap(); + + let mut process = ProcessNative::load_web().unwrap(); + builder.resolve_into(&mut process).unwrap(); + + assert!(process.contains_program(multiply_program.id()), "Transitive dep should be loaded"); + assert!(process.contains_program(double_program.id()), "Direct import should be loaded"); + } + + /// Test that resolve_imports_or_builder prefers the builder when both are provided. + #[wasm_bindgen_test] + fn test_resolve_imports_or_builder_prefers_builder() { + // Legacy Object has only multiply_test.aleo. + let legacy = Object::new(); + Reflect::set(&legacy, &JsValue::from_str("multiply_test.aleo"), &JsValue::from_str(MULTIPLY_PROGRAM)).unwrap(); + + // Builder has only addition_test.aleo. + let mut builder = ProgramImports::new(); + builder.add_program("addition_test.aleo", ADDITION_PROGRAM); + + let multiply_program = ProgramNative::from_str(MULTIPLY_PROGRAM).unwrap(); + let addition_program = ProgramNative::from_str(ADDITION_PROGRAM).unwrap(); + + let mut process = ProcessNative::load_web().unwrap(); + ProgramManager::resolve_imports_or_builder(&mut process, Some(legacy), Some(&builder)).unwrap(); + + // Builder should win — addition_test loaded, multiply_test NOT loaded. + assert!( + process.contains_program(addition_program.id()), + "Builder's program should be loaded when both are provided" + ); + assert!( + !process.contains_program(multiply_program.id()), + "Legacy Object's program should NOT be loaded when builder is provided" + ); + } } diff --git a/wasm/src/programs/manager/proving_request.rs b/wasm/src/programs/manager/proving_request.rs index c49fe4145..de788e452 100644 --- a/wasm/src/programs/manager/proving_request.rs +++ b/wasm/src/programs/manager/proving_request.rs @@ -47,6 +47,8 @@ impl ProgramManager { /// @param imports The imports to the program in the format {"programname.aleo":"aleo instructions source code"}. /// @param url The url of the Aleo network node to send the transaction to /// @param broadcast (optional) Flag to indicate if the transaction should be broadcast + /// @param {ProgramImports | undefined} program_imports (optional) A ProgramImports builder with + /// pre-computed proving and verifying keys for imported programs. /// @returns {Authorization} #[wasm_bindgen(js_name = buildProvingRequest)] #[allow(clippy::too_many_arguments)] @@ -63,6 +65,7 @@ impl ProgramManager { unchecked: bool, edition: Option<u16>, use_fee_master: bool, + program_imports: Option<ProgramImports>, ) -> Result<ProvingRequest, String> { log(&format!("Creating proving request for {function_name}")); let mut process_native = ProcessNative::load_web().map_err(|err| err.to_string())?; @@ -70,7 +73,7 @@ impl ProgramManager { log("Check program imports are valid and add them to the process"); let program_native = ProgramNative::from_str(program).map_err(|e| e.to_string())?; - ProgramManager::resolve_imports(process, imports.clone())?; + ProgramManager::resolve_imports_or_builder(process, imports.clone(), program_imports.as_ref())?; let rng = &mut StdRng::from_entropy(); // Convert the fee to microcredits. @@ -88,6 +91,7 @@ impl ProgramManager { program, imports, Some(edition), + program_imports, )?; // Authorize the fee. @@ -144,6 +148,7 @@ mod tests { false, Some(1), false, // use_fee_master: expect fee authorization + None, ) .await .unwrap(); @@ -198,6 +203,7 @@ mod tests { false, Some(1), true, // use_fee_master: no fee authorization + None, ) .await .unwrap(); From 3ba2b7a1c84201b75c5825026503ae6ca9ae93d8 Mon Sep 17 00:00:00 2001 From: Cameron Marshall <cameron.marshall12@gmail.com> Date: Fri, 6 Mar 2026 12:52:49 -0500 Subject: [PATCH 12/17] Fix review feedback: guard phase-2 key insertion, remove debug log, fix truthy check and malformed JSDoc --- sdk/src/program-manager.ts | 5 ++--- wasm/src/programs/manager/execute.rs | 2 +- wasm/src/programs/manager/imports.rs | 4 ++++ wasm/src/programs/manager/mod.rs | 4 +++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/sdk/src/program-manager.ts b/sdk/src/program-manager.ts index 3d5cb062c..c45d05f6e 100644 --- a/sdk/src/program-manager.ts +++ b/sdk/src/program-manager.ts @@ -3224,8 +3224,7 @@ class ProgramManager { } const programSource = program ? program.toString() : await this.networkClient.getProgram(programName, edition); const programImports = imports ? imports : await this.networkClient.getProgramImports(programSource); - console.log(JSON.stringify(programImports)); - if (Object.keys(programImports)) { + if (Object.keys(programImports).length > 0) { return WasmProgramManager.estimateFeeForAuthorization(authorization, programSource, programImports, edition, programImportsBuilder); } return WasmProgramManager.estimateFeeForAuthorization(authorization, programSource, imports, edition, programImportsBuilder); @@ -3273,7 +3272,7 @@ class ProgramManager { } const programSource = program ? program.toString() : await this.networkClient.getProgram(programName, edition); const programImports = imports ? imports : await this.networkClient.getProgramImports(programSource); - if (Object.keys(programImports)) { + if (Object.keys(programImports).length > 0) { return WasmProgramManager.estimateExecutionFee(programSource, functionName, programImports, edition, programImportsBuilder); } return WasmProgramManager.estimateExecutionFee(programSource, functionName, imports, edition, programImportsBuilder); diff --git a/wasm/src/programs/manager/execute.rs b/wasm/src/programs/manager/execute.rs index ea86ae80b..45e07dc75 100644 --- a/wasm/src/programs/manager/execute.rs +++ b/wasm/src/programs/manager/execute.rs @@ -610,7 +610,7 @@ impl ProgramManager { /// @param imports (optional) Provide a list of imports to use for the fee estimation in the /// form of a javascript object where the keys are a string of the program name and the values /// are a string representing the program source code \{ "hello.aleo": "hello.aleo source code" \} - /// @param edition { + /// @param {number | undefined} edition (optional) The edition of the program to use for fee estimation. /// @param {ProgramImports | undefined} program_imports (optional) A ProgramImports builder with /// pre-computed proving and verifying keys for imported programs. /// @returns {u64} Fee in microcredits diff --git a/wasm/src/programs/manager/imports.rs b/wasm/src/programs/manager/imports.rs index 00ba31baf..ecda5335f 100644 --- a/wasm/src/programs/manager/imports.rs +++ b/wasm/src/programs/manager/imports.rs @@ -225,6 +225,10 @@ impl ProgramImports { continue; } let program_id = ProgramIDNative::from_str(name).map_err(|e| e.to_string())?; + if !process.contains_program(&program_id) { + log(&format!("Program {name} not in process, skipping key insertion")); + continue; + } for (fn_name, pk) in &entry.proving_keys { let fn_id = IdentifierNative::from_str(fn_name).map_err(|e| e.to_string())?; diff --git a/wasm/src/programs/manager/mod.rs b/wasm/src/programs/manager/mod.rs index 6a615e0d5..92a5a6be3 100644 --- a/wasm/src/programs/manager/mod.rs +++ b/wasm/src/programs/manager/mod.rs @@ -146,7 +146,9 @@ impl ProgramManager { if let Some(imports) = imports { let keys = Object::keys(&imports); for i in 0..keys.length() { - let key = keys.get(i).as_string().unwrap_or_default(); + let Some(key) = keys.get(i).as_string() else { + continue; + }; if key == "credits.aleo" { continue; } From f8762c6dc59d781111a539302ec8f17dd8021855 Mon Sep 17 00:00:00 2001 From: Cameron Marshall <cameron.marshall12@gmail.com> Date: Tue, 10 Mar 2026 11:13:22 -0400 Subject: [PATCH 13/17] Refactor ProgramEntry to store native types, add edition support, fix review nits --- sdk/src/program-manager.ts | 2 +- sdk/tests/dynamic-dispatch.test.ts | 4 +- wasm/src/programs/manager/imports.rs | 161 ++++++++++++++++----------- wasm/src/programs/manager/mod.rs | 14 +-- 4 files changed, 106 insertions(+), 75 deletions(-) diff --git a/sdk/src/program-manager.ts b/sdk/src/program-manager.ts index c45d05f6e..f728309a1 100644 --- a/sdk/src/program-manager.ts +++ b/sdk/src/program-manager.ts @@ -90,7 +90,7 @@ interface DeployOptions { * ```ts * const [provingKey, verifyingKey] = await pm.synthesizeKeys(importSource, "fn_name", sampleInputs); * const builder = new ProgramImportsBuilder(); - * builder.addProgram("my_import.aleo", importSource); + * builder.addProgram("my_import.aleo", importSource, edition); * builder.addProvingKey("my_import.aleo", "fn_name", provingKey); * builder.addVerifyingKey("my_import.aleo", "fn_name", verifyingKey); * await pm.buildExecutionTransaction({ ..., programImportsBuilder: builder }); diff --git a/sdk/tests/dynamic-dispatch.test.ts b/sdk/tests/dynamic-dispatch.test.ts index 3b33d3b8a..0f48309e0 100644 --- a/sdk/tests/dynamic-dispatch.test.ts +++ b/sdk/tests/dynamic-dispatch.test.ts @@ -371,7 +371,7 @@ describe("Dynamic Dispatch", () => { it("should build an authorization using ProgramImportsBuilder instead of plain object", async () => { // Create a ProgramImportsBuilder and add the import via the builder API. const builder = new ProgramImportsBuilder(); - builder.addProgram("dd_constants.aleo", DD_CONSTANTS_PROGRAM); + builder.addProgram("dd_constants.aleo", DD_CONSTANTS_PROGRAM, 0); const authorization = await programManager.buildAuthorization({ programName: "dd_caller.aleo", @@ -412,7 +412,7 @@ describe("Dynamic Dispatch", () => { // Build a ProgramImportsBuilder with the program source AND pre-computed keys. const builder = new ProgramImportsBuilder(); - builder.addProgram("dd_constants.aleo", DD_CONSTANTS_PROGRAM); + builder.addProgram("dd_constants.aleo", DD_CONSTANTS_PROGRAM, 0); builder.addProvingKey("dd_constants.aleo", "get_value", provingKey); builder.addVerifyingKey("dd_constants.aleo", "get_value", verifyingKey); diff --git a/wasm/src/programs/manager/imports.rs b/wasm/src/programs/manager/imports.rs index ecda5335f..dead530fe 100644 --- a/wasm/src/programs/manager/imports.rs +++ b/wasm/src/programs/manager/imports.rs @@ -38,9 +38,22 @@ use super::ProgramManager; /// Internal storage for a single imported program's source and optional keys. #[derive(Clone)] struct ProgramEntry { - source: String, - proving_keys: HashMap<String, ProvingKeyNative>, - verifying_keys: HashMap<String, VerifyingKeyNative>, + program: ProgramNative, + edition: u16, + proving_keys: HashMap<IdentifierNative, ProvingKeyNative>, + verifying_keys: HashMap<IdentifierNative, VerifyingKeyNative>, +} + +impl ProgramEntry { + /// Get the program ID. + pub fn id(&self) -> &ProgramIDNative { + self.program.id() + } + + /// Get the program. + pub fn program(&self) -> &ProgramNative { + &self.program + } } /// A builder for specifying program imports with optional proving and verifying keys. @@ -62,7 +75,7 @@ struct ProgramEntry { #[wasm_bindgen] #[derive(Clone)] pub struct ProgramImports { - entries: HashMap<String, ProgramEntry>, + entries: HashMap<ProgramIDNative, ProgramEntry>, } #[wasm_bindgen] @@ -83,6 +96,8 @@ impl ProgramImports { /// { "my_program.aleo": { source: "program source..." } } /// ``` /// + /// Programs created via this method default to edition 1. + /// /// @param {Object} object A plain JavaScript object mapping program names to source code. /// @returns {ProgramImports} #[wasm_bindgen(js_name = "fromObject")] @@ -90,10 +105,10 @@ impl ProgramImports { let mut imports = Self::new(); let keys = Object::keys(&object); for i in 0..keys.length() { - let name = keys.get(i).as_string().unwrap_or_default(); + let Some(name) = keys.get(i).as_string() else { continue; }; let value = Reflect::get(&object, &name.as_str().into()).ok(); if let Some(source) = extract_source(&value) { - imports.add_program(&name, &source); + let _ = imports.add_program(&name, &source, None); } } imports @@ -101,13 +116,30 @@ impl ProgramImports { /// Add a program's source code to the imports. /// + /// The source is parsed and validated on insertion. Returns an error if the + /// source is not a valid Aleo program. + /// /// @param {string} name The program name (e.g., "my_program.aleo"). /// @param {string} source The program source code. + /// @param {number | undefined} edition The program edition (defaults to 1). #[wasm_bindgen(js_name = "addProgram")] - pub fn add_program(&mut self, name: &str, source: &str) { - self.entries.entry(name.to_string()).and_modify(|e| e.source = source.to_string()).or_insert_with(|| { - ProgramEntry { source: source.to_string(), proving_keys: HashMap::new(), verifying_keys: HashMap::new() } - }); + pub fn add_program(&mut self, name: &str, source: &str, edition: Option<u16>) -> Result<(), String> { + let program = ProgramNative::from_str(source).map_err(|e| e.to_string())?; + let program_id = ProgramIDNative::from_str(name).map_err(|e| e.to_string())?; + let edition = edition.unwrap_or(1); + self.entries + .entry(program_id) + .and_modify(|e| { + e.program = program.clone(); + e.edition = edition; + }) + .or_insert_with(|| ProgramEntry { + program, + edition, + proving_keys: HashMap::new(), + verifying_keys: HashMap::new(), + }); + Ok(()) } /// Add a proving key for a function or record within an imported program. @@ -119,13 +151,14 @@ impl ProgramImports { /// @param {string} identifier The function name or record name the key belongs to. /// @param {ProvingKey} key The proving key. #[wasm_bindgen(js_name = "addProvingKey")] - pub fn add_proving_key(&mut self, program_name: &str, identifier: &str, key: ProvingKey) { - let entry = self.entries.entry(program_name.to_string()).or_insert_with(|| ProgramEntry { - source: String::new(), - proving_keys: HashMap::new(), - verifying_keys: HashMap::new(), - }); - entry.proving_keys.insert(identifier.to_string(), ProvingKeyNative::from(key)); + pub fn add_proving_key(&mut self, program_name: &str, identifier: &str, key: ProvingKey) -> Result<(), String> { + let program_id = ProgramIDNative::from_str(program_name).map_err(|e| e.to_string())?; + let fn_id = IdentifierNative::from_str(identifier).map_err(|e| e.to_string())?; + let entry = self.entries.get_mut(&program_id).ok_or_else(|| { + format!("Program '{program_name}' must be added via addProgram before adding keys") + })?; + entry.proving_keys.insert(fn_id, ProvingKeyNative::from(key)); + Ok(()) } /// Add a verifying key for a function or record within an imported program. @@ -137,13 +170,14 @@ impl ProgramImports { /// @param {string} identifier The function name or record name the key belongs to. /// @param {VerifyingKey} key The verifying key. #[wasm_bindgen(js_name = "addVerifyingKey")] - pub fn add_verifying_key(&mut self, program_name: &str, identifier: &str, key: VerifyingKey) { - let entry = self.entries.entry(program_name.to_string()).or_insert_with(|| ProgramEntry { - source: String::new(), - proving_keys: HashMap::new(), - verifying_keys: HashMap::new(), - }); - entry.verifying_keys.insert(identifier.to_string(), VerifyingKeyNative::from(key)); + pub fn add_verifying_key(&mut self, program_name: &str, identifier: &str, key: VerifyingKey) -> Result<(), String> { + let program_id = ProgramIDNative::from_str(program_name).map_err(|e| e.to_string())?; + let fn_id = IdentifierNative::from_str(identifier).map_err(|e| e.to_string())?; + let entry = self.entries.get_mut(&program_id).ok_or_else(|| { + format!("Program '{program_name}' must be added via addProgram before adding keys") + })?; + entry.verifying_keys.insert(fn_id, VerifyingKeyNative::from(key)); + Ok(()) } /// Convert this ProgramImports to a plain JavaScript object containing only @@ -154,10 +188,10 @@ impl ProgramImports { #[wasm_bindgen(js_name = "toObject")] pub fn to_object(&self) -> Object { let obj = Object::new(); - for (name, entry) in &self.entries { - if !entry.source.is_empty() { - Reflect::set(&obj, &name.as_str().into(), &entry.source.as_str().into()).unwrap(); - } + for (program_id, entry) in &self.entries { + let source = entry.program().to_string(); + let name = program_id.to_string(); + Reflect::set(&obj, &name.as_str().into(), &source.as_str().into()).unwrap(); } obj } @@ -176,7 +210,7 @@ impl ProgramImports { /// @returns {boolean} #[wasm_bindgen(js_name = "hasProgram")] pub fn has_program(&self, name: &str) -> bool { - self.entries.contains_key(name) + ProgramIDNative::from_str(name).map_or(false, |id| self.entries.contains_key(&id)) } } @@ -204,51 +238,50 @@ impl ProgramImports { /// 2. Inserts any pre-provided proving and verifying keys directly into /// the process, avoiding expensive on-demand key synthesis. pub(crate) fn resolve_into(&self, process: &mut ProcessNative) -> Result<(), String> { + let credits_id = ProgramIDNative::from_str("credits.aleo").map_err(|e| e.to_string())?; + // Phase 1: Load all programs into the process. - for (name, entry) in &self.entries { - if name == "credits.aleo" || entry.source.is_empty() { + for (program_id, entry) in &self.entries { + if program_id == &credits_id { continue; } - let program = ProgramNative::from_str(&entry.source).map_err(|e| e.to_string())?; + let program = entry.program(); if !process.contains_program(program.id()) { - log(&format!("Importing program: {name}")); + log(&format!("Importing program: {program_id}")); // Resolve transitive static imports first. - self.resolve_program_imports(process, &program)?; - log(&format!("Adding {name} to the process")); - process.add_program_with_edition(&program, 1).map_err(|e| e.to_string())?; + self.resolve_program_imports(process, program)?; + log(&format!("Adding {program_id} to the process")); + process.add_program_with_edition(program, entry.edition).map_err(|e| e.to_string())?; } } // Phase 2: Insert keys into the process. - for (name, entry) in &self.entries { - if name == "credits.aleo" { + for (program_id, entry) in &self.entries { + if program_id == &credits_id { continue; } - let program_id = ProgramIDNative::from_str(name).map_err(|e| e.to_string())?; - if !process.contains_program(&program_id) { - log(&format!("Program {name} not in process, skipping key insertion")); + if !process.contains_program(program_id) { + log(&format!("Program {program_id} not in process, skipping key insertion")); continue; } - for (fn_name, pk) in &entry.proving_keys { - let fn_id = IdentifierNative::from_str(fn_name).map_err(|e| e.to_string())?; - if ProgramManager::contains_key(process, &program_id, &fn_id) { - log(&format!("Key already exists for {name}/{fn_name}, skipping")); + for (fn_id, pk) in &entry.proving_keys { + if ProgramManager::contains_key(process, program_id, fn_id) { + log(&format!("Key already exists for {program_id}/{fn_id}, skipping")); continue; } - log(&format!("Inserting proving key for {name}/{fn_name}")); - process.insert_proving_key(&program_id, &fn_id, pk.clone()).map_err(|e| e.to_string())?; + log(&format!("Inserting proving key for {program_id}/{fn_id}")); + process.insert_proving_key(program_id, fn_id, pk.clone()).map_err(|e| e.to_string())?; } - for (fn_name, vk) in &entry.verifying_keys { - let fn_id = IdentifierNative::from_str(fn_name).map_err(|e| e.to_string())?; - let has_vk = process.get_stack(&program_id).map_or(false, |stack| stack.contains_verifying_key(&fn_id)); + for (fn_id, vk) in &entry.verifying_keys { + let has_vk = process.get_stack(program_id).map_or(false, |stack| stack.contains_verifying_key(fn_id)); if has_vk { - log(&format!("Verifying key already exists for {name}/{fn_name}, skipping")); + log(&format!("Verifying key already exists for {program_id}/{fn_id}, skipping")); continue; } - log(&format!("Inserting verifying key for {name}/{fn_name}")); - process.insert_verifying_key(&program_id, &fn_id, vk.clone()).map_err(|e| e.to_string())?; + log(&format!("Inserting verifying key for {program_id}/{fn_id}")); + process.insert_verifying_key(program_id, fn_id, vk.clone()).map_err(|e| e.to_string())?; } } @@ -257,20 +290,18 @@ impl ProgramImports { /// Recursively resolve a program's static imports in depth-first order. fn resolve_program_imports(&self, process: &mut ProcessNative, program: &ProgramNative) -> Result<(), String> { - program.imports().keys().try_for_each(|program_id| { - let program_id_str = program_id.to_string(); - if program_id_str == "credits.aleo" { + let credits_id = ProgramIDNative::from_str("credits.aleo").map_err(|e| e.to_string())?; + program.imports().keys().try_for_each(|import_id| { + if *import_id == credits_id { return Ok(()); } - if let Some(entry) = self.entries.get(&program_id_str) { - if !entry.source.is_empty() { - let import = ProgramNative::from_str(&entry.source).map_err(|e| e.to_string())?; - if !process.contains_program(import.id()) { - log(&format!("Importing program: {program_id_str}")); - self.resolve_program_imports(process, &import)?; - log(&format!("Adding {program_id_str} to the process")); - process.add_program_with_edition(&import, 1).map_err(|e| e.to_string())?; - } + if let Some(entry) = self.entries.get(import_id) { + let import = entry.program(); + if !process.contains_program(import.id()) { + log(&format!("Importing program: {import_id}")); + self.resolve_program_imports(process, import)?; + log(&format!("Adding {import_id} to the process")); + process.add_program_with_edition(import, entry.edition).map_err(|e| e.to_string())?; } } Ok::<(), String>(()) diff --git a/wasm/src/programs/manager/mod.rs b/wasm/src/programs/manager/mod.rs index 92a5a6be3..58d149eb9 100644 --- a/wasm/src/programs/manager/mod.rs +++ b/wasm/src/programs/manager/mod.rs @@ -465,12 +465,12 @@ constructor: assert!(!builder.has_programs(), "New builder should have no programs"); assert!(!builder.has_program("multiply_test.aleo")); - builder.add_program("multiply_test.aleo", MULTIPLY_PROGRAM); + builder.add_program("multiply_test.aleo", MULTIPLY_PROGRAM, None).unwrap(); assert!(builder.has_programs(), "Builder should have programs after add"); assert!(builder.has_program("multiply_test.aleo")); assert!(!builder.has_program("addition_test.aleo")); - builder.add_program("addition_test.aleo", ADDITION_PROGRAM); + builder.add_program("addition_test.aleo", ADDITION_PROGRAM, None).unwrap(); assert!(builder.has_program("addition_test.aleo")); } @@ -478,8 +478,8 @@ constructor: #[wasm_bindgen_test] fn test_program_imports_resolve_into() { let mut builder = ProgramImports::new(); - builder.add_program("multiply_test.aleo", MULTIPLY_PROGRAM); - builder.add_program("addition_test.aleo", ADDITION_PROGRAM); + builder.add_program("multiply_test.aleo", MULTIPLY_PROGRAM, None).unwrap(); + builder.add_program("addition_test.aleo", ADDITION_PROGRAM, None).unwrap(); let multiply_program = ProgramNative::from_str(MULTIPLY_PROGRAM).unwrap(); let addition_program = ProgramNative::from_str(ADDITION_PROGRAM).unwrap(); @@ -495,8 +495,8 @@ constructor: #[wasm_bindgen_test] fn test_program_imports_resolve_into_transitive() { let mut builder = ProgramImports::new(); - builder.add_program("multiply_test.aleo", MULTIPLY_PROGRAM); - builder.add_program("double_test.aleo", MULTIPLY_IMPORT_PROGRAM); + builder.add_program("multiply_test.aleo", MULTIPLY_PROGRAM, None).unwrap(); + builder.add_program("double_test.aleo", MULTIPLY_IMPORT_PROGRAM, None).unwrap(); let multiply_program = ProgramNative::from_str(MULTIPLY_PROGRAM).unwrap(); let double_program = ProgramNative::from_str(MULTIPLY_IMPORT_PROGRAM).unwrap(); @@ -517,7 +517,7 @@ constructor: // Builder has only addition_test.aleo. let mut builder = ProgramImports::new(); - builder.add_program("addition_test.aleo", ADDITION_PROGRAM); + builder.add_program("addition_test.aleo", ADDITION_PROGRAM, None).unwrap(); let multiply_program = ProgramNative::from_str(MULTIPLY_PROGRAM).unwrap(); let addition_program = ProgramNative::from_str(ADDITION_PROGRAM).unwrap(); From 2173787e05469a180bed99133a58bda308e7aaf9 Mon Sep 17 00:00:00 2001 From: Cameron Marshall <cameron.marshall12@gmail.com> Date: Wed, 11 Mar 2026 12:44:37 -0400 Subject: [PATCH 14/17] Add KeyStore integration with auto-persist for top-level and import keys across all execution paths --- create-leo-app/template-node-ts/src/index.ts | 133 +-- create-leo-app/template-node-ts/tsconfig.json | 1 + sdk/src/program-manager.ts | 357 +++++++- sdk/tests/program-imports.test.ts | 672 +++++++++++++++ wasm/src/programs/manager/execute.rs | 768 ++++++++++++------ wasm/src/programs/manager/imports.rs | 194 +++-- wasm/src/programs/manager/mod.rs | 138 +++- 7 files changed, 1860 insertions(+), 403 deletions(-) create mode 100644 sdk/tests/program-imports.test.ts diff --git a/create-leo-app/template-node-ts/src/index.ts b/create-leo-app/template-node-ts/src/index.ts index 72e9dfa53..163055f97 100644 --- a/create-leo-app/template-node-ts/src/index.ts +++ b/create-leo-app/template-node-ts/src/index.ts @@ -1,9 +1,9 @@ import { Account, AleoKeyProvider, - AleoKeyProviderParams, ConfirmedTransactionJSON, initThreadPool, + LocalFileKeyStore, ProgramManager, Transaction, } from "@provablehq/sdk/testnet.js"; @@ -33,106 +33,115 @@ const programManager = new ProgramManager(); const account = new Account(); programManager.setAccount(account); -// Create a key provider in order to re-use the same key for each execution +// Create a key provider with in-memory caching for the current session const keyProvider = new AleoKeyProvider(); keyProvider.useCache(true); programManager.setKeyProvider(keyProvider); -async function localProgramExecution(program: string, programName: string, aleoFunction: string, inputs: string[]) { - // Pre-synthesize the program keys and then cache them in memory using the key provider. - try { - const keyPair = await programManager.synthesizeKeys(program, aleoFunction, inputs); +// Set up a persistent KeyStore to cache proving and verifying keys on disk. +// Keys are stored in the .aleo/ directory and survive across sessions. +const keyStore = new LocalFileKeyStore(); +programManager.setKeyStore(keyStore); - programManager.keyProvider.cacheKeys(`${programName}:${aleoFunction}`, keyPair); - - } catch (e) { - throw new Error(`Failed to synthesize keys: ${e.message}`); - } +// Run offline execution, deployment, and online execution of hello_hello.aleo. +async function run(online: boolean = false) { + const programName = "hello_hello.aleo"; + const hello_hello_program = generateHelloHelloSource(programName); + const functionName = "hello"; + const inputs = ["5u32", "5u32"]; - // Specify parameters for the key provider to use search for program keys. In particular specify the cache key - // that was used to cache the keys in the previous step. - const keyProviderParams = new AleoKeyProviderParams({cacheKey: `${programName}:${aleoFunction}`}); + // --- STEP 1: Execute offline (cold — keys are synthesized and auto-persisted) --- + console.log(""); + console.log("// --- STEP 1: Execute offline (cold run — keys synthesized and auto-persisted). --- //"); + let start = Date.now(); - // Execute once using the key provider params defined above. This will use the cached proving keys and make - // execution significantly faster. + // The first run() synthesizes keys inside WASM and automatically persists them + // to the KeyStore. No explicit synthesizeKeys() call is needed. let executionResponse = await programManager.run( - program, - aleoFunction, + hello_hello_program, + functionName, inputs, true, - undefined, - keyProviderParams, ); - console.log("hello_hello/hello executed - result:", executionResponse.getOutputs()); + const coldTime = Date.now() - start; + console.log(`Cold execution completed in ${coldTime / 1000}s — result: ${executionResponse.getOutputs()}`); - // Verify the execution using the verifying key that was generated earlier. + // Verify the execution. if (programManager.verifyExecution(executionResponse, 9_000_000)) { - console.log("hello_hello/hello execution verified!"); + console.log("Execution verified!"); } else { - throw("Execution failed verification!"); + throw new Error("Execution failed verification!"); } -} -// Run a deployment and both online and offline executions. -async function run(online: boolean = false) { - // Generate the hello_hello.aleo program source code and inputs. - let programName = `hello_hello.aleo`; - let hello_hello_program = generateHelloHelloSource(programName); - const functionName = "hello"; - const inputs = ["5u32", "5u32"]; + // Confirm keys were auto-persisted to the KeyStore. + const proverStored = await keyStore.has(`${programName}.${functionName}.prover`); + const verifierStored = await keyStore.has(`${programName}.${functionName}.verifier`); + console.log(`Keys auto-persisted to KeyStore: prover=${proverStored}, verifier=${verifierStored}`); + // --- STEP 2: Execute again offline (warm — keys are loaded from KeyStore, no synthesis) --- console.log(""); - console.log("// --- STEP 1: Execute the program offline to test it gives the expected results. --- //"); - // Execute the program locally. - console.log(`Executing ${programName}/hello offline`); - let start = Date.now(); - const result = await localProgramExecution(hello_hello_program, programName, functionName, inputs); - console.log(`✅ Local execute finished in ${(Date.now() - start)/1000}s`); + console.log("// --- STEP 2: Execute offline again (warm run — keys loaded from KeyStore). --- //"); + start = Date.now(); + + // No synthesizeKeys call needed — the ProgramManager automatically checks the KeyStore + // for cached proving and verifying keys before execution. + executionResponse = await programManager.run( + hello_hello_program, + functionName, + inputs, + true, + ); + const warmTime = Date.now() - start; + console.log(`Warm execution completed in ${warmTime / 1000}s — result: ${executionResponse.getOutputs()}`); + + if (programManager.verifyExecution(executionResponse, 9_000_000)) { + console.log("Execution verified!"); + } else { + throw new Error("Execution failed verification!"); + } + + if (warmTime < coldTime) { + console.log(`KeyStore speedup: ${(coldTime / warmTime).toFixed(1)}x faster on warm run`); + } + // --- STEP 3: Build a deployment transaction. --- console.log(""); - console.log("// --- STEP 2: Build the deployment transaction. --- //"); + console.log("// --- STEP 3: Build the deployment transaction. --- //"); start = Date.now(); - programName = `hello_hello_${Math.floor(Math.random() * 65536)}.aleo`; - hello_hello_program = generateHelloHelloSource(programName); + const deployProgramName = `hello_hello_${Math.floor(Math.random() * 65536)}.aleo`; + const deployProgram = generateHelloHelloSource(deployProgramName); const deploymentTx: Transaction = await programManager.buildDeploymentTransaction( - hello_hello_program, + deployProgram, 0, false, - ) - console.log(`✅ Deployment transaction built in ${(Date.now() - start)/1000}s`); + ); + console.log(`Deployment transaction built in ${(Date.now() - start) / 1000}s`); // If the deployment flag is set to true, deploy the program on testnet (requires aleo credits). if (online) { const txId: string = await programManager.networkClient.submitTransaction(deploymentTx); const confirmedTx: ConfirmedTransactionJSON = await programManager.networkClient.waitForTransactionConfirmation(txId); if (txId === confirmedTx.transaction.id) { - console.log(`Program ${programName} deployed to Aleo Testnet successfully!`); + console.log(`Program ${deployProgramName} deployed to Aleo Testnet successfully!`); } - } else { - programName = `hello_hello.aleo`; - hello_hello_program = generateHelloHelloSource(programName); } + // --- STEP 4: Build an online execution transaction. --- + // Keys from Step 1 are automatically loaded from KeyStore — no re-synthesis. console.log(""); - console.log("// --- STEP 3: Execute the program ONLINE. --- //"); - - // If the program was actually deployed, execute it online. Otherwise, execute an equivalent - // program with the same logic. - console.log(`Executing ${programName}/hello online on the aleo network`); + console.log("// --- STEP 4: Build an online execution transaction (keys from KeyStore). --- //"); start = Date.now(); - const keySearchParams = new AleoKeyProviderParams({cacheKey: `${programName}:${functionName}`}); const executionTx: Transaction = await programManager.buildExecutionTransaction( { programName, functionName, priorityFee: 0, privateFee: false, - inputs: inputs, - keySearchParams, - program: hello_hello_program - } - ) - console.log(`✅ Online execution of ${programName} built in ${(Date.now() - start)/1000}s`); + inputs, + program: hello_hello_program, + }, + ); + console.log(`Online execution transaction built in ${(Date.now() - start) / 1000}s`); // If the online option is specified, submit the transaction to the network. if (online) { @@ -142,7 +151,9 @@ async function run(online: boolean = false) { console.log(`Program ${programName}/hello executed successfully!`); } } + + // Clean up persisted keys + await keyStore.clear(); } -// Run the offline execution, deployment, and online execution of hello_hello.aleo. await run(false); diff --git a/create-leo-app/template-node-ts/tsconfig.json b/create-leo-app/template-node-ts/tsconfig.json index 052bb2cc1..8cc4fc219 100644 --- a/create-leo-app/template-node-ts/tsconfig.json +++ b/create-leo-app/template-node-ts/tsconfig.json @@ -6,6 +6,7 @@ /* Module Resolution Options */ "moduleResolution": "bundler", + "customConditions": ["node"], "esModuleInterop": true, /* Advanced Options */ diff --git a/sdk/src/program-manager.ts b/sdk/src/program-manager.ts index f728309a1..d7edd853e 100644 --- a/sdk/src/program-manager.ts +++ b/sdk/src/program-manager.ts @@ -12,6 +12,7 @@ import { AleoKeyProvider, AleoKeyProviderParams, } from "./keys/provider/memory.js"; +import { KeyStore, KeyLocator } from "./keys/keystore/interface.js"; import { FunctionKeyPair @@ -161,12 +162,18 @@ interface FeeAuthorizationOptions { * Represents the options for executing a transaction on the Aleo Network from an authorization. * * @property {string} programName - The name of the program containing the function to be executed. + * @property {Authorization} authorization - The authorization to execute. + * @property {Authorization} [feeAuthorization] - Optional fee authorization. * @property {KeySearchParams} [keySearchParams] - Optional parameters for finding the matching proving & verifying keys for the function. * @property {ProvingKey} [provingKey] - Optional proving key to use for the transaction. * @property {VerifyingKey} [verifyingKey] - Optional verifying key to use for the transaction. * @property {OfflineQuery} [offlineQuery] - Optional offline query if creating transactions in an offline environment. * @property {string | Program} [program] - Optional program source code to use for the transaction. * @property {ProgramImports} [imports] - Optional programs that the program being executed imports. + * @property {number} [edition] - Edition of the program to execute the function in. + * @property {ProgramImportsBuilder} [programImportsBuilder] - A builder carrying imported programs + * with optional pre-computed proving/verifying keys. Takes precedence over `imports`. + * See {@link ExecuteOptions.programImportsBuilder} for a usage example. */ interface ExecuteAuthorizationOptions { programName: string; @@ -178,6 +185,7 @@ interface ExecuteAuthorizationOptions { offlineQuery?: OfflineQuery; program?: string | Program; imports?: ProgramImports; + edition?: number; programImportsBuilder?: ProgramImportsBuilder; } @@ -252,6 +260,7 @@ class ProgramManager { networkClient: AleoNetworkClient; recordProvider: RecordProvider | undefined; inclusionKeysLoaded: boolean = false; + private _keyStore: KeyStore | undefined; /** Create a new instance of the ProgramManager * @@ -303,6 +312,17 @@ class ProgramManager { this.keyProvider = keyProvider; } + /** + * Set a KeyStore for persistent proving and verifying key storage. + * When configured, ProgramManager automatically loads cached keys before + * execution and persists synthesized keys after execution. + * + * @param {KeyStore} keyStore + */ + setKeyStore(keyStore: KeyStore) { + this._keyStore = keyStore; + } + /** * Set the host peer to use for transaction submission to the Aleo network * @@ -341,6 +361,194 @@ class ProgramManager { this.networkClient.headers[headerName] = value; } + /** + * Build a ProgramImportsBuilder internally from program imports and KeyStore keys. + * + * Merges user-provided imports (which may include dynamic dispatch targets) with + * auto-fetched static imports from the network, then queries KeyStore for proving + * and verifying keys for all imported programs. + */ + private async buildProgramImports( + program: string | Program, + imports?: ProgramImports, + ): Promise<ProgramImportsBuilder> { + const builder = new ProgramImportsBuilder(); + const programSource = typeof program === "string" ? program : program.toString(); + const programObj = Program.fromString(programSource); + + // Check if there are any imports to resolve + const importNames = programObj.getImports(); + if (importNames.length === 0 && (!imports || Object.keys(imports).length === 0)) { + return builder; + } + + // Fetch static imports from the network, then overlay user-provided imports + // (which may include dynamic dispatch targets or custom overrides) on top. + let resolvedImports: ProgramImports = {}; + if (importNames.length > 0) { + try { + resolvedImports = await this.networkClient.getProgramImports(programSource); + } catch (e) { + // Non-blocking — if we can't fetch imports, proceed with what we have + } + } + if (imports) { + resolvedImports = { ...resolvedImports, ...imports }; + } + + // Add each import program and query KeyStore for its keys + for (const [name, importProgram] of Object.entries(resolvedImports)) { + const importProgramStr = typeof importProgram === "string" ? importProgram : importProgram.toString(); + builder.addProgram(name, importProgramStr); + await this.loadKeysFromStore(builder, name, importProgramStr); + } + + return builder; + } + + /** + * Load proving and verifying keys from the KeyStore for a given program + * and add them to the ProgramImportsBuilder. + */ + private async loadKeysFromStore( + builder: ProgramImportsBuilder, + programName: string, + programSource: string, + ): Promise<void> { + let keyStore: KeyStore | undefined = this._keyStore; + if (!keyStore) { + try { + keyStore = await this.keyProvider?.keyStore?.(); + } catch { + return; + } + } + if (!keyStore) return; + + const program = Program.fromString(programSource); + const functions: string[] = Array.from(program.getFunctions()); + + for (const fnName of functions) { + const proverLocator = `${programName}.${fnName}.prover`; + const verifierLocator = `${programName}.${fnName}.verifier`; + + try { + if (await keyStore.has(proverLocator)) { + const pk = await keyStore.getProvingKey({ locator: proverLocator }); + if (pk) builder.addProvingKey(programName, fnName, pk); + } + if (await keyStore.has(verifierLocator)) { + const vk = await keyStore.getVerifyingKey({ locator: verifierLocator }); + if (vk) builder.addVerifyingKey(programName, fnName, vk); + } + } catch (e) { + // Non-blocking — key loading failures don't prevent execution + console.debug(`Failed to load keys for ${programName}/${fnName}: ${e}`); + } + } + } + + /** + * Persist synthesized keys from a mutated ProgramImportsBuilder to the + * configured KeyStore. After WASM execution, the builder is populated with + * any keys that were synthesized during the call. This method iterates all + * import programs and their functions, extracting and persisting each key pair. + * + * Uses `.take()` semantics — keys are removed from the builder to free WASM memory. + */ + private async persistExtractedKeys( + builder: ProgramImportsBuilder, + imports?: ProgramImports, + topLevelProgram?: string, + topLevelFunction?: string, + ): Promise<void> { + let keyStore: KeyStore | undefined = this._keyStore; + if (!keyStore) { + try { + keyStore = await this.keyProvider?.keyStore?.(); + } catch { + return; + } + } + if (!keyStore) return; + + // Persist import program keys. + const resolvedImports = imports ?? {}; + for (const [name, importProgram] of Object.entries(resolvedImports)) { + if (!builder.contains(name)) continue; + + const importProgramStr = typeof importProgram === "string" ? importProgram : importProgram.toString(); + const program = Program.fromString(importProgramStr); + const functions: string[] = Array.from(program.getFunctions()); + + for (const fnName of functions) { + try { + const pk = builder.getProvingKey(name, fnName); + const vk = builder.getVerifyingKey(name, fnName); + if (pk && vk) { + const proverLocator: KeyLocator = { locator: `${name}.${fnName}.prover` }; + const verifierLocator: KeyLocator = { locator: `${name}.${fnName}.verifier` }; + await keyStore.setKeys(proverLocator, verifierLocator, [pk, vk]); + } + } catch (e) { + console.debug(`Failed to persist keys for ${name}/${fnName}: ${e}`); + } + } + } + + // Persist top-level program keys. + if (topLevelProgram && topLevelFunction) { + const programName = Program.fromString(topLevelProgram).id(); + if (builder.contains(programName)) { + try { + const pk = builder.getProvingKey(programName, topLevelFunction); + const vk = builder.getVerifyingKey(programName, topLevelFunction); + if (pk && vk) { + const proverLocator: KeyLocator = { locator: `${programName}.${topLevelFunction}.prover` }; + const verifierLocator: KeyLocator = { locator: `${programName}.${topLevelFunction}.verifier` }; + await keyStore.setKeys(proverLocator, verifierLocator, [pk, vk]); + } + } catch (e) { + console.debug(`Failed to persist top-level keys for ${programName}/${topLevelFunction}: ${e}`); + } + } + } + } + + /** + * Resolve proving and verifying keys for a top-level program function. + * Checks KeyStore first (preferred), then falls back to KeyProvider (legacy). + * Returns undefined if keys are not found in either location. + */ + private async resolveTopLevelKeys( + programName: string, + functionName: string, + keySearchParams?: KeySearchParams, + ): Promise<FunctionKeyPair | undefined> { + // 1. Try KeyStore first (preferred path) + const keyStore = this._keyStore ?? await this.keyProvider?.keyStore?.().catch(() => undefined); + if (keyStore) { + try { + const proverLocator = `${programName}.${functionName}.prover`; + const verifierLocator = `${programName}.${functionName}.verifier`; + if (await keyStore.has(proverLocator) && await keyStore.has(verifierLocator)) { + const pk = await keyStore.getProvingKey({ locator: proverLocator }); + const vk = await keyStore.getVerifyingKey({ locator: verifierLocator }); + if (pk && vk) return [pk, vk]; + } + } catch { + // Fall through to KeyProvider + } + } + + // 2. Fall back to KeyProvider (legacy path) + try { + return <FunctionKeyPair>(await this.keyProvider.functionKeys(keySearchParams)); + } catch { + return undefined; + } + } + /** * Set the inclusion prover into the wasm memory. This should be done prior to any execution of a function with a * private record. @@ -830,7 +1038,6 @@ class ProgramManager { let programName = options.programName; let imports = options.imports; let edition = options.edition; - const programImportsBuilder = options.programImportsBuilder; let programObject; // Ensure the function exists on the network @@ -872,6 +1079,9 @@ class ProgramManager { } } + const programImportsBuilder = options.programImportsBuilder + ?? await this.buildProgramImports(program, imports); + // Get the private key from the account if it is not provided in the parameters let executionPrivateKey = privateKey; if ( @@ -898,15 +1108,15 @@ class ProgramManager { } const [feeProvingKey, feeVerifyingKey] = feeKeys; - // If the function proving and verifying keys are not provided, attempt to find them using the key provider + // If the function proving and verifying keys are not provided, check KeyStore + // first (preferred), then fall back to KeyProvider (legacy). if (!provingKey || !verifyingKey) { - try { - [provingKey, verifyingKey] = <FunctionKeyPair>( - await this.keyProvider.functionKeys(keySearchParams) - ); - } catch (e) { + const keys = await this.resolveTopLevelKeys(programName, functionName, keySearchParams); + if (keys) { + [provingKey, verifyingKey] = keys; + } else { console.log( - `Function keys not found. Key finder response: '${e}'. The function keys will be synthesized`, + "Function keys not found in KeyStore or KeyProvider. The function keys will be synthesized", ); } } @@ -963,8 +1173,10 @@ class ProgramManager { } } - // Build an execution transaction - return await WasmProgramManager.buildExecutionTransaction( + // Build an execution transaction. + // Use the key-extracting variant when we have a ProgramImportsBuilder, + // which mutates it with synthesized keys for KeyStore persistence. + const transaction = await WasmProgramManager.buildExecutionTransactionWithImports( executionPrivateKey, program, functionName, @@ -972,7 +1184,6 @@ class ProgramManager { priorityFee, feeRecord, this.host, - imports, provingKey, verifyingKey, feeProvingKey, @@ -981,6 +1192,11 @@ class ProgramManager { edition, programImportsBuilder, ); + + // Auto-persist synthesized keys (both imports and top-level) from the mutated builder to KeyStore. + await this.persistExtractedKeys(programImportsBuilder, imports, program, functionName); + + return transaction; } /** @@ -1065,7 +1281,6 @@ class ProgramManager { const feeAuthorization = options.feeAuthorization; const keySearchParams = options.keySearchParams; const offlineQuery = options.offlineQuery; - const programImportsBuilder = options.programImportsBuilder; let provingKey = options.provingKey; let verifyingKey = options.verifyingKey; let program = options.program; @@ -1086,6 +1301,9 @@ class ProgramManager { program = program.toString(); } + const programImportsBuilder = options.programImportsBuilder + ?? await this.buildProgramImports(program, imports); + // Get the fee proving and verifying keys from the key provider. let feeKeys; const privateFee = feeAuthorization ? feeAuthorization.isFeePrivate() : false; @@ -1100,19 +1318,33 @@ class ProgramManager { } const [feeProvingKey, feeVerifyingKey] = feeKeys; - // If the function proving and verifying keys are not provided, attempt to find them using the key provider. + // Extract the function name from the authorization for key resolution and persistence. + const functionName = authorization.functionName(); + + // If the function proving and verifying keys are not provided, check KeyStore + // first (preferred), then fall back to KeyProvider (legacy). if (!provingKey || !verifyingKey) { - try { - [provingKey, verifyingKey] = <FunctionKeyPair>( - await this.keyProvider.functionKeys(keySearchParams) - ); - } catch (e) { + const keys = await this.resolveTopLevelKeys(programName, functionName, keySearchParams); + if (keys) { + [provingKey, verifyingKey] = keys; + } else { console.log( - `Function keys not found. Key finder response: '${e}'. The function keys will be synthesized`, + "Function keys not found in KeyStore or KeyProvider. The function keys will be synthesized", ); } } + // Resolve the program edition. + let edition = options.edition; + if (edition == undefined) { + try { + edition = await this.networkClient.getLatestProgramEdition(programName); + } catch (e: any) { + console.warn(`Error finding edition for ${programName}. Network response: '${e.message}'. Assuming edition 0.`); + edition = 0; + } + } + // Resolve the program imports if they exist. console.log("Resolving program imports"); const numberOfImports = Program.fromString(program).getImports().length; @@ -1142,8 +1374,9 @@ class ProgramManager { } // Build an execution transaction from the authorization. + // Use the key-extracting variant to capture synthesized keys for KeyStore persistence. console.log("Executing authorizations") - return await WasmProgramManager.executeAuthorization( + const transaction = await WasmProgramManager.executeAuthorizationWithImports( authorization, feeAuthorization, program, @@ -1151,11 +1384,16 @@ class ProgramManager { verifyingKey, feeProvingKey, feeVerifyingKey, - imports, this.host, offlineQuery, + edition, programImportsBuilder, - ) + ); + + // Auto-persist synthesized keys (both imports and top-level) from the mutated builder to KeyStore. + await this.persistExtractedKeys(programImportsBuilder, imports, program, functionName); + + return transaction; } /** @@ -1196,7 +1434,6 @@ class ProgramManager { } = options; const privateKey = options.privateKey; - const programImportsBuilder = options.programImportsBuilder; let program = options.programSource; let programName = options.programName; let imports = options.programImports; @@ -1222,6 +1459,9 @@ class ProgramManager { programName = Program.fromString(program).id(); } + const programImportsBuilder = options.programImportsBuilder + ?? await this.buildProgramImports(program, imports); + // Get the private key from the account if it is not provided in the parameters. let executionPrivateKey = privateKey; if ( @@ -1308,7 +1548,6 @@ class ProgramManager { } = options; const privateKey = options.privateKey; - const programImportsBuilder = options.programImportsBuilder; let program = options.programSource; let programName = options.programName; let imports = options.programImports; @@ -1334,6 +1573,9 @@ class ProgramManager { programName = Program.fromString(program).id(); } + const programImportsBuilder = options.programImportsBuilder + ?? await this.buildProgramImports(program, imports); + // Get the private key from the account if it is not provided in the parameters. let executionPrivateKey = privateKey; if ( @@ -1430,7 +1672,6 @@ class ProgramManager { const baseFee = options.baseFee ? options.baseFee : 0; const privateKey = options.privateKey; const useFeeMaster = options.useFeeMaster ? options.useFeeMaster : false; - const programImportsBuilder = options.programImportsBuilder; let program = options.programSource; let programName = options.programName; let feeRecord = options.feeRecord; @@ -1457,6 +1698,9 @@ class ProgramManager { programName = Program.fromString(program).id(); } + const programImportsBuilder = options.programImportsBuilder + ?? await this.buildProgramImports(program, imports); + if (edition == undefined) { try { edition = await this.networkClient.getLatestProgramEdition(programName); @@ -1721,38 +1965,44 @@ class ProgramManager { throw "No private key provided and no private key set in the ProgramManager"; } - // If the function proving and verifying keys are not provided, attempt to find them using the key provider + // If the function proving and verifying keys are not provided, check KeyStore + // first (preferred), then fall back to KeyProvider (legacy). if (!provingKey || !verifyingKey) { - try { - [provingKey, verifyingKey] = <FunctionKeyPair>( - await this.keyProvider.functionKeys(keySearchParams) - ); - } catch (e) { + const programName = Program.fromString(program).id(); + const keys = await this.resolveTopLevelKeys(programName, function_name, keySearchParams); + if (keys) { + [provingKey, verifyingKey] = keys; + } else { console.log( - `Function keys not found. Key finder response: '${e}'. The function keys will be synthesized`, + "Function keys not found in KeyStore or KeyProvider. The function keys will be synthesized", ); } } - // Run the program offline and return the result - console.log("Running program offline"); - console.log("Proving key: ", provingKey); - console.log("Verifying key: ", verifyingKey); - return WasmProgramManager.executeFunctionOffline( + // Build a ProgramImportsBuilder from KeyStore if one wasn't provided. + const resolvedImportsBuilder = programImportsBuilder + ?? await this.buildProgramImports(program, imports); + + // Run the program offline using the key-extracting variant. + const executionResponse = await WasmProgramManager.executeFunctionOfflineWithImports( executionPrivateKey, program, function_name, inputs, proveExecution, false, - imports, provingKey, verifyingKey, this.host, offlineQuery, edition, - programImportsBuilder, + resolvedImportsBuilder, ); + + // Auto-persist synthesized keys (both imports and top-level) from the mutated builder to KeyStore. + await this.persistExtractedKeys(resolvedImportsBuilder, imports, program, function_name); + + return executionResponse; } /** @@ -2045,10 +2295,27 @@ class ProgramManager { inputs, imports, ); - return [ + const keys: FunctionKeyPair = [ <ProvingKey>keyPair.provingKey(), <VerifyingKey>keyPair.verifyingKey(), ]; + + // Auto-persist synthesized keys to KeyStore if configured + const keyStore = this._keyStore ?? await this.keyProvider?.keyStore?.().catch(() => undefined); + if (keyStore) { + try { + const programName = Program.fromString(program).id(); + await keyStore.setKeys( + { locator: `${programName}.${function_id}.prover` }, + { locator: `${programName}.${function_id}.verifier` }, + keys, + ); + } catch (e) { + console.debug(`Failed to persist synthesized keys: ${e}`); + } + } + + return keys; } catch (e: any) { logAndThrow( `Could not synthesize keys - error ${e.message}. Please ensure the program is valid and the inputs are correct.`, @@ -3217,13 +3484,14 @@ class ProgramManager { program, imports, edition, - programImportsBuilder, } = options; if (!authorization) { throw new Error("Authorization must be provided if estimating fee for Authorization.") } const programSource = program ? program.toString() : await this.networkClient.getProgram(programName, edition); const programImports = imports ? imports : await this.networkClient.getProgramImports(programSource); + const programImportsBuilder = options.programImportsBuilder + ?? await this.buildProgramImports(programSource, programImports); if (Object.keys(programImports).length > 0) { return WasmProgramManager.estimateFeeForAuthorization(authorization, programSource, programImports, edition, programImportsBuilder); } @@ -3265,13 +3533,14 @@ class ProgramManager { program, imports, edition, - programImportsBuilder, } = options; if (!functionName) { throw new Error("Function name must be specified when estimating fee."); } const programSource = program ? program.toString() : await this.networkClient.getProgram(programName, edition); const programImports = imports ? imports : await this.networkClient.getProgramImports(programSource); + const programImportsBuilder = options.programImportsBuilder + ?? await this.buildProgramImports(programSource, programImports); if (Object.keys(programImports).length > 0) { return WasmProgramManager.estimateExecutionFee(programSource, functionName, programImports, edition, programImportsBuilder); } @@ -3371,7 +3640,6 @@ class ProgramManager { let programName = options.programName; let imports = options.imports; let edition = options.edition; - const programImportsBuilder = options.programImportsBuilder; let programObject; // Ensure the function exists on the network @@ -3413,6 +3681,9 @@ class ProgramManager { } } + const programImportsBuilder = options.programImportsBuilder + ?? await this.buildProgramImports(program, imports); + // Get the private key from the account if it is not provided in the parameters. let executionPrivateKey = privateKey; if ( diff --git a/sdk/tests/program-imports.test.ts b/sdk/tests/program-imports.test.ts new file mode 100644 index 000000000..17a62b1a0 --- /dev/null +++ b/sdk/tests/program-imports.test.ts @@ -0,0 +1,672 @@ +import sinon from "sinon"; +import { expect } from "chai"; +import { + Program, + ProgramImportsBuilder, + ProgramManager, + ProvingKey, + VerifyingKey, +} from "@provablehq/sdk/%%NETWORK%%.js"; +import { + DD_CALLER_PROGRAM, + DD_CONSTANTS_PROGRAM, + DD_TEN_PROGRAM, +} from "./data/dynamic-dispatch.js"; +import type { KeyStore, KeyLocator } from "../src/keys/keystore/interface.js"; +import type { FunctionKeyProvider } from "../src/keys/provider/interface.js"; + +/** Sinon-stubbed KeyStore for assertions on call args/counts. */ +type StubbedKeyStore = { + [K in keyof KeyStore]: sinon.SinonStub; +}; + +function createMockKeyStore(keys: Record<string, { pk?: ProvingKey; vk?: VerifyingKey }> = {}): StubbedKeyStore { + return { + has: sinon.stub().callsFake(async (locator: string) => { + for (const [programFn, pair] of Object.entries(keys)) { + if (locator === `${programFn}.prover` && pair.pk) return true; + if (locator === `${programFn}.verifier` && pair.vk) return true; + } + return false; + }), + getProvingKey: sinon.stub().callsFake(async (loc: KeyLocator) => { + for (const [programFn, pair] of Object.entries(keys)) { + if (loc.locator === `${programFn}.prover`) return pair.pk ?? null; + } + return null; + }), + getVerifyingKey: sinon.stub().callsFake(async (loc: KeyLocator) => { + for (const [programFn, pair] of Object.entries(keys)) { + if (loc.locator === `${programFn}.verifier`) return pair.vk ?? null; + } + return null; + }), + setKeys: sinon.stub().resolves(), + getKeyBytes: sinon.stub().resolves(null), + setKeyBytes: sinon.stub().resolves(), + getKeyMetadata: sinon.stub().resolves(null), + delete: sinon.stub().resolves(), + clear: sinon.stub().resolves(), + }; +} + +function createMockKeyProvider(keyStore?: StubbedKeyStore): FunctionKeyProvider { + return { + keyStore: sinon.stub().resolves(keyStore), + bondPublicKeys: sinon.stub().resolves(), + bondValidatorKeys: sinon.stub().resolves(), + cacheKeys: sinon.stub(), + claimUnbondPublicKeys: sinon.stub().resolves(), + functionKeys: sinon.stub().resolves(), + feePrivateKeys: sinon.stub().resolves(), + feePublicKeys: sinon.stub().resolves(), + inclusionKeys: sinon.stub().resolves(), + joinKeys: sinon.stub().resolves(), + splitKeys: sinon.stub().resolves(), + transferKeys: sinon.stub().resolves(), + unBondPublicKeys: sinon.stub().resolves(), + } as unknown as FunctionKeyProvider; +} + +/** Access private methods on ProgramManager via cast. */ +function pm(manager: ProgramManager): any { + return manager as any; +} + +describe("ProgramImports & KeyStore integration", () => { + afterEach(() => sinon.restore()); + + describe("buildProgramImports", () => { + it("should return an empty builder when the program has no imports and none provided", async () => { + const manager = new ProgramManager(); + // dd_constants has no static imports and we provide none. + const builder: ProgramImportsBuilder = await pm(manager).buildProgramImports(DD_CONSTANTS_PROGRAM); + + expect(builder.isEmpty()).to.equal(true); + }); + + it("should add user-provided imports to the builder", async () => { + const manager = new ProgramManager(); + const imports = { "dd_constants.aleo": DD_CONSTANTS_PROGRAM }; + + const builder: ProgramImportsBuilder = await pm(manager).buildProgramImports(DD_CALLER_PROGRAM, imports); + + expect(builder.isEmpty()).to.equal(false); + expect(builder.contains("dd_constants.aleo")).to.equal(true); + }); + + it("should merge network-fetched imports with user-provided imports", async () => { + const manager = new ProgramManager(); + const networkImports = { "dd_constants.aleo": DD_CONSTANTS_PROGRAM }; + sinon.stub(manager.networkClient, "getProgramImports").resolves(networkImports); + // User provides dd_ten as a dynamic dispatch target (not a static import). + const userImports = { "dd_ten.aleo": DD_TEN_PROGRAM }; + + const builder: ProgramImportsBuilder = await pm(manager).buildProgramImports(DD_CALLER_PROGRAM, userImports); + + // Both network-fetched and user-provided imports should be present. + expect(builder.contains("dd_constants.aleo")).to.equal(true); + expect(builder.contains("dd_ten.aleo")).to.equal(true); + }); + + it("should let user-provided imports override network-fetched imports", async () => { + const manager = new ProgramManager(); + const networkImports = { "dd_constants.aleo": DD_CONSTANTS_PROGRAM }; + sinon.stub(manager.networkClient, "getProgramImports").resolves(networkImports); + // User provides their own version of dd_constants. + const userImports = { "dd_constants.aleo": DD_CONSTANTS_PROGRAM }; + + const builder: ProgramImportsBuilder = await pm(manager).buildProgramImports(DD_CALLER_PROGRAM, userImports); + + // User-provided import should be in the builder (overrides network). + expect(builder.contains("dd_constants.aleo")).to.equal(true); + }); + + it("should gracefully handle network fetch failures", async () => { + const manager = new ProgramManager(); + sinon.stub(manager.networkClient, "getProgramImports") + .rejects(new Error("network down")); + + // dd_constants has no static imports, but we don't pass any either, + // so network fetch is attempted and fails gracefully. + const builder: ProgramImportsBuilder = await pm(manager).buildProgramImports(DD_CONSTANTS_PROGRAM); + + expect(builder.isEmpty()).to.equal(true); + }); + + it("should accept a Program object in addition to a string", async () => { + const manager = new ProgramManager(); + const programObj = Program.fromString(DD_CALLER_PROGRAM); + const imports = { "dd_constants.aleo": DD_CONSTANTS_PROGRAM }; + + const builder: ProgramImportsBuilder = await pm(manager).buildProgramImports(programObj, imports); + + expect(builder.contains("dd_constants.aleo")).to.equal(true); + }); + + it("should include dynamic dispatch targets provided in imports even without static imports", async () => { + const manager = new ProgramManager(); + // dd_constants has no static imports, but we provide dd_ten as a dynamic target. + const imports = { "dd_ten.aleo": DD_TEN_PROGRAM }; + + const builder: ProgramImportsBuilder = await pm(manager).buildProgramImports(DD_CONSTANTS_PROGRAM, imports); + + expect(builder.contains("dd_ten.aleo")).to.equal(true); + }); + + it("should add multiple imports to the builder", async () => { + const manager = new ProgramManager(); + const imports = { + "dd_constants.aleo": DD_CONSTANTS_PROGRAM, + "dd_ten.aleo": DD_TEN_PROGRAM, + }; + + const builder: ProgramImportsBuilder = await pm(manager).buildProgramImports(DD_CALLER_PROGRAM, imports); + + expect(builder.contains("dd_constants.aleo")).to.equal(true); + expect(builder.contains("dd_ten.aleo")).to.equal(true); + }); + }); + + describe("loadKeysFromStore", () => { + it("should be a no-op when keyProvider has no keyStore", async () => { + const provider = createMockKeyProvider(undefined); + const manager = new ProgramManager(undefined, provider); + + const builder = new ProgramImportsBuilder(); + builder.addProgram("dd_constants.aleo", DD_CONSTANTS_PROGRAM); + + await pm(manager).loadKeysFromStore(builder, "dd_constants.aleo", DD_CONSTANTS_PROGRAM); + + // No keys should be added. + expect(builder.getProvingKey("dd_constants.aleo", "get_value")).to.equal(undefined); + }); + + it("should be a no-op when keyStore.has returns false for all locators", async () => { + const store = createMockKeyStore(); + const provider = createMockKeyProvider(store); + const manager = new ProgramManager(undefined, provider); + + const builder = new ProgramImportsBuilder(); + builder.addProgram("dd_constants.aleo", DD_CONSTANTS_PROGRAM); + + await pm(manager).loadKeysFromStore(builder, "dd_constants.aleo", DD_CONSTANTS_PROGRAM); + + // has() was called, but getProvingKey/getVerifyingKey should not be. + expect(store.has.called).to.equal(true); + expect(store.getProvingKey.called).to.equal(false); + expect(store.getVerifyingKey.called).to.equal(false); + }); + + it("should query correct locators for each function in the program", async () => { + const store = createMockKeyStore(); + const provider = createMockKeyProvider(store); + const manager = new ProgramManager(undefined, provider); + + const builder = new ProgramImportsBuilder(); + builder.addProgram("dd_ten.aleo", DD_TEN_PROGRAM); + + await pm(manager).loadKeysFromStore(builder, "dd_ten.aleo", DD_TEN_PROGRAM); + + const hasLocators = store.has.args.map((a: string[]) => a[0]); + expect(hasLocators).to.include("dd_ten.aleo.get_ten.prover"); + expect(hasLocators).to.include("dd_ten.aleo.get_ten.verifier"); + }); + + it("should swallow keyStore errors without throwing", async () => { + const store = createMockKeyStore(); + store.has.rejects(new Error("disk error")); + const provider = createMockKeyProvider(store); + const manager = new ProgramManager(undefined, provider); + + const builder = new ProgramImportsBuilder(); + builder.addProgram("dd_constants.aleo", DD_CONSTANTS_PROGRAM); + + // Should not throw. + await pm(manager).loadKeysFromStore(builder, "dd_constants.aleo", DD_CONSTANTS_PROGRAM); + }); + + it("should swallow keyProvider.keyStore() errors without throwing", async () => { + const provider = createMockKeyProvider(); + (provider.keyStore as sinon.SinonStub).rejects(new Error("provider unavailable")); + const manager = new ProgramManager(undefined, provider); + + const builder = new ProgramImportsBuilder(); + builder.addProgram("dd_constants.aleo", DD_CONSTANTS_PROGRAM); + + // Should not throw. + await pm(manager).loadKeysFromStore(builder, "dd_constants.aleo", DD_CONSTANTS_PROGRAM); + }); + }); + + describe("persistExtractedKeys", () => { + it("should be a no-op when no imports are provided", async () => { + const store = createMockKeyStore(); + const provider = createMockKeyProvider(store); + const manager = new ProgramManager(undefined, provider); + + const builder = new ProgramImportsBuilder(); + + await pm(manager).persistExtractedKeys(builder); + + expect(store.setKeys.called).to.equal(false); + }); + + it("should be a no-op when keyProvider has no keyStore", async () => { + const provider = createMockKeyProvider(undefined); + const manager = new ProgramManager(undefined, provider); + + const builder = new ProgramImportsBuilder(); + builder.addProgram("dd_constants.aleo", DD_CONSTANTS_PROGRAM); + + // Should not throw. + await pm(manager).persistExtractedKeys(builder, { "dd_constants.aleo": DD_CONSTANTS_PROGRAM }); + }); + + it("should skip programs not in the builder", async () => { + const store = createMockKeyStore(); + const provider = createMockKeyProvider(store); + const manager = new ProgramManager(undefined, provider); + + const builder = new ProgramImportsBuilder(); + // Builder is empty — does not contain dd_constants.aleo. + + await pm(manager).persistExtractedKeys(builder, { "dd_constants.aleo": DD_CONSTANTS_PROGRAM }); + + expect(store.setKeys.called).to.equal(false); + }); + + it("should skip functions where builder has no keys", async () => { + const store = createMockKeyStore(); + const provider = createMockKeyProvider(store); + const manager = new ProgramManager(undefined, provider); + + const builder = new ProgramImportsBuilder(); + builder.addProgram("dd_constants.aleo", DD_CONSTANTS_PROGRAM); + // No keys added to the builder. + + await pm(manager).persistExtractedKeys(builder, { "dd_constants.aleo": DD_CONSTANTS_PROGRAM }); + + expect(store.setKeys.called).to.equal(false); + }); + + it("should swallow keyStore errors without throwing", async () => { + const store = createMockKeyStore(); + store.setKeys.rejects(new Error("write error")); + const provider = createMockKeyProvider(store); + const manager = new ProgramManager(undefined, provider); + + const builder = new ProgramImportsBuilder(); + builder.addProgram("dd_constants.aleo", DD_CONSTANTS_PROGRAM); + + // Should not throw. + await pm(manager).persistExtractedKeys(builder, { "dd_constants.aleo": DD_CONSTANTS_PROGRAM }); + }); + + it("should swallow keyProvider.keyStore() errors without throwing", async () => { + const provider = createMockKeyProvider(); + (provider.keyStore as sinon.SinonStub).rejects(new Error("provider unavailable")); + const manager = new ProgramManager(undefined, provider); + + const builder = new ProgramImportsBuilder(); + + // Should not throw. + await pm(manager).persistExtractedKeys(builder, { "dd_constants.aleo": DD_CONSTANTS_PROGRAM }); + }); + + it("should accept Program objects in the imports map", async () => { + const store = createMockKeyStore(); + const provider = createMockKeyProvider(store); + const manager = new ProgramManager(undefined, provider); + + const builder = new ProgramImportsBuilder(); + builder.addProgram("dd_constants.aleo", DD_CONSTANTS_PROGRAM); + + const programObj = Program.fromString(DD_CONSTANTS_PROGRAM); + + // Should not throw — the method coerces Program objects to strings. + await pm(manager).persistExtractedKeys(builder, { "dd_constants.aleo": programObj }); + }); + + it("should persist top-level keys when topLevelProgram and topLevelFunction are provided", async () => { + const store = createMockKeyStore({ + // Pre-populate so getProvingKey/getVerifyingKey return values + "dd_constants.aleo.get_value": { pk: {} as ProvingKey, vk: {} as VerifyingKey }, + }); + const provider = createMockKeyProvider(store); + const manager = new ProgramManager(undefined, provider); + + // Simulate WASM having extracted the top-level key into the builder. + const builder = new ProgramImportsBuilder(); + builder.addProgram("dd_constants.aleo", DD_CONSTANTS_PROGRAM); + // Note: In the real flow, WASM's extract_top_level_keys populates the builder + // with keys from the process. Here the builder has no keys, so getProvingKey + // returns undefined and setKeys is not called. This verifies the guard works. + + await pm(manager).persistExtractedKeys(builder, {}, DD_CONSTANTS_PROGRAM, "get_value"); + + // Builder had no keys (getProvingKey returns undefined), so setKeys should not fire. + expect(store.setKeys.called).to.equal(false); + }); + + it("should not attempt top-level persistence when topLevelProgram is omitted", async () => { + const store = createMockKeyStore(); + const provider = createMockKeyProvider(store); + const manager = new ProgramManager(undefined, provider); + + const builder = new ProgramImportsBuilder(); + builder.addProgram("dd_constants.aleo", DD_CONSTANTS_PROGRAM); + + await pm(manager).persistExtractedKeys(builder, {}); + + // No top-level params means no top-level persistence attempt. + expect(store.setKeys.called).to.equal(false); + }); + + it("should swallow errors when persisting top-level keys", async () => { + const store = createMockKeyStore(); + store.setKeys.rejects(new Error("write error")); + const provider = createMockKeyProvider(store); + const manager = new ProgramManager(undefined, provider); + + const builder = new ProgramImportsBuilder(); + builder.addProgram("dd_constants.aleo", DD_CONSTANTS_PROGRAM); + + // Should resolve without throwing despite the write error. + let threw = false; + try { + await pm(manager).persistExtractedKeys(builder, {}, DD_CONSTANTS_PROGRAM, "get_value"); + } catch { + threw = true; + } + expect(threw).to.equal(false); + }); + }); + + describe("buildProgramImports with KeyStore", () => { + it("should query KeyStore for each imported program's functions", async () => { + const store = createMockKeyStore(); + const provider = createMockKeyProvider(store); + const manager = new ProgramManager(undefined, provider); + + const imports = { "dd_constants.aleo": DD_CONSTANTS_PROGRAM }; + await pm(manager).buildProgramImports(DD_CALLER_PROGRAM, imports); + + const hasLocators = store.has.args.map((a: string[]) => a[0]); + expect(hasLocators).to.include("dd_constants.aleo.get_value.prover"); + expect(hasLocators).to.include("dd_constants.aleo.get_value.verifier"); + }); + + it("should not query KeyStore when keyProvider has no keyStore", async () => { + const provider = createMockKeyProvider(undefined); + const manager = new ProgramManager(undefined, provider); + + const imports = { "dd_constants.aleo": DD_CONSTANTS_PROGRAM }; + const builder: ProgramImportsBuilder = await pm(manager).buildProgramImports(DD_CALLER_PROGRAM, imports); + + // Should still build the builder with programs, just no keys. + expect(builder.contains("dd_constants.aleo")).to.equal(true); + }); + + it("should query KeyStore for multiple imports", async () => { + const store = createMockKeyStore(); + const provider = createMockKeyProvider(store); + const manager = new ProgramManager(undefined, provider); + + const imports = { + "dd_constants.aleo": DD_CONSTANTS_PROGRAM, + "dd_ten.aleo": DD_TEN_PROGRAM, + }; + await pm(manager).buildProgramImports(DD_CALLER_PROGRAM, imports); + + const hasLocators = store.has.args.map((a: string[]) => a[0]); + expect(hasLocators).to.include("dd_constants.aleo.get_value.prover"); + expect(hasLocators).to.include("dd_ten.aleo.get_ten.prover"); + }); + }); + + describe("setKeyStore", () => { + it("should make KeyStore available to loadKeysFromStore", async () => { + const store = createMockKeyStore(); + const manager = new ProgramManager(); + manager.setKeyStore(store as unknown as KeyStore); + + const builder = new ProgramImportsBuilder(); + builder.addProgram("dd_constants.aleo", DD_CONSTANTS_PROGRAM); + + await pm(manager).loadKeysFromStore(builder, "dd_constants.aleo", DD_CONSTANTS_PROGRAM); + + // KeyStore was consulted via setKeyStore, not keyProvider.keyStore(). + expect(store.has.called).to.equal(true); + }); + + it("should take precedence over keyProvider.keyStore()", async () => { + const directStore = createMockKeyStore(); + const providerStore = createMockKeyStore(); + const provider = createMockKeyProvider(providerStore); + const manager = new ProgramManager(undefined, provider); + manager.setKeyStore(directStore as unknown as KeyStore); + + const builder = new ProgramImportsBuilder(); + builder.addProgram("dd_constants.aleo", DD_CONSTANTS_PROGRAM); + + await pm(manager).loadKeysFromStore(builder, "dd_constants.aleo", DD_CONSTANTS_PROGRAM); + + // Direct KeyStore was used, not the provider's. + expect(directStore.has.called).to.equal(true); + expect(providerStore.has.called).to.equal(false); + }); + }); + + describe("resolveTopLevelKeys", () => { + it("should return keys from KeyStore when available", async () => { + const store = createMockKeyStore({ + "dd_constants.aleo.get_value": { pk: {} as ProvingKey, vk: {} as VerifyingKey }, + }); + const manager = new ProgramManager(); + manager.setKeyStore(store as unknown as KeyStore); + + const keys = await pm(manager).resolveTopLevelKeys("dd_constants.aleo", "get_value"); + + expect(keys).to.not.be.undefined; + expect(store.has.calledWith("dd_constants.aleo.get_value.prover")).to.equal(true); + expect(store.has.calledWith("dd_constants.aleo.get_value.verifier")).to.equal(true); + expect(store.getProvingKey.called).to.equal(true); + expect(store.getVerifyingKey.called).to.equal(true); + }); + + it("should fall back to keyProvider.functionKeys when KeyStore has no keys", async () => { + const store = createMockKeyStore(); // empty + const provider = createMockKeyProvider(store); + const expectedKeys = [{} as ProvingKey, {} as VerifyingKey]; + (provider.functionKeys as sinon.SinonStub).resolves(expectedKeys); + const manager = new ProgramManager(undefined, provider); + + const keys = await pm(manager).resolveTopLevelKeys("dd_constants.aleo", "get_value", {}); + + expect(keys).to.deep.equal(expectedKeys); + expect(store.has.called).to.equal(true); + expect((provider.functionKeys as sinon.SinonStub).called).to.equal(true); + }); + + it("should return undefined when neither KeyStore nor KeyProvider has keys", async () => { + const store = createMockKeyStore(); // empty + const provider = createMockKeyProvider(store); + (provider.functionKeys as sinon.SinonStub).rejects(new Error("no keys")); + const manager = new ProgramManager(undefined, provider); + + const keys = await pm(manager).resolveTopLevelKeys("dd_constants.aleo", "get_value", {}); + + expect(keys).to.be.undefined; + }); + + it("should not call keyProvider.functionKeys when KeyStore has keys", async () => { + const store = createMockKeyStore({ + "dd_constants.aleo.get_value": { pk: {} as ProvingKey, vk: {} as VerifyingKey }, + }); + const provider = createMockKeyProvider(store); + const manager = new ProgramManager(undefined, provider); + manager.setKeyStore(store as unknown as KeyStore); + + await pm(manager).resolveTopLevelKeys("dd_constants.aleo", "get_value"); + + expect((provider.functionKeys as sinon.SinonStub).called).to.equal(false); + }); + + it("should swallow KeyStore errors and fall back to KeyProvider", async () => { + const store = createMockKeyStore(); + store.has.rejects(new Error("disk error")); + const provider = createMockKeyProvider(); + const expectedKeys = [{} as ProvingKey, {} as VerifyingKey]; + (provider.functionKeys as sinon.SinonStub).resolves(expectedKeys); + const manager = new ProgramManager(undefined, provider); + manager.setKeyStore(store as unknown as KeyStore); + + const keys = await pm(manager).resolveTopLevelKeys("dd_constants.aleo", "get_value", {}); + + expect(keys).to.deep.equal(expectedKeys); + }); + + it("should work with KeyStore set via setKeyStore (no keyProvider keyStore)", async () => { + const store = createMockKeyStore({ + "dd_ten.aleo.get_ten": { pk: {} as ProvingKey, vk: {} as VerifyingKey }, + }); + const manager = new ProgramManager(); + manager.setKeyStore(store as unknown as KeyStore); + + const keys = await pm(manager).resolveTopLevelKeys("dd_ten.aleo", "get_ten"); + + expect(keys).to.not.be.undefined; + expect(store.getProvingKey.called).to.equal(true); + }); + }); + + describe("synthesizeKeys auto-persist", () => { + it("should call keyStore.setKeys with correct locators after synthesis", async () => { + const store = createMockKeyStore(); + const manager = new ProgramManager(); + manager.setKeyStore(store as unknown as KeyStore); + + const fakePk = {} as ProvingKey; + const fakeVk = {} as VerifyingKey; + const fakeKeyPair = { + provingKey: sinon.stub().returns(fakePk), + verifyingKey: sinon.stub().returns(fakeVk), + }; + + // Stub the WASM call to return our fake key pair + sinon.stub(manager.networkClient, "getProgramImports").resolves({}); + const { ProgramManager: WasmPM } = await import("@provablehq/sdk/%%NETWORK%%.js"); + const synthesizeStub = sinon.stub(WasmPM, "synthesizeKeyPair").resolves(fakeKeyPair); + + try { + await manager.synthesizeKeys(DD_CONSTANTS_PROGRAM, "get_value", ["0u8"]); + + expect(store.setKeys.calledOnce).to.equal(true); + const [proverLoc, verifierLoc, keys] = store.setKeys.firstCall.args; + expect(proverLoc.locator).to.equal("dd_constants.aleo.get_value.prover"); + expect(verifierLoc.locator).to.equal("dd_constants.aleo.get_value.verifier"); + expect(keys).to.deep.equal([fakePk, fakeVk]); + } finally { + synthesizeStub.restore(); + } + }); + + it("should not call keyStore.setKeys when no KeyStore is configured", async () => { + const manager = new ProgramManager(); + + const fakePk = {} as ProvingKey; + const fakeVk = {} as VerifyingKey; + const fakeKeyPair = { + provingKey: sinon.stub().returns(fakePk), + verifyingKey: sinon.stub().returns(fakeVk), + }; + + sinon.stub(manager.networkClient, "getProgramImports").resolves({}); + const { ProgramManager: WasmPM } = await import("@provablehq/sdk/%%NETWORK%%.js"); + const synthesizeStub = sinon.stub(WasmPM, "synthesizeKeyPair").resolves(fakeKeyPair); + + try { + const keys = await manager.synthesizeKeys(DD_CONSTANTS_PROGRAM, "get_value", ["0u8"]); + + // Keys should still be returned even without a KeyStore + expect(keys).to.deep.equal([fakePk, fakeVk]); + } finally { + synthesizeStub.restore(); + } + }); + + it("should swallow keyStore.setKeys errors and still return keys", async () => { + const store = createMockKeyStore(); + store.setKeys.rejects(new Error("write error")); + const manager = new ProgramManager(); + manager.setKeyStore(store as unknown as KeyStore); + + const fakePk = {} as ProvingKey; + const fakeVk = {} as VerifyingKey; + const fakeKeyPair = { + provingKey: sinon.stub().returns(fakePk), + verifyingKey: sinon.stub().returns(fakeVk), + }; + + sinon.stub(manager.networkClient, "getProgramImports").resolves({}); + const { ProgramManager: WasmPM } = await import("@provablehq/sdk/%%NETWORK%%.js"); + const synthesizeStub = sinon.stub(WasmPM, "synthesizeKeyPair").resolves(fakeKeyPair); + + try { + const keys = await manager.synthesizeKeys(DD_CONSTANTS_PROGRAM, "get_value", ["0u8"]); + + // Keys returned despite setKeys failure + expect(keys).to.deep.equal([fakePk, fakeVk]); + expect(store.setKeys.calledOnce).to.equal(true); + } finally { + synthesizeStub.restore(); + } + }); + }); + + describe("run() import key loading", () => { + it("should auto-build ProgramImportsBuilder from KeyStore when none provided", async () => { + const store = createMockKeyStore(); + const provider = createMockKeyProvider(store); + const manager = new ProgramManager(undefined, provider); + + // Spy on buildProgramImports to verify it's called + const buildSpy = sinon.spy(pm(manager), "buildProgramImports"); + + // Stub the WASM execution to avoid actual execution + const { ProgramManager: WasmPM } = await import("@provablehq/sdk/%%NETWORK%%.js"); + const execStub = sinon.stub(WasmPM, "executeFunctionOfflineWithImports").resolves({} as any); + + try { + manager.setAccount({ privateKey: () => ({}) } as any); + await manager.run(DD_CONSTANTS_PROGRAM, "get_value", ["0u8"], false); + + expect(buildSpy.calledOnce).to.equal(true); + } finally { + execStub.restore(); + } + }); + + it("should not call buildProgramImports when programImportsBuilder is provided", async () => { + const store = createMockKeyStore(); + const provider = createMockKeyProvider(store); + const manager = new ProgramManager(undefined, provider); + + const buildSpy = sinon.spy(pm(manager), "buildProgramImports"); + + const { ProgramManager: WasmPM } = await import("@provablehq/sdk/%%NETWORK%%.js"); + const execStub = sinon.stub(WasmPM, "executeFunctionOfflineWithImports").resolves({} as any); + + try { + manager.setAccount({ privateKey: () => ({}) } as any); + const builder = new ProgramImportsBuilder(); + await manager.run(DD_CONSTANTS_PROGRAM, "get_value", ["0u8"], false, undefined, undefined, undefined, undefined, undefined, undefined, undefined, builder); + + expect(buildSpy.called).to.equal(false); + } finally { + execStub.restore(); + } + }); + }); +}); diff --git a/wasm/src/programs/manager/execute.rs b/wasm/src/programs/manager/execute.rs index 45e07dc75..d25176eb7 100644 --- a/wasm/src/programs/manager/execute.rs +++ b/wasm/src/programs/manager/execute.rs @@ -161,6 +161,97 @@ impl ProgramManager { Ok(execution_response) } + /// Execute a function offline with key extraction. + /// + /// This is the same as `executeFunctionOffline` but takes a mutable `ProgramImports` + /// reference. After execution, synthesized proving and verifying keys are extracted from + /// the process back into the `ProgramImports` builder so the caller can persist them + /// (e.g., to a KeyStore). + #[wasm_bindgen(js_name = executeFunctionOfflineWithImports)] + #[allow(clippy::too_many_arguments)] + pub async fn execute_function_offline_with_imports( + private_key: &PrivateKey, + program: &str, + function: &str, + inputs: Array, + prove_execution: bool, + cache: bool, + proving_key: Option<ProvingKey>, + verifying_key: Option<VerifyingKey>, + url: Option<String>, + offline_query: Option<OfflineQuery>, + edition: Option<u16>, + program_imports: &mut ProgramImports, + ) -> Result<ExecutionResponse, String> { + let node_url = url.as_deref().unwrap_or(DEFAULT_URL); + let inputs = inputs.to_vec(); + let rng = &mut StdRng::from_entropy(); + + let mut process_native = ProcessNative::load_web().map_err(|err| err.to_string())?; + let process = &mut process_native; + + log("Check program imports are valid and add them to the process"); + let program_native = ProgramNative::from_str(program).map_err(|e| e.to_string())?; + let taken = std::mem::replace(program_imports, ProgramImports::new()); + ProgramManager::resolve_imports_or_builder(process, None, Some(&taken))?; + // Restore immediately so we can extract keys into it later. + *program_imports = taken; + let edition = edition.unwrap_or(1); + + let (response, mut trace) = execute_program!( + process, + process_inputs!(inputs), + program, + function, + private_key, + proving_key, + verifying_key, + rng, + edition + ); + + let mut execution_response = if prove_execution { + log("Preparing inclusion proofs for execution"); + if let Some(offline_query) = offline_query { + trace.prepare_async(&offline_query).await.map_err(|err| err.to_string())?; + } else { + let function_name = IdentifierNative::from_str(function).map_err(|err| err.to_string())?; + let view_key = + ViewKeyNative::try_from(PrivateKeyNative::from(private_key)).map_err(|err| err.to_string())?; + let query = SnapshotQuery::try_from_inputs( + node_url, + &program_native, + &function_name, + &view_key, + &inputs.to_vec(), + ) + .await + .map_err(|err| err.to_string())?; + trace.prepare_async(&query).await.map_err(|err| err.to_string())?; + }; + + log("Proving execution"); + let locator = program_native.id().to_string().add("/").add(function); + let execution = + trace.prove_execution::<CurrentAleo, _>(&locator, VarunaVersion::V2, rng).map_err(|e| e.to_string())?; + ExecutionResponse::new(Some(execution), function, response, process, program)? + } else { + ExecutionResponse::new(None, function, response, process, program)? + }; + + if cache { + execution_response.add_proving_key(process, function, program_native.id())?; + } + + // Extract synthesized keys from the process back into ProgramImports. + program_imports.extract_keys(process); + if let Ok(function_id) = IdentifierNative::from_str(function) { + program_imports.extract_top_level_keys(process, &program_native, &function_id, edition); + } + + Ok(execution_response) + } + /// Execute Aleo function and create an Aleo execution transaction /// /// @param private_key The private key of the sender @@ -205,101 +296,81 @@ impl ProgramManager { edition: Option<u16>, program_imports: Option<ProgramImports>, ) -> Result<Transaction, String> { - let mut process_native = ProcessNative::load_web().map_err(|err| err.to_string())?; - let process = &mut process_native; - let node_url = url.as_deref().unwrap_or(DEFAULT_URL); - - log("Check program imports are valid and add them to the process"); - let program_native = ProgramNative::from_str(program).map_err(|e| e.to_string())?; - let program_id = program_native.id().to_string(); - ProgramManager::resolve_imports_or_builder(process, imports, program_imports.as_ref())?; - let rng = &mut StdRng::from_entropy(); - - log(&format!("Executing function: {program_id}/{function} on-chain")); - let edition = edition.unwrap_or(1); - let (_, mut trace) = execute_program!( - process, - process_inputs!(inputs), + let (transaction, _) = Self::execute_inner( + private_key, program, function, - private_key, + inputs, + priority_fee_credits, + fee_record, + url, + imports, proving_key, verifying_key, - rng, - edition - ); - - log("Preparing inclusion proofs for execution"); - let latest_height = if let Some(offline_query) = offline_query.as_ref() { - trace.prepare_async(offline_query).await.map_err(|err| err.to_string())?; - offline_query.current_block_height().map_err(|e| e.to_string())? - } else { - let function_name = IdentifierNative::from_str(function).map_err(|err| err.to_string())?; - let view_key = - ViewKeyNative::try_from(PrivateKeyNative::from(private_key)).map_err(|err| err.to_string())?; - let query = - SnapshotQuery::try_from_inputs(node_url, &program_native, &function_name, &view_key, &inputs.to_vec()) - .await - .map_err(|err| err.to_string())?; - trace.prepare_async(&query).await.map_err(|err| err.to_string())?; - query.current_block_height().map_err(|e| e.to_string())? - }; - - log("Proving execution"); - let locator = program_native.id().to_string().add("/").add(function); - let execution = trace - .prove_execution::<CurrentAleo, _>(&locator, VarunaVersion::V2, &mut StdRng::from_entropy()) - .map_err(|e| e.to_string())?; - - // If the function is anything other than credits.aleo/split or credits.aleo/upgrade, execute a fee. - let fee = match (program_id.as_str(), function) { - ("credits.aleo", "split") - | ("credits.aleo", "upgrade") - | ("credits.aleo", "fee_private") - | ("credits.aleo", "fee_public") => None, - _ => { - log("Calculating the minimum execution fee"); - let minimum_execution_cost = calculate_minimum_fee!(offline_query, node_url, process, &execution); - - // Check to see if the fee record has enough microcredits to pay for the deployment. - let priority_fee_microcredits = (priority_fee_credits * 1_000_000.0) as u64; - Self::validate_fee_record(&fee_record, minimum_execution_cost, priority_fee_microcredits)?; - - // Calculate the execution id. - let execution_id = execution.to_execution_id().map_err(|e| e.to_string())?; - - log("Executing fee"); - let fee = execute_fee!( - process, - private_key, - fee_record, - priority_fee_microcredits, - node_url, - fee_proving_key, - fee_verifying_key, - execution_id, - rng, - offline_query, - minimum_execution_cost - ); - Some(fee) - } - }; - - // Verify the execution - let consensus_version = - <CurrentNetwork as Network>::CONSENSUS_VERSION(latest_height).map_err(|err| err.to_string())?; - let inclusion_upgrade_height = - <CurrentNetwork as Network>::INCLUSION_UPGRADE_HEIGHT().map_err(|err| err.to_string())?; - let inclusion_version = - if latest_height >= inclusion_upgrade_height { InclusionVersion::V1 } else { InclusionVersion::V0 }; - process - .verify_execution(consensus_version, VarunaVersion::V2, inclusion_version, &execution) - .map_err(|err| err.to_string())?; + fee_proving_key, + fee_verifying_key, + offline_query, + edition, + program_imports, + false, + ) + .await?; + Ok(transaction) + } - log("Creating execution transaction"); - let transaction = TransactionNative::from_execution(execution, fee).map_err(|err| err.to_string())?; - Ok(Transaction::from(transaction)) + /// Execute Aleo function and create an Aleo execution transaction, with key extraction. + /// + /// This is the same as `buildExecutionTransaction` but takes a mutable `ProgramImports` + /// reference. After execution, synthesized proving and verifying keys are extracted from + /// the process back into the `ProgramImports` builder so the caller can persist them + /// (e.g., to a KeyStore). + /// + /// This method is intended for internal use by the TypeScript ProgramManager. + #[wasm_bindgen(js_name = buildExecutionTransactionWithImports)] + #[allow(clippy::too_many_arguments)] + pub async fn execute_with_imports( + private_key: &PrivateKey, + program: &str, + function: &str, + inputs: Array, + priority_fee_credits: f64, + fee_record: Option<RecordPlaintext>, + url: Option<String>, + proving_key: Option<ProvingKey>, + verifying_key: Option<VerifyingKey>, + fee_proving_key: Option<ProvingKey>, + fee_verifying_key: Option<VerifyingKey>, + offline_query: Option<OfflineQuery>, + edition: Option<u16>, + program_imports: &mut ProgramImports, + ) -> Result<Transaction, String> { + // Temporarily take ownership of ProgramImports, execute with key extraction, + // then restore the enriched ProgramImports back to the caller. + let taken = std::mem::replace(program_imports, ProgramImports::new()); + let (transaction, returned) = Self::execute_inner( + private_key, + program, + function, + inputs, + priority_fee_credits, + fee_record, + url, + None, // imports Object not needed — ProgramImports carries everything + proving_key, + verifying_key, + fee_proving_key, + fee_verifying_key, + offline_query, + edition, + Some(taken), + true, + ) + .await?; + // Restore the ProgramImports with extracted keys + if let Some(pi) = returned { + *program_imports = pi; + } + Ok(transaction) } /// Execute an authorization. @@ -327,154 +398,70 @@ impl ProgramManager { offline_query: Option<OfflineQuery>, program_imports: Option<ProgramImports>, ) -> Result<Transaction, String> { - // Create a process and insert the program and its imports. - log("Loading the SnarkVM process"); - let mut process_native = ProcessNative::load_web().map_err(|err| err.to_string())?; - let process = &mut process_native; - let node_url = url.as_deref().unwrap_or(DEFAULT_URL); - - // Get the latest height. - log("Checking the latest block height"); - let latest_height = if let Some(offline_query) = offline_query.as_ref() { - offline_query.current_block_height().map_err(|e| e.to_string())? - } else { - latest_block_height(node_url).await.map_err(|e| e.to_string())? - }; - - // Get the function name. - log("Checking the function name is valid."); - let function_name = IdentifierNative::from_str(&authorization.function_name()?).map_err(|e| e.to_string())?; - - log("Check program imports are valid and add them to the process"); - // Construct the program to ensure it's valid. - let program_native = ProgramNative::from_str(program).map_err(|e| e.to_string())?; - let program_id = program_native.id().to_string(); + let (transaction, _) = Self::execute_authorization_inner( + authorization, + fee_authorization, + program, + proving_key, + verifying_key, + fee_proving_key, + fee_verifying_key, + imports, + url, + offline_query, + program_imports, + None, + false, + ) + .await?; + Ok(transaction) + } - // Insert the program and its imports. - ProgramManager::resolve_imports_or_builder(process, imports, program_imports.as_ref())?; - if program_id != "credits.aleo" && !process.contains_program(program_native.id()) { - process.add_program(&program_native).map_err(|e| e.to_string())?; + /// Execute an authorization with key extraction. + /// + /// This is the same as `executeAuthorization` but takes a mutable `ProgramImports` + /// reference. After execution, synthesized proving and verifying keys are extracted from + /// the process back into the `ProgramImports` builder so the caller can persist them + /// (e.g., to a KeyStore). + /// + /// This method is intended for internal use by the TypeScript ProgramManager. + #[wasm_bindgen(js_name = executeAuthorizationWithImports)] + #[allow(clippy::too_many_arguments)] + pub async fn execute_authorization_with_imports( + authorization: Authorization, + fee_authorization: Option<Authorization>, + program: &str, + proving_key: Option<ProvingKey>, + verifying_key: Option<VerifyingKey>, + fee_proving_key: Option<ProvingKey>, + fee_verifying_key: Option<VerifyingKey>, + url: Option<String>, + offline_query: Option<OfflineQuery>, + edition: Option<u16>, + program_imports: &mut ProgramImports, + ) -> Result<Transaction, String> { + let taken = std::mem::replace(program_imports, ProgramImports::new()); + let (transaction, returned) = Self::execute_authorization_inner( + authorization, + fee_authorization, + program, + proving_key, + verifying_key, + fee_proving_key, + fee_verifying_key, + None, + url, + offline_query, + Some(taken), + edition, + true, + ) + .await?; + if let Some(pi) = returned { + *program_imports = pi; } - - // Insert the proving key if provided. - if let Some(proving_key) = proving_key { - if Self::contains_key(process, program_native.id(), &function_name) { - log(&format!( - "Proving & verifying keys were specified for {program_id} - {function_name:?} but a key already exists in the cache. Using cached keys" - )); - } else { - log(&format!( - "Inserting externally provided proving and verifying keys for {program_id} - {function_name:?}" - )); - process - .insert_proving_key(program_native.id(), &function_name, ProvingKeyNative::from(proving_key)) - .map_err(|e| e.to_string())?; - if let Some(verifying_key) = verifying_key { - process - .insert_verifying_key( - program_native.id(), - &function_name, - VerifyingKeyNative::from(verifying_key), - ) - .map_err(|e| e.to_string())?; - } - } - }; - - // Insert the fee proving key if provided. - if let Some(fee_authorization) = fee_authorization.as_ref() { - // If the fee keys were provided, insert them. - if let Some(fee_proving_key) = fee_proving_key { - let credits = ProgramIDNative::from_str("credits.aleo").unwrap(); - - // Get the fee function name. - let function_name = if fee_authorization.is_fee_private() { - IdentifierNative::from_str("fee_private").unwrap() - } else { - IdentifierNative::from_str("fee_public").unwrap() - }; - - // Insert the keys if they don't already exist. - if Self::contains_key(process, &credits, &function_name) { - log( - "Fee proving & verifying keys were specified but a key already exists in the cache. Using cached keys", - ); - } else { - log("Inserting externally provided fee proving and verifying keys"); - process - .insert_proving_key(&credits, &function_name, ProvingKeyNative::from(fee_proving_key)) - .map_err(|e| e.to_string())?; - if let Some(fee_verifying_key) = fee_verifying_key { - process - .insert_verifying_key(&credits, &function_name, VerifyingKeyNative::from(fee_verifying_key)) - .map_err(|e| e.to_string())?; - } - } - }; - } - - let rng = &mut StdRng::from_entropy(); - let authorization = AuthorizationNative::from(authorization); - - // Construct the locator of the main function. - let locator = { - let request = authorization.peek_next().map_err(|e| e.to_string())?; - LocatorNative::new(*request.program_id(), *request.function_name()).to_string() - }; - - // Determine the consensus version. - let consensus_version = - <CurrentNetwork as Network>::CONSENSUS_VERSION(latest_height).map_err(|e| e.to_string())?; - // Check whether the authorization is for a valid program edition. - authorization.check_valid_edition(process, consensus_version).map_err(|e| e.to_string())?; - // Check whether the authorization is creating valid records. - authorization.check_valid_records(consensus_version).map_err(|e| e.to_string())?; - // Determine which Varuna version to use. - let varuna_version = match (ConsensusVersion::V1..=ConsensusVersion::V3).contains(&consensus_version) { - true => VarunaVersion::V1, - false => VarunaVersion::V2, - }; - - let (_, mut trace) = process.execute::<CurrentAleo, _>(authorization, rng).map_err(|e| e.to_string())?; - - log("Preparing inclusion proofs for execution"); - if let Some(offline_query) = offline_query.as_ref() { - trace.prepare_async(offline_query).await.map_err(|e| e.to_string())?; - } else { - let query = SnapshotQuery::rest(); - trace.prepare_async(&query).await.map_err(|err| err.to_string())?; - }; - - log("Proving execution"); - let execution = trace - .prove_execution::<CurrentAleo, _>(&locator, varuna_version, &mut StdRng::from_entropy()) - .map_err(|e| e.to_string())?; - - let fee = if let Some(fee_authorization) = fee_authorization { - let fee_authorization = AuthorizationNative::from(fee_authorization); - - // Check whether the authorization is for a valid program edition. - fee_authorization.check_valid_edition(process, consensus_version).map_err(|e| e.to_string())?; - // Check whether the authorization is creating valid records. - fee_authorization.check_valid_records(consensus_version).map_err(|e| e.to_string())?; - - let (_, mut fee_trace) = - process.execute::<CurrentAleo, _>(fee_authorization, rng).map_err(|e| e.to_string())?; - - log("Preparing inclusion proofs for execution"); - if let Some(offline_query) = offline_query.as_ref() { - fee_trace.prepare_async(offline_query).await.map_err(|e| e.to_string())?; - } else { - let query = SnapshotQuery::rest(); - fee_trace.prepare_async(&query).await.map_err(|err| err.to_string())?; - }; - - Some(fee_trace.prove_fee::<CurrentAleo, _>(varuna_version, rng).map_err(|e| e.to_string())?) - } else { - None - }; - Ok(Transaction::from(TransactionNative::from_execution(execution, fee).map_err(|e| e.to_string())?)) - } + Ok(transaction) + } /// Generate an execution transaction without a proof. /// Intended for use with the Leo devnode tool. @@ -766,6 +753,315 @@ impl ProgramManager { } } +// Internal (non-wasm_bindgen) implementation details. +impl ProgramManager { + /// Shared execution logic for `execute` and `execute_with_imports`. + /// + /// When `extract_keys` is true, synthesized proving and verifying keys are extracted + /// from the process back into the ProgramImports, which is returned alongside the transaction. + #[allow(clippy::too_many_arguments)] + async fn execute_inner( + private_key: &PrivateKey, + program: &str, + function: &str, + inputs: Array, + priority_fee_credits: f64, + fee_record: Option<RecordPlaintext>, + url: Option<String>, + imports: Option<Object>, + proving_key: Option<ProvingKey>, + verifying_key: Option<VerifyingKey>, + fee_proving_key: Option<ProvingKey>, + fee_verifying_key: Option<VerifyingKey>, + offline_query: Option<OfflineQuery>, + edition: Option<u16>, + program_imports: Option<ProgramImports>, + extract_keys: bool, + ) -> Result<(Transaction, Option<ProgramImports>), String> { + let mut process_native = ProcessNative::load_web().map_err(|err| err.to_string())?; + let process = &mut process_native; + let node_url = url.as_deref().unwrap_or(DEFAULT_URL); + + log("Check program imports are valid and add them to the process"); + let program_native = ProgramNative::from_str(program).map_err(|e| e.to_string())?; + let program_id = program_native.id().to_string(); + ProgramManager::resolve_imports_or_builder(process, imports, program_imports.as_ref())?; + let rng = &mut StdRng::from_entropy(); + + log(&format!("Executing function: {program_id}/{function} on-chain")); + let edition = edition.unwrap_or(1); + let (_, mut trace) = execute_program!( + process, + process_inputs!(inputs), + program, + function, + private_key, + proving_key, + verifying_key, + rng, + edition + ); + + log("Preparing inclusion proofs for execution"); + let latest_height = if let Some(offline_query) = offline_query.as_ref() { + trace.prepare_async(offline_query).await.map_err(|err| err.to_string())?; + offline_query.current_block_height().map_err(|e| e.to_string())? + } else { + let function_name = IdentifierNative::from_str(function).map_err(|err| err.to_string())?; + let view_key = + ViewKeyNative::try_from(PrivateKeyNative::from(private_key)).map_err(|err| err.to_string())?; + let query = + SnapshotQuery::try_from_inputs(node_url, &program_native, &function_name, &view_key, &inputs.to_vec()) + .await + .map_err(|err| err.to_string())?; + trace.prepare_async(&query).await.map_err(|err| err.to_string())?; + query.current_block_height().map_err(|e| e.to_string())? + }; + + log("Proving execution"); + let locator = program_native.id().to_string().add("/").add(function); + let execution = trace + .prove_execution::<CurrentAleo, _>(&locator, VarunaVersion::V2, &mut StdRng::from_entropy()) + .map_err(|e| e.to_string())?; + + // If the function is anything other than credits.aleo/split or credits.aleo/upgrade, execute a fee. + let fee = match (program_id.as_str(), function) { + ("credits.aleo", "split") + | ("credits.aleo", "upgrade") + | ("credits.aleo", "fee_private") + | ("credits.aleo", "fee_public") => None, + _ => { + log("Calculating the minimum execution fee"); + let minimum_execution_cost = calculate_minimum_fee!(offline_query, node_url, process, &execution); + + // Check to see if the fee record has enough microcredits to pay for the deployment. + let priority_fee_microcredits = (priority_fee_credits * 1_000_000.0) as u64; + Self::validate_fee_record(&fee_record, minimum_execution_cost, priority_fee_microcredits)?; + + // Calculate the execution id. + let execution_id = execution.to_execution_id().map_err(|e| e.to_string())?; + + log("Executing fee"); + let fee = execute_fee!( + process, + private_key, + fee_record, + priority_fee_microcredits, + node_url, + fee_proving_key, + fee_verifying_key, + execution_id, + rng, + offline_query, + minimum_execution_cost + ); + Some(fee) + } + }; + + // Verify the execution + let consensus_version = + <CurrentNetwork as Network>::CONSENSUS_VERSION(latest_height).map_err(|err| err.to_string())?; + let inclusion_upgrade_height = + <CurrentNetwork as Network>::INCLUSION_UPGRADE_HEIGHT().map_err(|err| err.to_string())?; + let inclusion_version = + if latest_height >= inclusion_upgrade_height { InclusionVersion::V1 } else { InclusionVersion::V0 }; + process + .verify_execution(consensus_version, VarunaVersion::V2, inclusion_version, &execution) + .map_err(|err| err.to_string())?; + + // Extract synthesized keys from the process back into ProgramImports + // so the caller can persist them (e.g., to a KeyStore). + let program_imports = if extract_keys { + program_imports.map(|mut pi| { + pi.extract_keys(process); + // Also extract the top-level program's keys. + if let Ok(function_id) = IdentifierNative::from_str(function) { + pi.extract_top_level_keys(process, &program_native, &function_id, edition); + } + pi + }) + } else { + None + }; + + log("Creating execution transaction"); + let transaction = TransactionNative::from_execution(execution, fee).map_err(|err| err.to_string())?; + Ok((Transaction::from(transaction), program_imports)) + } + + /// Shared execution logic for `execute_authorization` and `execute_authorization_with_imports`. + #[allow(clippy::too_many_arguments)] + async fn execute_authorization_inner( + authorization: Authorization, + fee_authorization: Option<Authorization>, + program: &str, + proving_key: Option<ProvingKey>, + verifying_key: Option<VerifyingKey>, + fee_proving_key: Option<ProvingKey>, + fee_verifying_key: Option<VerifyingKey>, + imports: Option<Object>, + url: Option<String>, + offline_query: Option<OfflineQuery>, + program_imports: Option<ProgramImports>, + edition: Option<u16>, + extract_keys: bool, + ) -> Result<(Transaction, Option<ProgramImports>), String> { + // Create a process and insert the program and its imports. + log("Loading the SnarkVM process"); + let mut process_native = ProcessNative::load_web().map_err(|err| err.to_string())?; + let process = &mut process_native; + let node_url = url.as_deref().unwrap_or(DEFAULT_URL); + + // Get the latest height. + log("Checking the latest block height"); + let latest_height = if let Some(offline_query) = offline_query.as_ref() { + offline_query.current_block_height().map_err(|e| e.to_string())? + } else { + latest_block_height(node_url).await.map_err(|e| e.to_string())? + }; + + // Get the function name. + log("Checking the function name is valid."); + let function_name = IdentifierNative::from_str(&authorization.function_name()?).map_err(|e| e.to_string())?; + + log("Check program imports are valid and add them to the process"); + let program_native = ProgramNative::from_str(program).map_err(|e| e.to_string())?; + let program_id = program_native.id().to_string(); + + // Insert the program and its imports. + ProgramManager::resolve_imports_or_builder(process, imports, program_imports.as_ref())?; + if program_id != "credits.aleo" && !process.contains_program(program_native.id()) { + process.add_program(&program_native).map_err(|e| e.to_string())?; + } + + // Insert the proving key if provided. + if let Some(proving_key) = proving_key { + if Self::contains_key(process, program_native.id(), &function_name) { + log(&format!( + "Proving & verifying keys were specified for {program_id} - {function_name:?} but a key already exists in the cache. Using cached keys" + )); + } else { + log(&format!( + "Inserting externally provided proving and verifying keys for {program_id} - {function_name:?}" + )); + process + .insert_proving_key(program_native.id(), &function_name, ProvingKeyNative::from(proving_key)) + .map_err(|e| e.to_string())?; + if let Some(verifying_key) = verifying_key { + process + .insert_verifying_key( + program_native.id(), + &function_name, + VerifyingKeyNative::from(verifying_key), + ) + .map_err(|e| e.to_string())?; + } + } + }; + + // Insert the fee proving key if provided. + if let Some(fee_authorization) = fee_authorization.as_ref() { + if let Some(fee_proving_key) = fee_proving_key { + let credits = ProgramIDNative::from_str("credits.aleo").unwrap(); + + let function_name = if fee_authorization.is_fee_private() { + IdentifierNative::from_str("fee_private").unwrap() + } else { + IdentifierNative::from_str("fee_public").unwrap() + }; + + if Self::contains_key(process, &credits, &function_name) { + log( + "Fee proving & verifying keys were specified but a key already exists in the cache. Using cached keys", + ); + } else { + log("Inserting externally provided fee proving and verifying keys"); + process + .insert_proving_key(&credits, &function_name, ProvingKeyNative::from(fee_proving_key)) + .map_err(|e| e.to_string())?; + if let Some(fee_verifying_key) = fee_verifying_key { + process + .insert_verifying_key(&credits, &function_name, VerifyingKeyNative::from(fee_verifying_key)) + .map_err(|e| e.to_string())?; + } + } + }; + } + + let rng = &mut StdRng::from_entropy(); + let authorization = AuthorizationNative::from(authorization); + + // Construct the locator of the main function. + let locator = { + let request = authorization.peek_next().map_err(|e| e.to_string())?; + LocatorNative::new(*request.program_id(), *request.function_name()).to_string() + }; + + // Determine the consensus version. + let consensus_version = + <CurrentNetwork as Network>::CONSENSUS_VERSION(latest_height).map_err(|e| e.to_string())?; + authorization.check_valid_edition(process, consensus_version).map_err(|e| e.to_string())?; + authorization.check_valid_records(consensus_version).map_err(|e| e.to_string())?; + let varuna_version = match (ConsensusVersion::V1..=ConsensusVersion::V3).contains(&consensus_version) { + true => VarunaVersion::V1, + false => VarunaVersion::V2, + }; + + let (_, mut trace) = process.execute::<CurrentAleo, _>(authorization, rng).map_err(|e| e.to_string())?; + + log("Preparing inclusion proofs for execution"); + if let Some(offline_query) = offline_query.as_ref() { + trace.prepare_async(offline_query).await.map_err(|e| e.to_string())?; + } else { + let query = SnapshotQuery::rest(); + trace.prepare_async(&query).await.map_err(|err| err.to_string())?; + }; + + log("Proving execution"); + let execution = trace + .prove_execution::<CurrentAleo, _>(&locator, varuna_version, &mut StdRng::from_entropy()) + .map_err(|e| e.to_string())?; + + let fee = if let Some(fee_authorization) = fee_authorization { + let fee_authorization = AuthorizationNative::from(fee_authorization); + fee_authorization.check_valid_edition(process, consensus_version).map_err(|e| e.to_string())?; + fee_authorization.check_valid_records(consensus_version).map_err(|e| e.to_string())?; + + let (_, mut fee_trace) = + process.execute::<CurrentAleo, _>(fee_authorization, rng).map_err(|e| e.to_string())?; + + log("Preparing inclusion proofs for execution"); + if let Some(offline_query) = offline_query.as_ref() { + fee_trace.prepare_async(offline_query).await.map_err(|e| e.to_string())?; + } else { + let query = SnapshotQuery::rest(); + fee_trace.prepare_async(&query).await.map_err(|err| err.to_string())?; + }; + + Some(fee_trace.prove_fee::<CurrentAleo, _>(varuna_version, rng).map_err(|e| e.to_string())?) + } else { + None + }; + + // Extract synthesized keys from the process back into ProgramImports + // so the caller can persist them (e.g., to a KeyStore). + let program_imports = if extract_keys { + program_imports.map(|mut pi| { + pi.extract_keys(process); + // Also extract the top-level program's keys. + let edition = edition.unwrap_or(1); + pi.extract_top_level_keys(process, &program_native, &function_name, edition); + pi + }) + } else { + None + }; + + Ok((Transaction::from(TransactionNative::from_execution(execution, fee).map_err(|e| e.to_string())?), program_imports)) + } +} + #[cfg(test)] mod tests { use wasm_bindgen_test::wasm_bindgen_test; diff --git a/wasm/src/programs/manager/imports.rs b/wasm/src/programs/manager/imports.rs index dead530fe..5999161b9 100644 --- a/wasm/src/programs/manager/imports.rs +++ b/wasm/src/programs/manager/imports.rs @@ -180,6 +180,34 @@ impl ProgramImports { Ok(()) } + /// Get a proving key for a specific program and identifier (function or record name). + /// Uses `.take()` semantics — the key is removed from the builder on first call. + /// + /// @param {string} program_name The program name (e.g., "my_program.aleo"). + /// @param {string} identifier The function or record name. + /// @returns {ProvingKey | undefined} + #[wasm_bindgen(js_name = "getProvingKey")] + pub fn get_proving_key(&mut self, program_name: &str, identifier: &str) -> Option<ProvingKey> { + let program_id = ProgramIDNative::from_str(program_name).ok()?; + let fn_id = IdentifierNative::from_str(identifier).ok()?; + let entry = self.entries.get_mut(&program_id)?; + entry.proving_keys.remove(&fn_id).map(ProvingKey::from) + } + + /// Get a verifying key for a specific program and identifier (function or record name). + /// Uses `.take()` semantics — the key is removed from the builder on first call. + /// + /// @param {string} program_name The program name (e.g., "my_program.aleo"). + /// @param {string} identifier The function or record name. + /// @returns {VerifyingKey | undefined} + #[wasm_bindgen(js_name = "getVerifyingKey")] + pub fn get_verifying_key(&mut self, program_name: &str, identifier: &str) -> Option<VerifyingKey> { + let program_id = ProgramIDNative::from_str(program_name).ok()?; + let fn_id = IdentifierNative::from_str(identifier).ok()?; + let entry = self.entries.get_mut(&program_id)?; + entry.verifying_keys.remove(&fn_id).map(VerifyingKey::from) + } + /// Convert this ProgramImports to a plain JavaScript object containing only /// program sources (no keys). Useful for interop with APIs that accept the /// legacy `{ "name.aleo": "source" }` format. @@ -199,17 +227,17 @@ impl ProgramImports { /// Check whether any programs have been added to this builder. /// /// @returns {boolean} - #[wasm_bindgen(js_name = "hasPrograms")] - pub fn has_programs(&self) -> bool { - !self.entries.is_empty() + #[wasm_bindgen(js_name = "isEmpty")] + pub fn is_empty(&self) -> bool { + self.entries.is_empty() } /// Check whether a specific program has been added. /// /// @param {string} name The program name. /// @returns {boolean} - #[wasm_bindgen(js_name = "hasProgram")] - pub fn has_program(&self, name: &str) -> bool { + #[wasm_bindgen(js_name = "contains")] + pub fn contains(&self, name: &str) -> bool { ProgramIDNative::from_str(name).map_or(false, |id| self.entries.contains_key(&id)) } } @@ -221,8 +249,8 @@ impl ProgramImports { pub(crate) fn extract_source(value: &Option<wasm_bindgen::JsValue>) -> Option<String> { let value = value.as_ref()?; // Plain string format: "source code" - if let Some(source) = value.as_string() { - return Some(source); + if let Some(program) = value.as_string() { + return Some(program); } // Structured object format: { source: "source code", ... } Reflect::get(value, &"source".into()).ok().and_then(|v| v.as_string()) @@ -230,65 +258,63 @@ pub(crate) fn extract_source(value: &Option<wasm_bindgen::JsValue>) -> Option<St // Internal methods (not exported to JS). impl ProgramImports { - /// Resolve all programs and their keys into the given process. + /// Load all programs and their keys into the given process. /// - /// This performs the following steps: - /// 1. Iterates all entries and loads program source code into the process - /// (respecting transitive static imports via depth-first resolution). - /// 2. Inserts any pre-provided proving and verifying keys directly into - /// the process, avoiding expensive on-demand key synthesis. - pub(crate) fn resolve_into(&self, process: &mut ProcessNative) -> Result<(), String> { + /// For each entry, this: + /// 1. Recursively resolves transitive static imports (depth-first). + /// 2. Adds the program to the process. + /// 3. Inserts any pre-provided proving and verifying keys, avoiding + /// expensive on-demand key synthesis. + pub(crate) fn load_programs(&self, process: &mut ProcessNative) -> Result<(), String> { let credits_id = ProgramIDNative::from_str("credits.aleo").map_err(|e| e.to_string())?; - // Phase 1: Load all programs into the process. for (program_id, entry) in &self.entries { if program_id == &credits_id { continue; } - let program = entry.program(); - if !process.contains_program(program.id()) { + if !process.contains_program(entry.id()) { log(&format!("Importing program: {program_id}")); - // Resolve transitive static imports first. - self.resolve_program_imports(process, program)?; + self.resolve_program_imports(process, entry.program())?; log(&format!("Adding {program_id} to the process")); - process.add_program_with_edition(program, entry.edition).map_err(|e| e.to_string())?; + process.add_program_with_edition(entry.program(), entry.edition).map_err(|e| e.to_string())?; } + self.insert_entry_keys(process, program_id, entry)?; } - // Phase 2: Insert keys into the process. - for (program_id, entry) in &self.entries { - if program_id == &credits_id { - continue; - } - if !process.contains_program(program_id) { - log(&format!("Program {program_id} not in process, skipping key insertion")); - continue; - } + Ok(()) + } - for (fn_id, pk) in &entry.proving_keys { - if ProgramManager::contains_key(process, program_id, fn_id) { - log(&format!("Key already exists for {program_id}/{fn_id}, skipping")); - continue; - } - log(&format!("Inserting proving key for {program_id}/{fn_id}")); - process.insert_proving_key(program_id, fn_id, pk.clone()).map_err(|e| e.to_string())?; + /// Insert pre-provided proving and verifying keys for a single program entry. + fn insert_entry_keys( + &self, + process: &mut ProcessNative, + program_id: &ProgramIDNative, + entry: &ProgramEntry, + ) -> Result<(), String> { + for (fn_id, pk) in &entry.proving_keys { + if ProgramManager::contains_key(process, program_id, fn_id) { + log(&format!("Key already exists for {program_id}/{fn_id}, skipping")); + continue; } + log(&format!("Inserting proving key for {program_id}/{fn_id}")); + process.insert_proving_key(program_id, fn_id, pk.clone()).map_err(|e| e.to_string())?; + } - for (fn_id, vk) in &entry.verifying_keys { - let has_vk = process.get_stack(program_id).map_or(false, |stack| stack.contains_verifying_key(fn_id)); - if has_vk { - log(&format!("Verifying key already exists for {program_id}/{fn_id}, skipping")); - continue; - } - log(&format!("Inserting verifying key for {program_id}/{fn_id}")); - process.insert_verifying_key(program_id, fn_id, vk.clone()).map_err(|e| e.to_string())?; + for (fn_id, vk) in &entry.verifying_keys { + let has_vk = process.get_stack(program_id).map_or(false, |stack| stack.contains_verifying_key(fn_id)); + if has_vk { + log(&format!("Verifying key already exists for {program_id}/{fn_id}, skipping")); + continue; } + log(&format!("Inserting verifying key for {program_id}/{fn_id}")); + process.insert_verifying_key(program_id, fn_id, vk.clone()).map_err(|e| e.to_string())?; } Ok(()) } - /// Recursively resolve a program's static imports in depth-first order. + /// Recursively resolve a program's static imports in depth-first order, + /// inserting keys for each transitive import as it is loaded. fn resolve_program_imports(&self, process: &mut ProcessNative, program: &ProgramNative) -> Result<(), String> { let credits_id = ProgramIDNative::from_str("credits.aleo").map_err(|e| e.to_string())?; program.imports().keys().try_for_each(|import_id| { @@ -296,15 +322,85 @@ impl ProgramImports { return Ok(()); } if let Some(entry) = self.entries.get(import_id) { - let import = entry.program(); - if !process.contains_program(import.id()) { + if !process.contains_program(entry.id()) { log(&format!("Importing program: {import_id}")); - self.resolve_program_imports(process, import)?; + self.resolve_program_imports(process, entry.program())?; log(&format!("Adding {import_id} to the process")); - process.add_program_with_edition(import, entry.edition).map_err(|e| e.to_string())?; + process.add_program_with_edition(entry.program(), entry.edition).map_err(|e| e.to_string())?; } + self.insert_entry_keys(process, import_id, entry)?; } Ok::<(), String>(()) }) } + + /// Extract synthesized proving and verifying keys from the process back into + /// this builder. For every program entry, iterates its functions and pulls + /// any keys that exist in the process but are not already stored here. + /// + /// This is called after execution completes so the caller can persist the + /// synthesized keys (e.g., to a KeyStore) without re-synthesis. + pub(crate) fn extract_keys(&mut self, process: &ProcessNative) { + let credits_id = ProgramIDNative::from_str("credits.aleo").ok(); + + for (program_id, entry) in &mut self.entries { + if credits_id.as_ref() == Some(program_id) { + continue; + } + + // Iterate all functions in the program and extract keys from the process. + for function_name in entry.program.functions().keys() { + if !entry.proving_keys.contains_key(function_name) { + if let Ok(pk) = process.get_proving_key(program_id, function_name) { + log(&format!("Extracted proving key for {program_id}/{function_name}")); + entry.proving_keys.insert(function_name.clone(), pk); + } + } + if !entry.verifying_keys.contains_key(function_name) { + if let Ok(vk) = process.get_verifying_key(program_id, function_name) { + log(&format!("Extracted verifying key for {program_id}/{function_name}")); + entry.verifying_keys.insert(function_name.clone(), vk); + } + } + } + } + } + + /// Extract the top-level program's proving and verifying keys from the process. + /// + /// Unlike `extract_keys` which only iterates existing entries (imports), this + /// method adds the top-level program as an entry if needed, then extracts its + /// keys for the specified function. This enables the caller to persist + /// top-level keys to a KeyStore after execution. + pub(crate) fn extract_top_level_keys( + &mut self, + process: &ProcessNative, + program: &ProgramNative, + function_name: &IdentifierNative, + edition: u16, + ) { + let program_id = program.id(); + + // Ensure the top-level program exists as an entry in the builder. + self.entries.entry(program_id.clone()).or_insert_with(|| ProgramEntry { + program: program.clone(), + edition, + proving_keys: HashMap::new(), + verifying_keys: HashMap::new(), + }); + + let entry = self.entries.get_mut(program_id).unwrap(); + if !entry.proving_keys.contains_key(function_name) { + if let Ok(pk) = process.get_proving_key(program_id, function_name) { + log(&format!("Extracted top-level proving key for {program_id}/{function_name}")); + entry.proving_keys.insert(function_name.clone(), pk); + } + } + if !entry.verifying_keys.contains_key(function_name) { + if let Ok(vk) = process.get_verifying_key(program_id, function_name) { + log(&format!("Extracted top-level verifying key for {program_id}/{function_name}")); + entry.verifying_keys.insert(function_name.clone(), vk); + } + } + } } diff --git a/wasm/src/programs/manager/mod.rs b/wasm/src/programs/manager/mod.rs index 58d149eb9..53d9d2b6b 100644 --- a/wasm/src/programs/manager/mod.rs +++ b/wasm/src/programs/manager/mod.rs @@ -200,7 +200,7 @@ impl ProgramManager { imports: Option<Object>, program_imports: Option<&ProgramImports>, ) -> Result<(), String> { - if let Some(pi) = program_imports { pi.resolve_into(process) } else { Self::resolve_imports(process, imports) } + if let Some(pi) = program_imports { pi.load_programs(process) } else { Self::resolve_imports(process, imports) } } pub(crate) fn validate_fee_record( @@ -458,25 +458,25 @@ constructor: // ProgramImports builder tests // ─────────────────────────────────────────────────────────────────── - /// Test basic ProgramImports builder operations: new, add_program, has_programs, has_program. + /// Test basic ProgramImports builder operations: new, add_program, is_empty, contains. #[wasm_bindgen_test] fn test_program_imports_builder_basic() { let mut builder = ProgramImports::new(); - assert!(!builder.has_programs(), "New builder should have no programs"); - assert!(!builder.has_program("multiply_test.aleo")); + assert!(builder.is_empty(), "New builder should be empty"); + assert!(!builder.contains("multiply_test.aleo")); builder.add_program("multiply_test.aleo", MULTIPLY_PROGRAM, None).unwrap(); - assert!(builder.has_programs(), "Builder should have programs after add"); - assert!(builder.has_program("multiply_test.aleo")); - assert!(!builder.has_program("addition_test.aleo")); + assert!(!builder.is_empty(), "Builder should not be empty after add"); + assert!(builder.contains("multiply_test.aleo")); + assert!(!builder.contains("addition_test.aleo")); builder.add_program("addition_test.aleo", ADDITION_PROGRAM, None).unwrap(); - assert!(builder.has_program("addition_test.aleo")); + assert!(builder.contains("addition_test.aleo")); } - /// Test that resolve_into loads programs into the process via the builder path. + /// Test that load_programs loads programs into the process via the builder path. #[wasm_bindgen_test] - fn test_program_imports_resolve_into() { + fn test_program_imports_load_programs() { let mut builder = ProgramImports::new(); builder.add_program("multiply_test.aleo", MULTIPLY_PROGRAM, None).unwrap(); builder.add_program("addition_test.aleo", ADDITION_PROGRAM, None).unwrap(); @@ -485,15 +485,15 @@ constructor: let addition_program = ProgramNative::from_str(ADDITION_PROGRAM).unwrap(); let mut process = ProcessNative::load_web().unwrap(); - builder.resolve_into(&mut process).unwrap(); + builder.load_programs(&mut process).unwrap(); assert!(process.contains_program(multiply_program.id()), "multiply_test.aleo should be in process"); assert!(process.contains_program(addition_program.id()), "addition_test.aleo should be in process"); } - /// Test that resolve_into handles transitive dependencies via the builder's own recursive resolution. + /// Test that load_programs handles transitive dependencies via the builder's own recursive resolution. #[wasm_bindgen_test] - fn test_program_imports_resolve_into_transitive() { + fn test_program_imports_load_programs_transitive() { let mut builder = ProgramImports::new(); builder.add_program("multiply_test.aleo", MULTIPLY_PROGRAM, None).unwrap(); builder.add_program("double_test.aleo", MULTIPLY_IMPORT_PROGRAM, None).unwrap(); @@ -502,7 +502,7 @@ constructor: let double_program = ProgramNative::from_str(MULTIPLY_IMPORT_PROGRAM).unwrap(); let mut process = ProcessNative::load_web().unwrap(); - builder.resolve_into(&mut process).unwrap(); + builder.load_programs(&mut process).unwrap(); assert!(process.contains_program(multiply_program.id()), "Transitive dep should be loaded"); assert!(process.contains_program(double_program.id()), "Direct import should be loaded"); @@ -535,4 +535,114 @@ constructor: "Legacy Object's program should NOT be loaded when builder is provided" ); } + + /// Test ProgramImports::from_object creates a builder from a plain JS object. + #[wasm_bindgen_test] + fn test_program_imports_from_object() { + let obj = Object::new(); + Reflect::set(&obj, &JsValue::from_str("multiply_test.aleo"), &JsValue::from_str(MULTIPLY_PROGRAM)).unwrap(); + Reflect::set(&obj, &JsValue::from_str("addition_test.aleo"), &JsValue::from_str(ADDITION_PROGRAM)).unwrap(); + + let builder = ProgramImports::from_object(obj); + assert!(!builder.is_empty()); + assert!(builder.contains("multiply_test.aleo")); + assert!(builder.contains("addition_test.aleo")); + assert!(!builder.contains("nonexistent.aleo")); + } + + /// Test ProgramImports::from_object with structured entries (objects with `source` field). + #[wasm_bindgen_test] + fn test_program_imports_from_object_structured() { + let obj = Object::new(); + + // Structured entry. + let structured = Object::new(); + Reflect::set(&structured, &JsValue::from_str("source"), &JsValue::from_str(MULTIPLY_PROGRAM)).unwrap(); + Reflect::set(&obj, &JsValue::from_str("multiply_test.aleo"), &structured.into()).unwrap(); + + // Plain string entry. + Reflect::set(&obj, &JsValue::from_str("addition_test.aleo"), &JsValue::from_str(ADDITION_PROGRAM)).unwrap(); + + let builder = ProgramImports::from_object(obj); + assert!(builder.contains("multiply_test.aleo"), "Structured entry should be parsed"); + assert!(builder.contains("addition_test.aleo"), "Plain string entry should be parsed"); + } + + /// Test ProgramImports::to_object round-trips program sources. + #[wasm_bindgen_test] + fn test_program_imports_to_object() { + let mut builder = ProgramImports::new(); + builder.add_program("multiply_test.aleo", MULTIPLY_PROGRAM, None).unwrap(); + builder.add_program("addition_test.aleo", ADDITION_PROGRAM, None).unwrap(); + + let obj = builder.to_object(); + + // Verify keys exist. + let mul_val = Reflect::get(&obj, &JsValue::from_str("multiply_test.aleo")).unwrap(); + let add_val = Reflect::get(&obj, &JsValue::from_str("addition_test.aleo")).unwrap(); + assert!(mul_val.is_string(), "multiply_test.aleo should be a string in the output"); + assert!(add_val.is_string(), "addition_test.aleo should be a string in the output"); + + // Verify the source strings are valid programs (round-trip parse). + let mul_src = mul_val.as_string().unwrap(); + let add_src = add_val.as_string().unwrap(); + assert!(ProgramNative::from_str(&mul_src).is_ok(), "multiply_test source should be valid"); + assert!(ProgramNative::from_str(&add_src).is_ok(), "addition_test source should be valid"); + } + + /// Test getProvingKey / getVerifyingKey return None when no keys have been added. + #[wasm_bindgen_test] + fn test_program_imports_get_keys_returns_none_without_keys() { + let mut builder = ProgramImports::new(); + builder.add_program("multiply_test.aleo", MULTIPLY_PROGRAM, None).unwrap(); + + // No keys added — get should return None. + assert!(builder.get_proving_key("multiply_test.aleo", "multiply").is_none()); + assert!(builder.get_verifying_key("multiply_test.aleo", "multiply").is_none()); + + // Unknown program — should also return None. + assert!(builder.get_proving_key("nonexistent.aleo", "foo").is_none()); + assert!(builder.get_verifying_key("nonexistent.aleo", "foo").is_none()); + } + + /// Test extract_keys is a safe no-op when no keys exist in the process. + #[wasm_bindgen_test] + fn test_program_imports_extract_keys_noop() { + let mut builder = ProgramImports::new(); + builder.add_program("multiply_test.aleo", MULTIPLY_PROGRAM, None).unwrap(); + + // Load programs into the process (no execution, so no keys synthesized). + let mut process = ProcessNative::load_web().unwrap(); + builder.load_programs(&mut process).unwrap(); + + // extract_keys should be a safe no-op — no keys to extract. + builder.extract_keys(&process); + + // Verify no keys were extracted (none existed). + assert!(builder.get_proving_key("multiply_test.aleo", "multiply").is_none()); + assert!(builder.get_verifying_key("multiply_test.aleo", "multiply").is_none()); + } + + /// Test add_program with explicit edition parameter. + #[wasm_bindgen_test] + fn test_program_imports_add_program_with_edition() { + let mut builder = ProgramImports::new(); + // Edition 2 — should still load successfully. + builder.add_program("multiply_test.aleo", MULTIPLY_PROGRAM, Some(2)).unwrap(); + assert!(builder.contains("multiply_test.aleo")); + + let mut process = ProcessNative::load_web().unwrap(); + builder.load_programs(&mut process).unwrap(); + let multiply_program = ProgramNative::from_str(MULTIPLY_PROGRAM).unwrap(); + assert!(process.contains_program(multiply_program.id())); + } + + /// Test add_program rejects invalid source code. + #[wasm_bindgen_test] + fn test_program_imports_add_program_invalid_source() { + let mut builder = ProgramImports::new(); + let result = builder.add_program("bad.aleo", "this is not valid aleo code", None); + assert!(result.is_err(), "Invalid source code should return an error"); + } + } From be5e5b7dedf041889ff6a1f555b6271e70162bbc Mon Sep 17 00:00:00 2001 From: Cameron Marshall <cameron.marshall12@gmail.com> Date: Wed, 11 Mar 2026 16:00:36 -0400 Subject: [PATCH 15/17] Add key byte methods and toObject to ProgramImportsBuilder, fix test stubs and review nits --- .../src/IndexedDBKeyStore.ts | 123 +++++++++++ .../template-react-ts/src/workers/worker.ts | 13 ++ sdk/src/program-manager.ts | 56 ++--- sdk/tests/data/dynamic-dispatch.ts | 23 ++ sdk/tests/program-imports.test.ts | 126 +++++++---- wasm/src/programs/manager/execute.rs | 5 +- wasm/src/programs/manager/imports.rs | 207 ++++++++++++++++-- wasm/src/programs/manager/mod.rs | 142 +++++++++++- 8 files changed, 584 insertions(+), 111 deletions(-) create mode 100644 create-leo-app/template-react-ts/src/IndexedDBKeyStore.ts diff --git a/create-leo-app/template-react-ts/src/IndexedDBKeyStore.ts b/create-leo-app/template-react-ts/src/IndexedDBKeyStore.ts new file mode 100644 index 000000000..e22639225 --- /dev/null +++ b/create-leo-app/template-react-ts/src/IndexedDBKeyStore.ts @@ -0,0 +1,123 @@ +import type { + KeyStore, + KeyLocator, +} from "@provablehq/sdk"; +import { + ProvingKey, + VerifyingKey, +} from "@provablehq/sdk"; +import type { FunctionKeyPair } from "@provablehq/sdk"; + +const DB_NAME = "aleo-keystore"; +const DB_VERSION = 1; +const KEYS_STORE = "keys"; +const META_STORE = "metadata"; + +/** + * Browser-native KeyStore backed by IndexedDB. + * + * Implements the SDK's KeyStore interface so that proving and verifying keys + * are persisted across page reloads. Drop-in replacement for LocalFileKeyStore + * in browser/React environments. + */ +export class IndexedDBKeyStore implements KeyStore { + private dbPromise: Promise<IDBDatabase>; + + constructor() { + this.dbPromise = this.open(); + } + + private open(): Promise<IDBDatabase> { + return new Promise((resolve, reject) => { + const req = indexedDB.open(DB_NAME, DB_VERSION); + req.onupgradeneeded = () => { + const db = req.result; + if (!db.objectStoreNames.contains(KEYS_STORE)) { + db.createObjectStore(KEYS_STORE); + } + if (!db.objectStoreNames.contains(META_STORE)) { + db.createObjectStore(META_STORE); + } + }; + req.onsuccess = () => resolve(req.result); + req.onerror = () => reject(req.error); + }); + } + + private async tx( + store: string, + mode: IDBTransactionMode, + ): Promise<IDBObjectStore> { + const db = await this.dbPromise; + return db.transaction(store, mode).objectStore(store); + } + + private request<T>(req: IDBRequest<T>): Promise<T> { + return new Promise((resolve, reject) => { + req.onsuccess = () => resolve(req.result); + req.onerror = () => reject(req.error); + }); + } + + async getKeyBytes(locator: KeyLocator): Promise<Uint8Array | null> { + const store = await this.tx(KEYS_STORE, "readonly"); + const result = await this.request(store.get(locator.locator)); + return result instanceof Uint8Array ? result : null; + } + + async getProvingKey(locator: KeyLocator): Promise<ProvingKey | null> { + const bytes = await this.getKeyBytes(locator); + if (!bytes) return null; + return ProvingKey.fromBytes(bytes); + } + + async getVerifyingKey(locator: KeyLocator): Promise<VerifyingKey | null> { + const bytes = await this.getKeyBytes(locator); + if (!bytes) return null; + return VerifyingKey.fromBytes(bytes); + } + + async setKeys( + proverLocator: KeyLocator, + verifierLocator: KeyLocator, + keys: FunctionKeyPair, + ): Promise<void> { + const [pk, vk] = keys; + const store = await this.tx(KEYS_STORE, "readwrite"); + await this.request(store.put(pk.toBytes(), proverLocator.locator)); + // Reopen transaction (IndexedDB auto-commits after await). + const store2 = await this.tx(KEYS_STORE, "readwrite"); + await this.request(store2.put(vk.toBytes(), verifierLocator.locator)); + } + + async setKeyBytes(keyBytes: Uint8Array, locator: KeyLocator): Promise<void> { + const store = await this.tx(KEYS_STORE, "readwrite"); + await this.request(store.put(keyBytes, locator.locator)); + } + + async getKeyMetadata(locator: string): Promise<any | null> { + const store = await this.tx(META_STORE, "readonly"); + const result = await this.request(store.get(locator)); + return result ?? null; + } + + async has(locator: string): Promise<boolean> { + const store = await this.tx(KEYS_STORE, "readonly"); + const count = await this.request(store.count(locator)); + return count > 0; + } + + async delete(locator: string): Promise<void> { + const keyStore = await this.tx(KEYS_STORE, "readwrite"); + await this.request(keyStore.delete(locator)); + const metaStore = await this.tx(META_STORE, "readwrite"); + await this.request(metaStore.delete(locator)); + } + + async clear(): Promise<void> { + const keyStore = await this.tx(KEYS_STORE, "readwrite"); + await this.request(keyStore.clear()); + const metaStore = await this.tx(META_STORE, "readwrite"); + await this.request(metaStore.clear()); + } +} diff --git a/create-leo-app/template-react-ts/src/workers/worker.ts b/create-leo-app/template-react-ts/src/workers/worker.ts index 1f5a5a0dd..fd2e50f7f 100644 --- a/create-leo-app/template-react-ts/src/workers/worker.ts +++ b/create-leo-app/template-react-ts/src/workers/worker.ts @@ -9,9 +9,13 @@ import { NetworkRecordProvider, } from "@provablehq/sdk"; import { expose, proxy } from "comlink"; +import { IndexedDBKeyStore } from "../IndexedDBKeyStore"; await initThreadPool(); +// Shared KeyStore instance persists keys in IndexedDB across page reloads. +const keyStore = new IndexedDBKeyStore(); + async function localProgramExecution(program, aleoFunction, inputs) { const programManager = new ProgramManager(); @@ -19,6 +23,12 @@ async function localProgramExecution(program, aleoFunction, inputs) { const account = new Account(); programManager.setAccount(account); + // Set up key caching: in-memory for the session, IndexedDB across sessions. + const keyProvider = new AleoKeyProvider(); + keyProvider.useCache(true); + programManager.setKeyProvider(keyProvider); + programManager.setKeyStore(keyStore); + const executionResponse = await programManager.run( program, aleoFunction, @@ -56,6 +66,9 @@ async function deployProgram(program) { programManager.setAccount(account); + // Persist keys across sessions with IndexedDB. + programManager.setKeyStore(keyStore); + // Define a fee to pay to deploy the program const fee = 1.9; // 1.9 Aleo credits diff --git a/sdk/src/program-manager.ts b/sdk/src/program-manager.ts index d7edd853e..9f73614e0 100644 --- a/sdk/src/program-manager.ts +++ b/sdk/src/program-manager.ts @@ -458,7 +458,6 @@ class ProgramManager { */ private async persistExtractedKeys( builder: ProgramImportsBuilder, - imports?: ProgramImports, topLevelProgram?: string, topLevelFunction?: string, ): Promise<void> { @@ -472,13 +471,17 @@ class ProgramManager { } if (!keyStore) return; - // Persist import program keys. - const resolvedImports = imports ?? {}; - for (const [name, importProgram] of Object.entries(resolvedImports)) { + // Enumerate programs/functions from the builder and persist any extracted keys. + const builderObj = builder.toObject() as Record<string, any>; + for (const name of Object.keys(builderObj)) { if (!builder.contains(name)) continue; - const importProgramStr = typeof importProgram === "string" ? importProgram : importProgram.toString(); - const program = Program.fromString(importProgramStr); + // Extract the program source from the builder's output (plain string or structured object). + const entry = builderObj[name]; + const source = typeof entry === "string" ? entry : entry?.program; + if (typeof source !== "string") continue; + + const program = Program.fromString(source); const functions: string[] = Array.from(program.getFunctions()); for (const fnName of functions) { @@ -1115,26 +1118,12 @@ class ProgramManager { if (keys) { [provingKey, verifyingKey] = keys; } else { - console.log( + console.debug( "Function keys not found in KeyStore or KeyProvider. The function keys will be synthesized", ); } } - // Resolve the program imports if they exist - const numberOfImports = programObject.getImports().length; - if (numberOfImports > 0 && !imports) { - try { - imports = <ProgramImports>( - await this.networkClient.getProgramImports(programName) - ); - } catch (e: any) { - logAndThrow( - `Error finding program imports. Network response: '${e.message}'. Please ensure you're connected to a valid Aleo network and the program is deployed to the network.`, - ); - } - } - // Get the fee record from the account if it is not provided in the parameters try { if (privateFee) { @@ -1194,7 +1183,7 @@ class ProgramManager { ); // Auto-persist synthesized keys (both imports and top-level) from the mutated builder to KeyStore. - await this.persistExtractedKeys(programImportsBuilder, imports, program, functionName); + await this.persistExtractedKeys(programImportsBuilder, program, functionName); return transaction; } @@ -1328,7 +1317,7 @@ class ProgramManager { if (keys) { [provingKey, verifyingKey] = keys; } else { - console.log( + console.debug( "Function keys not found in KeyStore or KeyProvider. The function keys will be synthesized", ); } @@ -1345,21 +1334,6 @@ class ProgramManager { } } - // Resolve the program imports if they exist. - console.log("Resolving program imports"); - const numberOfImports = Program.fromString(program).getImports().length; - if (numberOfImports > 0 && !imports) { - try { - imports = <ProgramImports>( - await this.networkClient.getProgramImports(programName) - ); - } catch (e: any) { - logAndThrow( - `Error finding program imports. Network response: '${e.message}'. Please ensure you're connected to a valid Aleo network and the program is deployed to the network.`, - ); - } - } - // If the offline query exists, add the inclusion key. if (offlineQuery && !this.inclusionKeysLoaded) { console.log("Loading inclusion keys for offline proving."); @@ -1391,7 +1365,7 @@ class ProgramManager { ); // Auto-persist synthesized keys (both imports and top-level) from the mutated builder to KeyStore. - await this.persistExtractedKeys(programImportsBuilder, imports, program, functionName); + await this.persistExtractedKeys(programImportsBuilder, program, functionName); return transaction; } @@ -1973,7 +1947,7 @@ class ProgramManager { if (keys) { [provingKey, verifyingKey] = keys; } else { - console.log( + console.debug( "Function keys not found in KeyStore or KeyProvider. The function keys will be synthesized", ); } @@ -2000,7 +1974,7 @@ class ProgramManager { ); // Auto-persist synthesized keys (both imports and top-level) from the mutated builder to KeyStore. - await this.persistExtractedKeys(resolvedImportsBuilder, imports, program, function_name); + await this.persistExtractedKeys(resolvedImportsBuilder, program, function_name); return executionResponse; } diff --git a/sdk/tests/data/dynamic-dispatch.ts b/sdk/tests/data/dynamic-dispatch.ts index 0bd17b678..10782eb11 100644 --- a/sdk/tests/data/dynamic-dispatch.ts +++ b/sdk/tests/data/dynamic-dispatch.ts @@ -52,6 +52,29 @@ constructor: assert.eq true true; `; +/// A simple program used as a static import target. +/// Mirrors multiply_test.aleo from wasm/src/programs/manager/mod.rs. +export const MULTIPLY_PROGRAM = `program multiply_test.aleo; + +function multiply: + input r0 as u32.public; + input r1 as u32.private; + mul r0 r1 into r2; + output r2 as u32.private; +`; + +/// A program that statically imports multiply_test.aleo. +/// Mirrors double_test.aleo from wasm/src/programs/manager/mod.rs. +export const DOUBLE_PROGRAM = `import multiply_test.aleo; + +program double_test.aleo; + +function double_it: + input r0 as u32.private; + call multiply_test.aleo/multiply 2u32 r0 into r1; + output r1 as u32.private; +`; + /// Pre-computed field-encoded identifiers for dynamic dispatch. /// These are Identifier::to_field() values computed from the Rust/WASM tests. export const DD_CONSTANTS_FIELD = "35731532782568442653824738404field"; diff --git a/sdk/tests/program-imports.test.ts b/sdk/tests/program-imports.test.ts index 17a62b1a0..9c427173c 100644 --- a/sdk/tests/program-imports.test.ts +++ b/sdk/tests/program-imports.test.ts @@ -4,6 +4,7 @@ import { Program, ProgramImportsBuilder, ProgramManager, + ProgramManagerBase, ProvingKey, VerifyingKey, } from "@provablehq/sdk/%%NETWORK%%.js"; @@ -11,6 +12,8 @@ import { DD_CALLER_PROGRAM, DD_CONSTANTS_PROGRAM, DD_TEN_PROGRAM, + MULTIPLY_PROGRAM, + DOUBLE_PROGRAM, } from "./data/dynamic-dispatch.js"; import type { KeyStore, KeyLocator } from "../src/keys/keystore/interface.js"; import type { FunctionKeyProvider } from "../src/keys/provider/interface.js"; @@ -97,31 +100,19 @@ describe("ProgramImports & KeyStore integration", () => { it("should merge network-fetched imports with user-provided imports", async () => { const manager = new ProgramManager(); - const networkImports = { "dd_constants.aleo": DD_CONSTANTS_PROGRAM }; - sinon.stub(manager.networkClient, "getProgramImports").resolves(networkImports); - // User provides dd_ten as a dynamic dispatch target (not a static import). + // DOUBLE_PROGRAM has a static `import multiply_test.aleo;` + sinon.stub(manager.networkClient, "getProgramImports").resolves({ + "multiply_test.aleo": MULTIPLY_PROGRAM, + }); const userImports = { "dd_ten.aleo": DD_TEN_PROGRAM }; - const builder: ProgramImportsBuilder = await pm(manager).buildProgramImports(DD_CALLER_PROGRAM, userImports); + const builder: ProgramImportsBuilder = await pm(manager).buildProgramImports(DOUBLE_PROGRAM, userImports); // Both network-fetched and user-provided imports should be present. - expect(builder.contains("dd_constants.aleo")).to.equal(true); + expect(builder.contains("multiply_test.aleo")).to.equal(true); expect(builder.contains("dd_ten.aleo")).to.equal(true); }); - it("should let user-provided imports override network-fetched imports", async () => { - const manager = new ProgramManager(); - const networkImports = { "dd_constants.aleo": DD_CONSTANTS_PROGRAM }; - sinon.stub(manager.networkClient, "getProgramImports").resolves(networkImports); - // User provides their own version of dd_constants. - const userImports = { "dd_constants.aleo": DD_CONSTANTS_PROGRAM }; - - const builder: ProgramImportsBuilder = await pm(manager).buildProgramImports(DD_CALLER_PROGRAM, userImports); - - // User-provided import should be in the builder (overrides network). - expect(builder.contains("dd_constants.aleo")).to.equal(true); - }); - it("should gracefully handle network fetch failures", async () => { const manager = new ProgramManager(); sinon.stub(manager.networkClient, "getProgramImports") @@ -213,6 +204,29 @@ describe("ProgramImports & KeyStore integration", () => { expect(hasLocators).to.include("dd_ten.aleo.get_ten.verifier"); }); + it("should fetch and add keys when keyStore.has returns true", async () => { + const fakePk = {} as ProvingKey; + const fakeVk = {} as VerifyingKey; + const store = createMockKeyStore({ + "multiply_test.aleo.multiply": { pk: fakePk, vk: fakeVk }, + }); + const manager = new ProgramManager(); + manager.setKeyStore(store as unknown as KeyStore); + + const builder = new ProgramImportsBuilder(); + builder.addProgram("multiply_test.aleo", MULTIPLY_PROGRAM); + + // Stub the WASM builder methods so fake keys don't trigger WASM type errors + sinon.stub(builder, "addProvingKey"); + sinon.stub(builder, "addVerifyingKey"); + + await pm(manager).loadKeysFromStore(builder, "multiply_test.aleo", MULTIPLY_PROGRAM); + + expect(store.has.called).to.equal(true); + expect(store.getProvingKey.called).to.equal(true); + expect(store.getVerifyingKey.called).to.equal(true); + }); + it("should swallow keyStore errors without throwing", async () => { const store = createMockKeyStore(); store.has.rejects(new Error("disk error")); @@ -260,7 +274,7 @@ describe("ProgramImports & KeyStore integration", () => { builder.addProgram("dd_constants.aleo", DD_CONSTANTS_PROGRAM); // Should not throw. - await pm(manager).persistExtractedKeys(builder, { "dd_constants.aleo": DD_CONSTANTS_PROGRAM }); + await pm(manager).persistExtractedKeys(builder); }); it("should skip programs not in the builder", async () => { @@ -271,7 +285,7 @@ describe("ProgramImports & KeyStore integration", () => { const builder = new ProgramImportsBuilder(); // Builder is empty — does not contain dd_constants.aleo. - await pm(manager).persistExtractedKeys(builder, { "dd_constants.aleo": DD_CONSTANTS_PROGRAM }); + await pm(manager).persistExtractedKeys(builder); expect(store.setKeys.called).to.equal(false); }); @@ -285,7 +299,7 @@ describe("ProgramImports & KeyStore integration", () => { builder.addProgram("dd_constants.aleo", DD_CONSTANTS_PROGRAM); // No keys added to the builder. - await pm(manager).persistExtractedKeys(builder, { "dd_constants.aleo": DD_CONSTANTS_PROGRAM }); + await pm(manager).persistExtractedKeys(builder); expect(store.setKeys.called).to.equal(false); }); @@ -300,7 +314,7 @@ describe("ProgramImports & KeyStore integration", () => { builder.addProgram("dd_constants.aleo", DD_CONSTANTS_PROGRAM); // Should not throw. - await pm(manager).persistExtractedKeys(builder, { "dd_constants.aleo": DD_CONSTANTS_PROGRAM }); + await pm(manager).persistExtractedKeys(builder); }); it("should swallow keyProvider.keyStore() errors without throwing", async () => { @@ -309,23 +323,24 @@ describe("ProgramImports & KeyStore integration", () => { const manager = new ProgramManager(undefined, provider); const builder = new ProgramImportsBuilder(); + builder.addProgram("dd_constants.aleo", DD_CONSTANTS_PROGRAM); // Should not throw. - await pm(manager).persistExtractedKeys(builder, { "dd_constants.aleo": DD_CONSTANTS_PROGRAM }); + await pm(manager).persistExtractedKeys(builder); }); - it("should accept Program objects in the imports map", async () => { + it("should not persist when only one of PK/VK is present", async () => { const store = createMockKeyStore(); const provider = createMockKeyProvider(store); const manager = new ProgramManager(undefined, provider); const builder = new ProgramImportsBuilder(); builder.addProgram("dd_constants.aleo", DD_CONSTANTS_PROGRAM); + // Builder has the program but no keys — getProvingKey/getVerifyingKey return undefined. - const programObj = Program.fromString(DD_CONSTANTS_PROGRAM); + await pm(manager).persistExtractedKeys(builder); - // Should not throw — the method coerces Program objects to strings. - await pm(manager).persistExtractedKeys(builder, { "dd_constants.aleo": programObj }); + expect(store.setKeys.called).to.equal(false); }); it("should persist top-level keys when topLevelProgram and topLevelFunction are provided", async () => { @@ -343,7 +358,7 @@ describe("ProgramImports & KeyStore integration", () => { // with keys from the process. Here the builder has no keys, so getProvingKey // returns undefined and setKeys is not called. This verifies the guard works. - await pm(manager).persistExtractedKeys(builder, {}, DD_CONSTANTS_PROGRAM, "get_value"); + await pm(manager).persistExtractedKeys(builder, DD_CONSTANTS_PROGRAM, "get_value"); // Builder had no keys (getProvingKey returns undefined), so setKeys should not fire. expect(store.setKeys.called).to.equal(false); @@ -357,7 +372,7 @@ describe("ProgramImports & KeyStore integration", () => { const builder = new ProgramImportsBuilder(); builder.addProgram("dd_constants.aleo", DD_CONSTANTS_PROGRAM); - await pm(manager).persistExtractedKeys(builder, {}); + await pm(manager).persistExtractedKeys(builder); // No top-level params means no top-level persistence attempt. expect(store.setKeys.called).to.equal(false); @@ -375,7 +390,7 @@ describe("ProgramImports & KeyStore integration", () => { // Should resolve without throwing despite the write error. let threw = false; try { - await pm(manager).persistExtractedKeys(builder, {}, DD_CONSTANTS_PROGRAM, "get_value"); + await pm(manager).persistExtractedKeys(builder, DD_CONSTANTS_PROGRAM, "get_value"); } catch { threw = true; } @@ -440,6 +455,21 @@ describe("ProgramImports & KeyStore integration", () => { expect(store.has.called).to.equal(true); }); + it("should make KeyStore available to persistExtractedKeys", async () => { + const store = createMockKeyStore(); + const manager = new ProgramManager(); + manager.setKeyStore(store as unknown as KeyStore); + + const builder = new ProgramImportsBuilder(); + builder.addProgram("dd_constants.aleo", DD_CONSTANTS_PROGRAM); + + await pm(manager).persistExtractedKeys(builder); + + // setKeys not called (no keys in builder), but method completed without error, + // meaning the KeyStore was resolved via setKeyStore. + expect(store.setKeys.called).to.equal(false); + }); + it("should take precedence over keyProvider.keyStore()", async () => { const directStore = createMockKeyStore(); const providerStore = createMockKeyStore(); @@ -556,8 +586,7 @@ describe("ProgramImports & KeyStore integration", () => { // Stub the WASM call to return our fake key pair sinon.stub(manager.networkClient, "getProgramImports").resolves({}); - const { ProgramManager: WasmPM } = await import("@provablehq/sdk/%%NETWORK%%.js"); - const synthesizeStub = sinon.stub(WasmPM, "synthesizeKeyPair").resolves(fakeKeyPair); + const synthesizeStub = sinon.stub(ProgramManagerBase, "synthesizeKeyPair").resolves(fakeKeyPair as any); try { await manager.synthesizeKeys(DD_CONSTANTS_PROGRAM, "get_value", ["0u8"]); @@ -583,8 +612,7 @@ describe("ProgramImports & KeyStore integration", () => { }; sinon.stub(manager.networkClient, "getProgramImports").resolves({}); - const { ProgramManager: WasmPM } = await import("@provablehq/sdk/%%NETWORK%%.js"); - const synthesizeStub = sinon.stub(WasmPM, "synthesizeKeyPair").resolves(fakeKeyPair); + const synthesizeStub = sinon.stub(ProgramManagerBase, "synthesizeKeyPair").resolves(fakeKeyPair as any); try { const keys = await manager.synthesizeKeys(DD_CONSTANTS_PROGRAM, "get_value", ["0u8"]); @@ -610,8 +638,7 @@ describe("ProgramImports & KeyStore integration", () => { }; sinon.stub(manager.networkClient, "getProgramImports").resolves({}); - const { ProgramManager: WasmPM } = await import("@provablehq/sdk/%%NETWORK%%.js"); - const synthesizeStub = sinon.stub(WasmPM, "synthesizeKeyPair").resolves(fakeKeyPair); + const synthesizeStub = sinon.stub(ProgramManagerBase, "synthesizeKeyPair").resolves(fakeKeyPair as any); try { const keys = await manager.synthesizeKeys(DD_CONSTANTS_PROGRAM, "get_value", ["0u8"]); @@ -625,7 +652,7 @@ describe("ProgramImports & KeyStore integration", () => { }); }); - describe("run() import key loading", () => { + describe("run() integration", () => { it("should auto-build ProgramImportsBuilder from KeyStore when none provided", async () => { const store = createMockKeyStore(); const provider = createMockKeyProvider(store); @@ -635,8 +662,7 @@ describe("ProgramImports & KeyStore integration", () => { const buildSpy = sinon.spy(pm(manager), "buildProgramImports"); // Stub the WASM execution to avoid actual execution - const { ProgramManager: WasmPM } = await import("@provablehq/sdk/%%NETWORK%%.js"); - const execStub = sinon.stub(WasmPM, "executeFunctionOfflineWithImports").resolves({} as any); + const execStub = sinon.stub(ProgramManagerBase, "executeFunctionOfflineWithImports").resolves({} as any); try { manager.setAccount({ privateKey: () => ({}) } as any); @@ -655,8 +681,7 @@ describe("ProgramImports & KeyStore integration", () => { const buildSpy = sinon.spy(pm(manager), "buildProgramImports"); - const { ProgramManager: WasmPM } = await import("@provablehq/sdk/%%NETWORK%%.js"); - const execStub = sinon.stub(WasmPM, "executeFunctionOfflineWithImports").resolves({} as any); + const execStub = sinon.stub(ProgramManagerBase, "executeFunctionOfflineWithImports").resolves({} as any); try { manager.setAccount({ privateKey: () => ({}) } as any); @@ -668,5 +693,24 @@ describe("ProgramImports & KeyStore integration", () => { execStub.restore(); } }); + + it("should call persistExtractedKeys after execution", async () => { + const store = createMockKeyStore(); + const provider = createMockKeyProvider(store); + const manager = new ProgramManager(undefined, provider); + + const persistSpy = sinon.spy(pm(manager), "persistExtractedKeys"); + + const execStub = sinon.stub(ProgramManagerBase, "executeFunctionOfflineWithImports").resolves({} as any); + + try { + manager.setAccount({ privateKey: () => ({}) } as any); + await manager.run(DD_CONSTANTS_PROGRAM, "get_value", ["0u8"], false); + + expect(persistSpy.calledOnce).to.equal(true); + } finally { + execStub.restore(); + } + }); }); }); diff --git a/wasm/src/programs/manager/execute.rs b/wasm/src/programs/manager/execute.rs index d25176eb7..d700eab57 100644 --- a/wasm/src/programs/manager/execute.rs +++ b/wasm/src/programs/manager/execute.rs @@ -1058,7 +1058,10 @@ impl ProgramManager { None }; - Ok((Transaction::from(TransactionNative::from_execution(execution, fee).map_err(|e| e.to_string())?), program_imports)) + Ok(( + Transaction::from(TransactionNative::from_execution(execution, fee).map_err(|e| e.to_string())?), + program_imports, + )) } } diff --git a/wasm/src/programs/manager/imports.rs b/wasm/src/programs/manager/imports.rs index 5999161b9..4f1adc436 100644 --- a/wasm/src/programs/manager/imports.rs +++ b/wasm/src/programs/manager/imports.rs @@ -28,10 +28,11 @@ use crate::{ }, }; -use js_sys::{Object, Reflect}; +use js_sys::{Object, Reflect, Uint8Array}; use snarkvm_synthesizer_program::StackTrait; +use snarkvm_wasm::utilities::{FromBytes, ToBytes}; use std::{collections::HashMap, str::FromStr}; -use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::{JsCast, prelude::wasm_bindgen}; use super::ProgramManager; @@ -88,27 +89,47 @@ impl ProgramImports { /// Create a ProgramImports from a plain JavaScript object. /// - /// Accepts the legacy format where keys are program names and values are - /// either source code strings or objects with a `source` property: + /// Accepts three formats: /// ```js + /// // 1. Plain string — source code only. /// { "my_program.aleo": "program source..." } - /// // or - /// { "my_program.aleo": { source: "program source..." } } + /// + /// // 2. Structured — program source with optional edition. + /// { "my_program.aleo": { program: "program source..." } } + /// + /// // 3. Structured with keys — program source plus proving/verifying keys per function. + /// { + /// "my_program.aleo": { + /// program: "program source...", + /// keys: { + /// "my_function": { + /// provingKey: Uint8Array, + /// verifyingKey: Uint8Array + /// } + /// } + /// } + /// } /// ``` /// /// Programs created via this method default to edition 1. /// - /// @param {Object} object A plain JavaScript object mapping program names to source code. + /// @param {Object} object A plain JavaScript object mapping program names to source code + /// and optional keys. /// @returns {ProgramImports} #[wasm_bindgen(js_name = "fromObject")] pub fn from_object(object: Object) -> Self { let mut imports = Self::new(); let keys = Object::keys(&object); for i in 0..keys.length() { - let Some(name) = keys.get(i).as_string() else { continue; }; + let Some(name) = keys.get(i).as_string() else { + continue; + }; let value = Reflect::get(&object, &name.as_str().into()).ok(); if let Some(source) = extract_source(&value) { let _ = imports.add_program(&name, &source, None); + if let Some(value) = value.as_ref() { + extract_keys_from_value(&mut imports, &name, value); + } } } imports @@ -154,9 +175,10 @@ impl ProgramImports { pub fn add_proving_key(&mut self, program_name: &str, identifier: &str, key: ProvingKey) -> Result<(), String> { let program_id = ProgramIDNative::from_str(program_name).map_err(|e| e.to_string())?; let fn_id = IdentifierNative::from_str(identifier).map_err(|e| e.to_string())?; - let entry = self.entries.get_mut(&program_id).ok_or_else(|| { - format!("Program '{program_name}' must be added via addProgram before adding keys") - })?; + let entry = self + .entries + .get_mut(&program_id) + .ok_or_else(|| format!("Program '{program_name}' must be added via addProgram before adding keys"))?; entry.proving_keys.insert(fn_id, ProvingKeyNative::from(key)); Ok(()) } @@ -173,13 +195,61 @@ impl ProgramImports { pub fn add_verifying_key(&mut self, program_name: &str, identifier: &str, key: VerifyingKey) -> Result<(), String> { let program_id = ProgramIDNative::from_str(program_name).map_err(|e| e.to_string())?; let fn_id = IdentifierNative::from_str(identifier).map_err(|e| e.to_string())?; - let entry = self.entries.get_mut(&program_id).ok_or_else(|| { - format!("Program '{program_name}' must be added via addProgram before adding keys") - })?; + let entry = self + .entries + .get_mut(&program_id) + .ok_or_else(|| format!("Program '{program_name}' must be added via addProgram before adding keys"))?; entry.verifying_keys.insert(fn_id, VerifyingKeyNative::from(key)); Ok(()) } + /// Add a proving key from its byte representation. + /// + /// Deserializes the bytes into a native proving key and stores it. The program + /// must already have been added via `addProgram`. + /// + /// @param {string} program_name The program name (e.g., "my_program.aleo"). + /// @param {string} identifier The function name or record name the key belongs to. + /// @param {Uint8Array} bytes The proving key bytes. + #[wasm_bindgen(js_name = "addProvingKeyBytes")] + pub fn add_proving_key_bytes(&mut self, program_name: &str, identifier: &str, bytes: &[u8]) -> Result<(), String> { + let program_id = ProgramIDNative::from_str(program_name).map_err(|e| e.to_string())?; + let fn_id = IdentifierNative::from_str(identifier).map_err(|e| e.to_string())?; + let entry = self + .entries + .get_mut(&program_id) + .ok_or_else(|| format!("Program '{program_name}' must be added via addProgram before adding keys"))?; + let pk = ProvingKeyNative::from_bytes_le(bytes).map_err(|e| e.to_string())?; + entry.proving_keys.insert(fn_id, pk); + Ok(()) + } + + /// Add a verifying key from its byte representation. + /// + /// Deserializes the bytes into a native verifying key and stores it. The program + /// must already have been added via `addProgram`. + /// + /// @param {string} program_name The program name (e.g., "my_program.aleo"). + /// @param {string} identifier The function name or record name the key belongs to. + /// @param {Uint8Array} bytes The verifying key bytes. + #[wasm_bindgen(js_name = "addVerifyingKeyBytes")] + pub fn add_verifying_key_bytes( + &mut self, + program_name: &str, + identifier: &str, + bytes: &[u8], + ) -> Result<(), String> { + let program_id = ProgramIDNative::from_str(program_name).map_err(|e| e.to_string())?; + let fn_id = IdentifierNative::from_str(identifier).map_err(|e| e.to_string())?; + let entry = self + .entries + .get_mut(&program_id) + .ok_or_else(|| format!("Program '{program_name}' must be added via addProgram before adding keys"))?; + let vk = VerifyingKeyNative::from_bytes_le(bytes).map_err(|e| e.to_string())?; + entry.verifying_keys.insert(fn_id, vk); + Ok(()) + } + /// Get a proving key for a specific program and identifier (function or record name). /// Uses `.take()` semantics — the key is removed from the builder on first call. /// @@ -208,18 +278,76 @@ impl ProgramImports { entry.verifying_keys.remove(&fn_id).map(VerifyingKey::from) } - /// Convert this ProgramImports to a plain JavaScript object containing only - /// program sources (no keys). Useful for interop with APIs that accept the - /// legacy `{ "name.aleo": "source" }` format. + /// Convert this ProgramImports to a plain JavaScript object. + /// + /// Entries without keys use the simple `{ "name.aleo": "source" }` format. + /// Entries with keys use the structured format: + /// ```js + /// { + /// "name.aleo": { + /// program: "program source...", + /// keys: { + /// "function_name": { + /// provingKey: Uint8Array, + /// verifyingKey: Uint8Array + /// } + /// } + /// } + /// } + /// ``` /// /// @returns {Object} #[wasm_bindgen(js_name = "toObject")] pub fn to_object(&self) -> Object { let obj = Object::new(); for (program_id, entry) in &self.entries { - let source = entry.program().to_string(); let name = program_id.to_string(); - Reflect::set(&obj, &name.as_str().into(), &source.as_str().into()).unwrap(); + let source = entry.program().to_string(); + + let has_keys = !entry.proving_keys.is_empty() || !entry.verifying_keys.is_empty(); + if !has_keys { + Reflect::set(&obj, &name.as_str().into(), &source.as_str().into()).unwrap(); + continue; + } + + // Structured format with keys. + let structured = Object::new(); + Reflect::set(&structured, &"program".into(), &source.as_str().into()).unwrap(); + + let keys_obj = Object::new(); + // Collect all identifier names that have at least one key. + let mut fn_ids: Vec<&IdentifierNative> = Vec::new(); + for fn_id in entry.proving_keys.keys() { + fn_ids.push(fn_id); + } + for fn_id in entry.verifying_keys.keys() { + if !entry.proving_keys.contains_key(fn_id) { + fn_ids.push(fn_id); + } + } + + for fn_id in fn_ids { + let fn_name = fn_id.to_string(); + let key_pair = Object::new(); + + if let Some(pk) = entry.proving_keys.get(fn_id) { + if let Ok(bytes) = pk.to_bytes_le() { + let arr = Uint8Array::from(bytes.as_slice()); + Reflect::set(&key_pair, &"provingKey".into(), &arr.into()).unwrap(); + } + } + if let Some(vk) = entry.verifying_keys.get(fn_id) { + if let Ok(bytes) = vk.to_bytes_le() { + let arr = Uint8Array::from(bytes.as_slice()); + Reflect::set(&key_pair, &"verifyingKey".into(), &arr.into()).unwrap(); + } + } + + Reflect::set(&keys_obj, &fn_name.as_str().into(), &key_pair.into()).unwrap(); + } + + Reflect::set(&structured, &"keys".into(), &keys_obj.into()).unwrap(); + Reflect::set(&obj, &name.as_str().into(), &structured.into()).unwrap(); } obj } @@ -242,18 +370,51 @@ impl ProgramImports { } } -/// Extract source code from a JS value (plain string or object with `.source`). +/// Extract source code from a JS value (plain string or object with `.program`). /// /// Supports both legacy format (`"source code"`) and structured format -/// (`{ source: "source code", ... }`). +/// (`{ program: "source code", ... }`). pub(crate) fn extract_source(value: &Option<wasm_bindgen::JsValue>) -> Option<String> { let value = value.as_ref()?; // Plain string format: "source code" if let Some(program) = value.as_string() { return Some(program); } - // Structured object format: { source: "source code", ... } - Reflect::get(value, &"source".into()).ok().and_then(|v| v.as_string()) + // Structured object format: { program: "source code", ... } + Reflect::get(value, &"program".into()).ok().and_then(|v| v.as_string()) +} + +/// Extract proving and verifying keys from a structured JS value's `keys` property. +/// +/// Keys must be provided as `Uint8Array` byte representations. +/// Expects the format: +/// ```js +/// { keys: { "fn_name": { provingKey: Uint8Array, verifyingKey: Uint8Array } } } +/// ``` +/// Silently skips any entries that are missing, malformed, or not valid `Uint8Array` values. +fn extract_keys_from_value(imports: &mut ProgramImports, program_name: &str, value: &wasm_bindgen::JsValue) { + let Ok(keys_obj) = Reflect::get(value, &"keys".into()) else { return }; + if keys_obj.is_undefined() || keys_obj.is_null() { + return; + } + let Ok(keys_obj) = keys_obj.dyn_into::<Object>() else { return }; + let fn_names = Object::keys(&keys_obj); + + for i in 0..fn_names.length() { + let Some(fn_name) = fn_names.get(i).as_string() else { continue }; + let Ok(key_pair) = Reflect::get(&keys_obj, &fn_name.as_str().into()) else { continue }; + + if let Ok(pk_val) = Reflect::get(&key_pair, &"provingKey".into()) { + if let Ok(arr) = pk_val.dyn_into::<Uint8Array>() { + let _ = imports.add_proving_key_bytes(program_name, &fn_name, &arr.to_vec()); + } + } + if let Ok(vk_val) = Reflect::get(&key_pair, &"verifyingKey".into()) { + if let Ok(arr) = vk_val.dyn_into::<Uint8Array>() { + let _ = imports.add_verifying_key_bytes(program_name, &fn_name, &arr.to_vec()); + } + } + } } // Internal methods (not exported to JS). diff --git a/wasm/src/programs/manager/mod.rs b/wasm/src/programs/manager/mod.rs index 53d9d2b6b..47c27de53 100644 --- a/wasm/src/programs/manager/mod.rs +++ b/wasm/src/programs/manager/mod.rs @@ -138,7 +138,7 @@ impl ProgramManager { /// Resolve all imports from the imports object and load them into the process. /// Accepts plain string values (`{ "name.aleo": "source" }`) or objects with a - /// `source` property (`{ "name.aleo": { source: "..." } }`). + /// `program` property (`{ "name.aleo": { program: "..." } }`). /// /// To provide proving/verifying keys for imported programs, use `ProgramImports` /// (the builder) instead of this legacy Object path. @@ -230,7 +230,7 @@ mod tests { use crate::Metadata; use js_sys::{Object, Reflect}; - use wasm_bindgen::JsValue; + use wasm_bindgen::{JsCast, JsValue}; use wasm_bindgen_test::*; pub const MULTIPLY_PROGRAM: &str = r#"// The 'multiply_test.aleo' program which is imported by the 'double_test.aleo' program. @@ -417,7 +417,7 @@ constructor: // Structured entry: object with `source` field. let structured_entry = Object::new(); - Reflect::set(&structured_entry, &JsValue::from_str("source"), &JsValue::from_str(MULTIPLY_PROGRAM)).unwrap(); + Reflect::set(&structured_entry, &JsValue::from_str("program"), &JsValue::from_str(MULTIPLY_PROGRAM)).unwrap(); Reflect::set(&imports, &JsValue::from_str("multiply_test.aleo"), &structured_entry.into()).unwrap(); // Plain string entry (backward compat). @@ -443,7 +443,7 @@ constructor: // double_test.aleo as structured entry (imports multiply_test.aleo statically). let structured_entry = Object::new(); - Reflect::set(&structured_entry, &JsValue::from_str("source"), &JsValue::from_str(MULTIPLY_IMPORT_PROGRAM)) + Reflect::set(&structured_entry, &JsValue::from_str("program"), &JsValue::from_str(MULTIPLY_IMPORT_PROGRAM)) .unwrap(); Reflect::set(&imports, &JsValue::from_str("double_test.aleo"), &structured_entry.into()).unwrap(); @@ -557,7 +557,7 @@ constructor: // Structured entry. let structured = Object::new(); - Reflect::set(&structured, &JsValue::from_str("source"), &JsValue::from_str(MULTIPLY_PROGRAM)).unwrap(); + Reflect::set(&structured, &JsValue::from_str("program"), &JsValue::from_str(MULTIPLY_PROGRAM)).unwrap(); Reflect::set(&obj, &JsValue::from_str("multiply_test.aleo"), &structured.into()).unwrap(); // Plain string entry. @@ -568,6 +568,32 @@ constructor: assert!(builder.contains("addition_test.aleo"), "Plain string entry should be parsed"); } + /// Test ProgramImports::from_object with structured entries that include a `keys` property. + /// Verifies that the `keys` object is parsed without errors and non-Uint8Array values + /// are silently skipped. + #[wasm_bindgen_test] + fn test_program_imports_from_object_structured_with_keys() { + let obj = Object::new(); + + // Build a structured entry with a `keys` map containing non-Uint8Array placeholder values. + let structured = Object::new(); + Reflect::set(&structured, &JsValue::from_str("program"), &JsValue::from_str(MULTIPLY_PROGRAM)).unwrap(); + + let keys_map = Object::new(); + let fn_keys = Object::new(); + // These are strings, not Uint8Array, so dyn_into will fail and they should be silently skipped. + Reflect::set(&fn_keys, &JsValue::from_str("provingKey"), &JsValue::from_str("not a key")).unwrap(); + Reflect::set(&fn_keys, &JsValue::from_str("verifyingKey"), &JsValue::from_str("not a key")).unwrap(); + Reflect::set(&keys_map, &JsValue::from_str("multiply"), &fn_keys.into()).unwrap(); + Reflect::set(&structured, &JsValue::from_str("keys"), &keys_map.into()).unwrap(); + + Reflect::set(&obj, &JsValue::from_str("multiply_test.aleo"), &structured.into()).unwrap(); + + let builder = ProgramImports::from_object(obj); + // Program should still be added even though key extraction was skipped. + assert!(builder.contains("multiply_test.aleo"), "Program should be added despite invalid key types"); + } + /// Test ProgramImports::to_object round-trips program sources. #[wasm_bindgen_test] fn test_program_imports_to_object() { @@ -645,4 +671,110 @@ constructor: assert!(result.is_err(), "Invalid source code should return an error"); } + /// Test that addProvingKeyBytes / addVerifyingKeyBytes require an existing program. + #[wasm_bindgen_test] + fn test_program_imports_add_key_bytes_requires_program() { + let mut builder = ProgramImports::new(); + let result = builder.add_proving_key_bytes("nonexistent.aleo", "multiply", &[0u8; 4]); + assert!(result.is_err(), "Should fail when program not added"); + assert!(result.unwrap_err().contains("must be added via addProgram")); + + let result = builder.add_verifying_key_bytes("nonexistent.aleo", "multiply", &[0u8; 4]); + assert!(result.is_err(), "Should fail when program not added"); + assert!(result.unwrap_err().contains("must be added via addProgram")); + } + + /// Test that addProvingKeyBytes / addVerifyingKeyBytes reject invalid bytes. + #[wasm_bindgen_test] + fn test_program_imports_add_key_bytes_invalid() { + let mut builder = ProgramImports::new(); + builder.add_program("multiply_test.aleo", MULTIPLY_PROGRAM, None).unwrap(); + + let result = builder.add_proving_key_bytes("multiply_test.aleo", "multiply", &[0u8; 4]); + assert!(result.is_err(), "Invalid bytes should fail deserialization"); + + let result = builder.add_verifying_key_bytes("multiply_test.aleo", "multiply", &[0u8; 4]); + assert!(result.is_err(), "Invalid bytes should fail deserialization"); + } + + /// Test that addProvingKeyBytes / addVerifyingKeyBytes successfully round-trip + /// with real key bytes from the built-in verifier parameters. + #[wasm_bindgen_test] + fn test_program_imports_add_verifying_key_bytes_roundtrip() { + // Load real verifying key bytes from built-in parameters. + let vk_bytes = crate::types::native::parameters::TransferPublicVerifier::load_bytes().unwrap(); + let vk = VerifyingKey::from_bytes(&vk_bytes).unwrap(); + + let mut builder = ProgramImports::new(); + builder.add_program("multiply_test.aleo", MULTIPLY_PROGRAM, None).unwrap(); + builder.add_verifying_key_bytes("multiply_test.aleo", "multiply", &vk_bytes).unwrap(); + + // Extract via getter and verify it matches. + let extracted = builder.get_verifying_key("multiply_test.aleo", "multiply").unwrap(); + assert_eq!(extracted.checksum(), vk.checksum(), "Round-tripped verifying key should match"); + } + + /// Test that to_object outputs structured format with keys as Uint8Array when keys are present. + #[wasm_bindgen_test] + fn test_program_imports_to_object_with_keys() { + let vk_bytes = crate::types::native::parameters::TransferPublicVerifier::load_bytes().unwrap(); + + let mut builder = ProgramImports::new(); + builder.add_program("multiply_test.aleo", MULTIPLY_PROGRAM, None).unwrap(); + builder.add_verifying_key_bytes("multiply_test.aleo", "multiply", &vk_bytes).unwrap(); + + // Also add a program without keys to verify it uses the simple format. + builder.add_program("addition_test.aleo", ADDITION_PROGRAM, None).unwrap(); + + let obj = builder.to_object(); + + // addition_test.aleo should be a plain string (no keys). + let add_val = Reflect::get(&obj, &JsValue::from_str("addition_test.aleo")).unwrap(); + assert!(add_val.is_string(), "Program without keys should use plain string format"); + + // multiply_test.aleo should be a structured object. + let mul_val = Reflect::get(&obj, &JsValue::from_str("multiply_test.aleo")).unwrap(); + assert!(!mul_val.is_string(), "Program with keys should use structured format"); + + // Verify source is present. + let source = Reflect::get(&mul_val, &JsValue::from_str("program")).unwrap(); + assert!(source.is_string(), "Structured entry should have a source property"); + assert!(ProgramNative::from_str(&source.as_string().unwrap()).is_ok(), "Source should be valid program"); + + // Verify keys structure. + let keys = Reflect::get(&mul_val, &JsValue::from_str("keys")).unwrap(); + assert!(!keys.is_undefined(), "Structured entry should have a keys property"); + + let fn_keys = Reflect::get(&keys, &JsValue::from_str("multiply")).unwrap(); + assert!(!fn_keys.is_undefined(), "Keys should contain the function name"); + + let vk_val = Reflect::get(&fn_keys, &JsValue::from_str("verifyingKey")).unwrap(); + assert!(vk_val.is_instance_of::<js_sys::Uint8Array>(), "Verifying key should be a Uint8Array"); + } + + /// Test that to_object → fromObject round-trips keys via Uint8Array bytes. + #[wasm_bindgen_test] + fn test_program_imports_to_object_from_object_roundtrip_keys() { + let vk_bytes = crate::types::native::parameters::TransferPublicVerifier::load_bytes().unwrap(); + let original_checksum = VerifyingKey::from_bytes(&vk_bytes).unwrap().checksum(); + + let mut builder = ProgramImports::new(); + builder.add_program("multiply_test.aleo", MULTIPLY_PROGRAM, None).unwrap(); + builder.add_verifying_key_bytes("multiply_test.aleo", "multiply", &vk_bytes).unwrap(); + + // Round-trip: to_object → fromObject. + let obj = builder.to_object(); + let mut restored = ProgramImports::from_object(obj); + + assert!(restored.contains("multiply_test.aleo"), "Program should survive round-trip"); + + // Verify the verifying key survived the round-trip. + let restored_vk = restored.get_verifying_key("multiply_test.aleo", "multiply"); + assert!(restored_vk.is_some(), "Verifying key should survive to_object → fromObject round-trip"); + assert_eq!( + restored_vk.unwrap().checksum(), + original_checksum, + "Round-tripped verifying key should have same checksum" + ); + } } From 953a7316322d926ea4e242ccf92c8f5e2b6708ac Mon Sep 17 00:00:00 2001 From: Cameron Marshall <cameron.marshall12@gmail.com> Date: Wed, 11 Mar 2026 16:58:23 -0400 Subject: [PATCH 16/17] Fix review feedback: restore type: module, add programNames/getProgram to avoid key serialization, fix test stubs --- sdk/src/program-manager.ts | 14 +++++------- wasm/src/programs/manager/imports.rs | 29 ++++++++++++++++++++++-- wasm/src/programs/manager/mod.rs | 33 ++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 11 deletions(-) diff --git a/sdk/src/program-manager.ts b/sdk/src/program-manager.ts index 9f73614e0..8a597d36d 100644 --- a/sdk/src/program-manager.ts +++ b/sdk/src/program-manager.ts @@ -471,15 +471,11 @@ class ProgramManager { } if (!keyStore) return; - // Enumerate programs/functions from the builder and persist any extracted keys. - const builderObj = builder.toObject() as Record<string, any>; - for (const name of Object.keys(builderObj)) { - if (!builder.contains(name)) continue; - - // Extract the program source from the builder's output (plain string or structured object). - const entry = builderObj[name]; - const source = typeof entry === "string" ? entry : entry?.program; - if (typeof source !== "string") continue; + // Enumerate programs from the builder without serializing keys. + const programNames: string[] = Array.from(builder.programNames()); + for (const name of programNames) { + const source = builder.getProgram(name); + if (!source) continue; const program = Program.fromString(source); const functions: string[] = Array.from(program.getFunctions()); diff --git a/wasm/src/programs/manager/imports.rs b/wasm/src/programs/manager/imports.rs index 4f1adc436..9c520a360 100644 --- a/wasm/src/programs/manager/imports.rs +++ b/wasm/src/programs/manager/imports.rs @@ -28,11 +28,11 @@ use crate::{ }, }; -use js_sys::{Object, Reflect, Uint8Array}; +use js_sys::{Array, Object, Reflect, Uint8Array}; use snarkvm_synthesizer_program::StackTrait; use snarkvm_wasm::utilities::{FromBytes, ToBytes}; use std::{collections::HashMap, str::FromStr}; -use wasm_bindgen::{JsCast, prelude::wasm_bindgen}; +use wasm_bindgen::{JsCast, JsValue, prelude::wasm_bindgen}; use super::ProgramManager; @@ -352,6 +352,31 @@ impl ProgramImports { obj } + /// Return the names of all programs in this builder as a JS `Array<string>`. + /// + /// This is a lightweight alternative to `toObject()` when you only need to + /// enumerate program names without serializing keys. + /// + /// @returns {Array<string>} + #[wasm_bindgen(js_name = "programNames")] + pub fn program_names(&self) -> Array { + let arr = Array::new_with_length(self.entries.len() as u32); + for (i, program_id) in self.entries.keys().enumerate() { + arr.set(i as u32, JsValue::from_str(&program_id.to_string())); + } + arr + } + + /// Return the source code of a program by name, without serializing keys. + /// + /// @param {string} name The program name (e.g., "my_program.aleo"). + /// @returns {string | undefined} + #[wasm_bindgen(js_name = "getProgram")] + pub fn get_program(&self, name: &str) -> Option<String> { + let program_id = ProgramIDNative::from_str(name).ok()?; + self.entries.get(&program_id).map(|entry| entry.program().to_string()) + } + /// Check whether any programs have been added to this builder. /// /// @returns {boolean} diff --git a/wasm/src/programs/manager/mod.rs b/wasm/src/programs/manager/mod.rs index 47c27de53..5eeecf083 100644 --- a/wasm/src/programs/manager/mod.rs +++ b/wasm/src/programs/manager/mod.rs @@ -474,6 +474,39 @@ constructor: assert!(builder.contains("addition_test.aleo")); } + /// Test that program_names returns the names of all added programs. + #[wasm_bindgen_test] + fn test_program_imports_program_names() { + let mut builder = ProgramImports::new(); + let empty_names = builder.program_names(); + assert_eq!(empty_names.length(), 0); + + builder.add_program("multiply_test.aleo", MULTIPLY_PROGRAM, None).unwrap(); + builder.add_program("addition_test.aleo", ADDITION_PROGRAM, None).unwrap(); + + let names = builder.program_names(); + assert_eq!(names.length(), 2); + + let names_vec: Vec<String> = names.iter().filter_map(|v| v.as_string()).collect(); + assert!(names_vec.contains(&"multiply_test.aleo".to_string())); + assert!(names_vec.contains(&"addition_test.aleo".to_string())); + } + + /// Test that get_program returns source for known programs and None for unknown. + #[wasm_bindgen_test] + fn test_program_imports_get_program() { + let mut builder = ProgramImports::new(); + assert!(builder.get_program("multiply_test.aleo").is_none()); + + builder.add_program("multiply_test.aleo", MULTIPLY_PROGRAM, None).unwrap(); + + let source = builder.get_program("multiply_test.aleo"); + assert!(source.is_some()); + assert!(source.unwrap().contains("multiply_test.aleo")); + + assert!(builder.get_program("nonexistent.aleo").is_none()); + } + /// Test that load_programs loads programs into the process via the builder path. #[wasm_bindgen_test] fn test_program_imports_load_programs() { From 3774d9d6397b7eab4be4181ff0a880f0634f6714 Mon Sep 17 00:00:00 2001 From: Cameron Marshall <cameron.marshall12@gmail.com> Date: Fri, 13 Mar 2026 12:36:20 -0400 Subject: [PATCH 17/17] Add transitive import resolution for dynamic dispatch targets with test coverage --- sdk/src/program-manager.ts | 21 ++++++++++++++++++++- sdk/tests/data/dynamic-dispatch.ts | 16 ++++++++++++++++ sdk/tests/program-imports.test.ts | 22 ++++++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/sdk/src/program-manager.ts b/sdk/src/program-manager.ts index 8a597d36d..96096fae9 100644 --- a/sdk/src/program-manager.ts +++ b/sdk/src/program-manager.ts @@ -396,11 +396,30 @@ class ProgramManager { resolvedImports = { ...resolvedImports, ...imports }; } - // Add each import program and query KeyStore for its keys + // Add each import program, recursively fetch its static imports, and + // query the KeyStore for cached keys. for (const [name, importProgram] of Object.entries(resolvedImports)) { + if (builder.contains(name)) continue; const importProgramStr = typeof importProgram === "string" ? importProgram : importProgram.toString(); builder.addProgram(name, importProgramStr); await this.loadKeysFromStore(builder, name, importProgramStr); + + // Recursively resolve static imports of dynamic targets so the caller + // doesn't need to manually provide transitive dependencies. + const importObj = Program.fromString(importProgramStr); + if (importObj.getImports().length > 0) { + try { + const transitive = await this.networkClient.getProgramImports(importProgramStr); + for (const [tName, tSource] of Object.entries(transitive)) { + if (builder.contains(tName)) continue; + const tStr = typeof tSource === "string" ? tSource : tSource.toString(); + builder.addProgram(tName, tStr); + await this.loadKeysFromStore(builder, tName, tStr); + } + } catch { + // Non-blocking — transitive import resolution is best-effort + } + } } return builder; diff --git a/sdk/tests/data/dynamic-dispatch.ts b/sdk/tests/data/dynamic-dispatch.ts index 10782eb11..3453bbe42 100644 --- a/sdk/tests/data/dynamic-dispatch.ts +++ b/sdk/tests/data/dynamic-dispatch.ts @@ -75,6 +75,22 @@ function double_it: output r1 as u32.private; `; +/// A program that dynamically calls double_test.aleo, which itself statically +/// imports multiply_test.aleo. Used to test transitive import resolution. +export const DD_TRANSITIVE_CALLER = `program dd_transitive_caller.aleo; + +function call_double: + input r0 as field.public; + input r1 as field.public; + input r2 as field.public; + input r3 as u32.private; + call.dynamic r0 r1 r2 with r3 (as u32.private) into r4 (as u32.private); + output r4 as u32.private; + +constructor: + assert.eq true true; +`; + /// Pre-computed field-encoded identifiers for dynamic dispatch. /// These are Identifier::to_field() values computed from the Rust/WASM tests. export const DD_CONSTANTS_FIELD = "35731532782568442653824738404field"; diff --git a/sdk/tests/program-imports.test.ts b/sdk/tests/program-imports.test.ts index 9c427173c..d96e887a6 100644 --- a/sdk/tests/program-imports.test.ts +++ b/sdk/tests/program-imports.test.ts @@ -12,6 +12,7 @@ import { DD_CALLER_PROGRAM, DD_CONSTANTS_PROGRAM, DD_TEN_PROGRAM, + DD_TRANSITIVE_CALLER, MULTIPLY_PROGRAM, DOUBLE_PROGRAM, } from "./data/dynamic-dispatch.js"; @@ -145,6 +146,27 @@ describe("ProgramImports & KeyStore integration", () => { expect(builder.contains("dd_ten.aleo")).to.equal(true); }); + it("should recursively resolve static imports of dynamic targets", async () => { + const manager = new ProgramManager(); + // DOUBLE_PROGRAM has `import multiply_test.aleo;` — stub the network fetch + const fetchStub = sinon.stub(manager.networkClient, "getProgramImports").resolves({ + "multiply_test.aleo": MULTIPLY_PROGRAM, + }); + + // User provides DOUBLE_PROGRAM as a dynamic target for DD_TRANSITIVE_CALLER + const builder: ProgramImportsBuilder = await pm(manager).buildProgramImports( + DD_TRANSITIVE_CALLER, + { "double_test.aleo": DOUBLE_PROGRAM }, + ); + + // Verify the transitive fetch was triggered for DOUBLE_PROGRAM's static imports + expect(fetchStub.calledOnce).to.equal(true); + expect(fetchStub.firstCall.args[0]).to.include("double_test.aleo"); + // Both the user-provided import and its transitive static dependency should be present + expect(builder.contains("double_test.aleo")).to.equal(true); + expect(builder.contains("multiply_test.aleo")).to.equal(true); + }); + it("should add multiple imports to the builder", async () => { const manager = new ProgramManager(); const imports = {