diff --git a/Cargo.lock b/Cargo.lock index 986aa395..fce86040 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -280,6 +280,9 @@ name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +dependencies = [ + "serde", +] [[package]] name = "elements" @@ -450,7 +453,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.114", ] [[package]] @@ -472,18 +475,18 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -621,7 +624,7 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.114", ] [[package]] @@ -723,9 +726,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.31" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -749,7 +752,7 @@ checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.114", ] [[package]] @@ -809,7 +812,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.114", "wasm-bindgen-shared", ] @@ -831,7 +834,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.114", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/Cargo.toml b/Cargo.toml index 20372f73..af72dfda 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ serde = { version = "1.0.188", features = ["derive"], optional = true } serde_json = { version = "1.0.105", optional = true } simplicity-lang = { version = "0.7.0" } miniscript = "12.3.1" -either = "1.12.0" +either = { version = "1.12.0", features = ["serde"] } itertools = "0.13.0" arbitrary = { version = "1", optional = true, features = ["derive"] } clap = "4.5.37" @@ -39,7 +39,7 @@ getrandom = { version = "0.2", features = ["js"] } [workspace] members = ["codegen", "fuzz"] -exclude = ["bitcoind-tests", "lsp"] +exclude = ["bitcoind-tests", "lsp", "macros"] [lints.clippy] # Exclude lints we don't think are valuable. diff --git a/macros/Cargo.lock b/macros/Cargo.lock new file mode 100644 index 00000000..d1116895 --- /dev/null +++ b/macros/Cargo.lock @@ -0,0 +1,1075 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "base58ck" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" +dependencies = [ + "bitcoin-internals", + "bitcoin_hashes", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "bech32" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" + +[[package]] +name = "bincode" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" +dependencies = [ + "bincode_derive", + "serde", + "unty", +] + +[[package]] +name = "bincode_derive" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09" +dependencies = [ + "virtue", +] + +[[package]] +name = "bitcoin" +version = "0.32.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e499f9fc0407f50fe98af744ab44fa67d409f76b6772e1689ec8485eb0c0f66" +dependencies = [ + "base58ck", + "bech32", + "bitcoin-internals", + "bitcoin-io", + "bitcoin-units", + "bitcoin_hashes", + "hex-conservative", + "hex_lit", + "secp256k1", +] + +[[package]] +name = "bitcoin-internals" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2" + +[[package]] +name = "bitcoin-io" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" + +[[package]] +name = "bitcoin-private" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73290177011694f38ec25e165d0387ab7ea749a4b81cd4c80dae5988229f7a57" + +[[package]] +name = "bitcoin-units" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2" +dependencies = [ + "bitcoin-internals", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" +dependencies = [ + "bitcoin-io", + "hex-conservative", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clap" +version = "4.5.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_lex" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", +] + +[[package]] +name = "elements" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81b2569d3495bfdfce36c504fd4d78752ff4a7699f8a33e6f3ee523bddf9f6ad" +dependencies = [ + "bech32", + "bitcoin", + "secp256k1-zkp", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "ghost-cell" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8449d342b1c67f49169e92e71deb7b9b27f30062301a16dbc27a4cc8d2351b7" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-conservative" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hex_lit" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "miniscript" +version = "12.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487906208f38448e186e3deb02f2b8ef046a9078b0de00bdb28bf4fb9b76951c" +dependencies = [ + "bech32", + "bitcoin", +] + +[[package]] +name = "minreq" +version = "2.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05015102dad0f7d61691ca347e9d9d9006685a64aefb3d79eecf62665de2153d" +dependencies = [ + "rustls", + "rustls-webpki", + "serde", + "serde_json", + "webpki-roots", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "pest" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f9dbced329c441fa79d80472764b1a2c7e57123553b8519b36663a2fb234ed" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bb96d5051a78f44f43c8f712d8e810adb0ebf923fc9ed2655a7f66f63ba8ee5" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "pest_meta" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "602113b5b5e8621770cfd490cfd90b9f84ab29bd2b0e49ad83eb6d186cef2365" +dependencies = [ + "pest", + "sha2", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "santiago" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de36022292bc2086eb8f55bffa460fef3475e4459b478820711f4c421feb87ec" +dependencies = [ + "regex", +] + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "secp256k1" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" +dependencies = [ + "bitcoin_hashes", + "rand", + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + +[[package]] +name = "secp256k1-zkp" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a44aed3002b5ae975f8624c5df3a949cfbf00479e18778b6058fcd213b76e3" +dependencies = [ + "bitcoin-private", + "rand", + "secp256k1", + "secp256k1-zkp-sys", +] + +[[package]] +name = "secp256k1-zkp-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57f08b2d0b143a22e07f798ae4f0ab20d5590d7c68e0d090f2088a48a21d1654" +dependencies = [ + "cc", + "secp256k1-sys", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simplicity-lang" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e57bd4d84853974a212eab24ed89da54f49fbccf5e33e93bcd29f0a6591cd5" +dependencies = [ + "bitcoin", + "bitcoin_hashes", + "byteorder", + "elements", + "getrandom", + "ghost-cell", + "hex-conservative", + "miniscript", + "santiago", + "simplicity-sys", +] + +[[package]] +name = "simplicity-sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bcb4e5bfc15080d67e0ce2c17d1c31bfb7521d65c86ea26ed0de72d5119d119" +dependencies = [ + "bitcoin_hashes", + "cc", +] + +[[package]] +name = "simplicityhl" +version = "0.4.1" +dependencies = [ + "base64", + "clap", + "either", + "getrandom", + "itertools", + "miniscript", + "pest", + "pest_derive", + "serde", + "serde_json", + "simplicity-lang", +] + +[[package]] +name = "simplicityhl-core" +version = "0.4.2" +dependencies = [ + "hex", + "minreq", + "sha2", + "simplicityhl", + "thiserror", +] + +[[package]] +name = "simplicityhl-macros" +version = "0.1.0" +dependencies = [ + "bincode", + "proc-macro-error", + "proc-macro2", + "quote", + "serde", + "simplicityhl", + "simplicityhl-core", + "syn 2.0.114", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "unty" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "virtue" +version = "0.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.114", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zerocopy" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71ddd76bcebeed25db614f82bf31a9f4222d3fbba300e6fb6c00afa26cbd4d9d" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8187381b52e32220d50b255276aa16a084ec0a9017a0ca2152a1f55c539758d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "zmij" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439" diff --git a/macros/Cargo.toml b/macros/Cargo.toml new file mode 100644 index 00000000..261ac3b3 --- /dev/null +++ b/macros/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "simplicityhl-macros" +version = "0.1.0" +edition = "2024" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = { version = "1.0", features = ["span-locations"] } +proc-macro-error = { version = "1.0" } +syn = { version = "2.0.114", features = ["full"] } +quote = { version = "1" } +bincode = { version = "2.0.1" } +serde = { version = "1.0.228" } + + +simplicityhl = { path = "../" } +simplicityhl-core = { path = "../../simplicity-contracts-copy/crates/simplicityhl-core" } \ No newline at end of file diff --git a/macros/examples/main_example.rs b/macros/examples/main_example.rs new file mode 100644 index 00000000..ce37f6aa --- /dev/null +++ b/macros/examples/main_example.rs @@ -0,0 +1,38 @@ +use bincode::de::Decoder; +use bincode::enc::Encoder; +use bincode::error::{DecodeError, EncodeError}; +use bincode::*; +use simplicityhl_macros::include_simf; +use std::ops::Deref; + +include_simf!("examples/source_simf/options.simf"); + +fn main() { + // println!("{}", options2::CONTRACT_SOURCE); + // let x = options2::get_options_template_program(); +} + +// impl bincode::Decode for OptionsArguments { +// fn decode>(decoder: &mut D) -> Result { +// todo!() +// } +// } +// +// impl bincode::Encode for OptionsArguments { +// fn encode(&self, encoder: &mut E) -> Result<(), EncodeError> { +// encoder. +// } +// } +// +// impl bincode::Decode for OptionsWitness { +// fn decode>(decoder: &mut D) -> Result { +// todo!() +// } +// } +// +// impl bincode::Encode for OptionsWitness { +// fn encode(&self, encoder: &mut E) -> Result<(), EncodeError> { +// +// todo!() +// } +// } diff --git a/macros/examples/source_simf/options.simf b/macros/examples/source_simf/options.simf new file mode 100644 index 00000000..e7da014b --- /dev/null +++ b/macros/examples/source_simf/options.simf @@ -0,0 +1,395 @@ +/* + * Options + * + * Important: Currently only the LBTC collateral is supported. + * + * Based on the https://blockstream.com/assets/downloads/pdf/options-whitepaper.pdf + * + * This contract implements cash-settled European-style options using covenant-locked collateral. + * + * Room for optimization: + * - https://github.com/BlockstreamResearch/simplicity-contracts/issues/2 (Use input asset to determine option covenent type) + * - https://github.com/BlockstreamResearch/simplicity-contracts/issues/3 (Simplify match token_branch in funding_path.) + * - https://github.com/BlockstreamResearch/simplicity-contracts/issues/4 (why batching is hard to implement) + * - https://github.com/BlockstreamResearch/simplicity-contracts/issues/5 (Reduce Contract Parameters) + * - https://github.com/BlockstreamResearch/simplicity-contracts/issues/21 (explains why funding is limited) + */ + +/// Assert: a == b * expected_q, via divmod +fn divmod_eq(a: u64, b: u64, expected_q: u64) { + let (q, r): (u64, u64) = jet::div_mod_64(a, b); + assert!(jet::eq_64(q, expected_q)); + assert!(jet::eq_64(r, 0)); +} + +fn get_output_script_hash(index: u32) -> u256 { + unwrap(jet::output_script_hash(index)) +} + +fn get_input_script_hash(index: u32) -> u256 { + unwrap(jet::input_script_hash(index)) +} + +fn get_output_explicit_asset_amount(index: u32) -> (u256, u64) { + let pair: (Asset1, Amount1) = unwrap(jet::output_amount(index)); + let (asset, amount): (Asset1, Amount1) = pair; + let asset_bits: u256 = unwrap_right::<(u1, u256)>(asset); + let amount: u64 = unwrap_right::<(u1, u256)>(amount); + (asset_bits, amount) +} + +fn get_input_explicit_asset_amount(index: u32) -> (u256, u64) { + let pair: (Asset1, Amount1) = unwrap(jet::input_amount(index)); + let (asset, amount): (Asset1, Amount1) = pair; + let asset_bits: u256 = unwrap_right::<(u1, u256)>(asset); + let amount: u64 = unwrap_right::<(u1, u256)>(amount); + (asset_bits, amount) +} + +fn ensure_one_bit(bit: bool) { assert!(jet::eq_1(::into(bit), 1)); } +fn ensure_zero_bit(bit: bool) { assert!(jet::eq_1(::into(bit), 0)); } + +fn increment_by(index: u32, amount: u32) -> u32 { + let (carry, result): (bool, u32) = jet::add_32(index, amount); + ensure_zero_bit(carry); + result +} + +fn ensure_input_and_output_script_hash_eq(index: u32) { + assert!(jet::eq_256(unwrap(jet::input_script_hash(index)), unwrap(jet::output_script_hash(index)))); +} + +fn ensure_output_is_op_return(index: u32) { + match jet::output_null_datum(index, 0) { + Some(entry: Option>>) => (), + None => panic!(), + } +} + +fn ensure_input_asset_eq(index: u32, expected_bits: u256) { + let asset: Asset1 = unwrap(jet::input_asset(index)); + let asset_bits: u256 = unwrap_right::<(u1, u256)>(asset); + assert!(jet::eq_256(asset_bits, expected_bits)); +} + +fn ensure_output_asset_eq(index: u32, expected_bits: u256) { + let asset: Asset1 = unwrap(jet::output_asset(index)); + let asset_bits: u256 = unwrap_right::<(u1, u256)>(asset); + assert!(jet::eq_256(asset_bits, expected_bits)); +} + +fn ensure_output_asset_with_amount_eq(index: u32, expected_bits: u256, expected_amount: u64) { + let (asset, amount): (u256, u64) = dbg!(get_output_explicit_asset_amount(index)); + assert!(jet::eq_256(asset, expected_bits)); + assert!(jet::eq_64(amount, expected_amount)); +} + +fn ensure_input_script_hash_eq(index: u32, expected: u256) { + assert!(jet::eq_256(unwrap(jet::input_script_hash(index)), expected)); +} + +fn ensure_output_script_hash_eq(index: u32, expected: u256) { + assert!(jet::eq_256(unwrap(jet::output_script_hash(index)), expected)); +} + +fn ensure_correct_change_at_index(index: u32, asset_id: u256, asset_amount_to_spend: u64, contract_script_hash: u256, is_change_needed: bool) { + let (asset_bits, available_asset_amount): (u256, u64) = get_input_explicit_asset_amount(index); + assert!(jet::eq_256(unwrap(jet::input_script_hash(index)), contract_script_hash)); + assert!(jet::eq_32(jet::current_index(), index)); + + match is_change_needed { + true => { + ensure_input_and_output_script_hash_eq(index); + + let (carry, collateral_change): (bool, u64) = jet::subtract_64(available_asset_amount, asset_amount_to_spend); + ensure_zero_bit(carry); + ensure_output_asset_with_amount_eq(index, asset_id, collateral_change); + }, + false => assert!(jet::eq_64(asset_amount_to_spend, available_asset_amount)), + } +} + +fn check_y(expected_y: Fe, actual_y: Fe) { + match jet::eq_256(expected_y, actual_y) { + true => {}, + false => { + assert!(jet::eq_256(expected_y, jet::fe_negate(actual_y))); + } + }; +} + +fn ensure_input_and_output_reissuance_token_eq(index: u32) { + let (input_asset, input_amount): (Asset1, Amount1) = unwrap(jet::input_amount(index)); + let (output_asset, output_amount): (Asset1, Amount1) = unwrap(jet::output_amount(index)); + + match (input_asset) { + Left(in_conf: Point) => { + let (input_asset_parity, input_asset_x): (u1, u256) = in_conf; + let (output_asset_parity, output_asset_x): (u1, u256) = unwrap_left::(output_asset); + + assert!(jet::eq_1(input_asset_parity, output_asset_parity)); + assert!(jet::eq_256(input_asset_x, output_asset_x)); + }, + Right(in_expl: u256) => { + let out_expl: u256 = unwrap_right::(output_asset); + assert!(jet::eq_256(in_expl, out_expl)); + } + }; + + match (input_amount) { + Left(in_conf: Point) => { + let (input_amount_parity, input_amount_x): (u1, u256) = in_conf; + let (output_amount_parity, output_amount_x): (u1, u256) = unwrap_left::(output_amount); + + assert!(jet::eq_1(input_amount_parity, output_amount_parity)); + assert!(jet::eq_256(input_amount_x, output_amount_x)); + }, + Right(in_expl: u64) => { + let out_expl: u64 = unwrap_right::(output_amount); + assert!(jet::eq_64(in_expl, out_expl)); + } + }; +} + +// Verify that a reissuance token commitment matches the expected token ID using provided blinding factors. +// Reissuance tokens are confidential because, in Elements, +// the asset must be provided in blinded form in order to reissue tokens. +// https://github.com/BlockstreamResearch/simplicity-contracts/issues/21#issuecomment-3691599583 +fn verify_token_commitment(actual_asset: Asset1, actual_amount: Amount1, expected_token_id: u256, abf: u256, vbf: u256) { + match actual_asset { + Left(conf_token: Point) => { + let amount_scalar: u256 = 1; + let (actual_ax, actual_ay): Ge = unwrap(jet::decompress(conf_token)); + + let gej_point: Gej = (jet::hash_to_curve(expected_token_id), 1); + let asset_blind_point: Gej = jet::generate(abf); + + let asset_generator: Gej = jet::gej_add(gej_point, asset_blind_point); + let (ax, ay): Ge = unwrap(jet::gej_normalize(asset_generator)); + + assert!(jet::eq_256(actual_ax, ax)); + check_y(actual_ay, ay); + + // Check amount + let conf_val: Point = unwrap_left::(actual_amount); + let (actual_vx, actual_vy): Ge = unwrap(jet::decompress(conf_val)); + + let amount_part: Gej = jet::scale(amount_scalar, asset_generator); + let vbf_part: Gej = jet::generate(vbf); + + let value_generator: Gej = jet::gej_add(amount_part, vbf_part); + let (vx, vy): Ge = unwrap(jet::gej_normalize(value_generator)); + + assert!(jet::eq_256(actual_vx, vx)); + check_y(actual_vy, vy); + }, + Right(reissuance_token: u256) => { + let expected_amount: u64 = 1; + let actual_amount: u64 = unwrap_right::(actual_amount); + + assert!(jet::eq_64(expected_amount, actual_amount)); + assert!(jet::eq_256(reissuance_token, expected_token_id)); + } + }; +} + +fn verify_output_reissuance_token(index: u32, expected_token_id: u256, abf: u256, vbf: u256) { + let (asset, amount): (Asset1, Amount1) = unwrap(jet::output_amount(index)); + verify_token_commitment(asset, amount, expected_token_id, abf, vbf); +} + +fn verify_input_reissuance_token(index: u32, expected_token_id: u256, abf: u256, vbf: u256) { + let (asset, amount): (Asset1, Amount1) = unwrap(jet::input_amount(index)); + verify_token_commitment(asset, amount, expected_token_id, abf, vbf); +} + +/* + * Funding Path + */ +fn funding_path( + expected_asset_amount: u64, + input_option_abf: u256, + input_option_vbf: u256, + input_grantor_abf: u256, + input_grantor_vbf: u256, + output_option_abf: u256, + output_option_vbf: u256, + output_grantor_abf: u256, + output_grantor_vbf: u256 +) { + ensure_input_and_output_script_hash_eq(0); + ensure_input_and_output_script_hash_eq(1); + + verify_input_reissuance_token(0, param::OPTION_REISSUANCE_TOKEN_ASSET, input_option_abf, input_option_vbf); + verify_input_reissuance_token(1, param::GRANTOR_REISSUANCE_TOKEN_ASSET, input_grantor_abf, input_grantor_vbf); + + verify_output_reissuance_token(0, param::OPTION_REISSUANCE_TOKEN_ASSET, output_option_abf, output_option_vbf); + verify_output_reissuance_token(1, param::GRANTOR_REISSUANCE_TOKEN_ASSET, output_grantor_abf, output_grantor_vbf); + + assert!(dbg!(jet::eq_256(get_output_script_hash(0), get_output_script_hash(1)))); + + assert!(jet::le_32(jet::current_index(), 1)); + + ensure_output_script_hash_eq(2, get_output_script_hash(0)); + + let (collateral_asset_bits, collateral_amount): (u256, u64) = get_output_explicit_asset_amount(2); + let option_token_amount: u64 = unwrap_right::<(u1, u256)>(unwrap(unwrap(jet::issuance_asset_amount(0)))); + let grantor_token_amount: u64 = unwrap_right::<(u1, u256)>(unwrap(unwrap(jet::issuance_asset_amount(1)))); + assert!(jet::eq_64(option_token_amount, grantor_token_amount)); + + divmod_eq(collateral_amount, param::COLLATERAL_PER_CONTRACT, option_token_amount); + divmod_eq(expected_asset_amount, param::SETTLEMENT_PER_CONTRACT, option_token_amount); + + ensure_output_asset_with_amount_eq(2, param::COLLATERAL_ASSET_ID, collateral_amount); + ensure_output_asset_with_amount_eq(3, param::OPTION_TOKEN_ASSET, option_token_amount); + ensure_output_asset_with_amount_eq(4, param::GRANTOR_TOKEN_ASSET, grantor_token_amount); +} + +/* + * Cancellation Path + */ +fn cancellation_path(amount_to_burn: u64, collateral_amount_to_withdraw: u64, is_change_needed: bool) { + let collateral_input_index: u32 = 0; + let option_input_index: u32 = 1; + let grantor_input_index: u32 = 2; + + let (burn_option_output_index, burn_grantor_output_index): (u32, u32) = match is_change_needed { + true => (1, 2), + false => (0, 1), + }; + + let expected_current_script_hash: u256 = get_input_script_hash(collateral_input_index); + + // Check and ensure collateral change + ensure_correct_change_at_index(0, param::COLLATERAL_ASSET_ID, collateral_amount_to_withdraw, expected_current_script_hash, is_change_needed); + + // Burn option and grantor tokens + ensure_output_is_op_return(burn_option_output_index); + ensure_output_is_op_return(burn_grantor_output_index); + + ensure_output_asset_with_amount_eq(burn_option_output_index, param::OPTION_TOKEN_ASSET, amount_to_burn); + ensure_output_asset_with_amount_eq(burn_grantor_output_index, param::GRANTOR_TOKEN_ASSET, amount_to_burn); + + // Ensure returned collateral amount is correct + divmod_eq(collateral_amount_to_withdraw, param::COLLATERAL_PER_CONTRACT, amount_to_burn); +} + +/* + * Exercise Path + */ +fn exercise_path(option_amount_to_burn: u64, collateral_amount_to_get: u64, asset_amount_to_pay: u64, is_change_needed: bool) { + jet::check_lock_time(param::START_TIME); + + let collateral_input_index: u32 = 0; + + let (burn_option_output_index, asset_to_covenant_output_index): (u32, u32) = match is_change_needed { + true => (1, 2), + false => (0, 1), + }; + + let expected_current_script_hash: u256 = get_input_script_hash(collateral_input_index); + + // Check and ensure collateral change + ensure_correct_change_at_index(0, param::COLLATERAL_ASSET_ID, collateral_amount_to_get, expected_current_script_hash, is_change_needed); + + // Ensure collateral and asset amounts are correct + divmod_eq(collateral_amount_to_get, param::COLLATERAL_PER_CONTRACT, option_amount_to_burn); + divmod_eq(asset_amount_to_pay, param::SETTLEMENT_PER_CONTRACT, option_amount_to_burn); + + // Burn option token + ensure_output_is_op_return(burn_option_output_index); + ensure_output_asset_with_amount_eq(burn_option_output_index, param::OPTION_TOKEN_ASSET, option_amount_to_burn); + + // Ensure settlement asset and script hash are correct + ensure_output_asset_with_amount_eq(asset_to_covenant_output_index, param::SETTLEMENT_ASSET_ID, asset_amount_to_pay); + ensure_output_script_hash_eq(asset_to_covenant_output_index, expected_current_script_hash); +} + +/* + * Settlement Path + */ +fn settlement_path(grantor_token_amount_to_burn: u64, asset_amount: u64, is_change_needed: bool) { + jet::check_lock_time(param::START_TIME); + + let target_asset_input_index: u32 = 0; + + let burn_grantor_output_index: u32 = match is_change_needed { + true => 1, + false => 0, + }; + + let expected_current_script_hash: u256 = get_input_script_hash(target_asset_input_index); + + // Check and ensure settlement asset change + ensure_correct_change_at_index(0, param::SETTLEMENT_ASSET_ID, asset_amount, expected_current_script_hash, is_change_needed); + + // Ensure settlement asset and grantor token amounts are correct + divmod_eq(asset_amount, param::SETTLEMENT_PER_CONTRACT, grantor_token_amount_to_burn); + + // Burn grantor token + ensure_output_is_op_return(burn_grantor_output_index); + ensure_output_asset_with_amount_eq(burn_grantor_output_index, param::GRANTOR_TOKEN_ASSET, grantor_token_amount_to_burn); +} + +/* + * Expiry Path + */ +fn expiry_path(grantor_token_amount_to_burn: u64, collateral_amount: u64, is_change_needed: bool) { + jet::check_lock_time(param::EXPIRY_TIME); + + let collateral_input_index: u32 = 0; + + let burn_grantor_output_index: u32 = match is_change_needed { + true => 1, + false => 0, + }; + + let expected_current_script_hash: u256 = get_input_script_hash(collateral_input_index); + + // Check and ensure collateral change + ensure_correct_change_at_index(0, param::COLLATERAL_ASSET_ID, collateral_amount, expected_current_script_hash, is_change_needed); + + // Ensure collateral amount is correct + divmod_eq(collateral_amount, param::COLLATERAL_PER_CONTRACT, grantor_token_amount_to_burn); + + // Burn grantor token + ensure_output_is_op_return(burn_grantor_output_index); + ensure_output_asset_with_amount_eq(burn_grantor_output_index, param::GRANTOR_TOKEN_ASSET, grantor_token_amount_to_burn); +} + +fn main() { + match witness::PATH { + Left(left_or_right: Either<(u64, u256, u256, u256, u256, u256, u256, u256, u256), Either<(bool, u64, u64, u64), (bool, u64, u64)>>) => match left_or_right { + Left(params: (u64, u256, u256, u256, u256, u256, u256, u256, u256)) => { + let (expected_asset_amount, input_option_abf, input_option_vbf, input_grantor_abf, input_grantor_vbf, output_option_abf, output_option_vbf, output_grantor_abf, output_grantor_vbf): (u64, u256, u256, u256, u256, u256, u256, u256, u256) = params; + funding_path( + expected_asset_amount, + input_option_abf, input_option_vbf, + input_grantor_abf, input_grantor_vbf, + output_option_abf, output_option_vbf, + output_grantor_abf, output_grantor_vbf + ); + }, + Right(exercise_or_settlement: Either<(bool, u64, u64, u64), (bool, u64, u64)>) => match exercise_or_settlement { + Left(params: (bool, u64, u64, u64)) => { + let (is_change_needed, amount_to_burn, collateral_amount, asset_amount): (bool, u64, u64, u64) = dbg!(params); + exercise_path(amount_to_burn, collateral_amount, asset_amount, is_change_needed) + }, + Right(params: (bool, u64, u64)) => { + let (is_change_needed, amount_to_burn, asset_amount): (bool, u64, u64) = dbg!(params); + settlement_path(amount_to_burn, asset_amount, is_change_needed) + }, + }, + }, + Right(left_or_right: Either<(bool, u64, u64), (bool, u64, u64)>) => match left_or_right { + Left(params: (bool, u64, u64)) => { + let (is_change_needed, grantor_token_amount_to_burn, collateral_amount): (bool, u64, u64) = params; + expiry_path(grantor_token_amount_to_burn, collateral_amount, is_change_needed) + }, + Right(params: (bool, u64, u64)) => { + let (is_change_needed, amount_to_burn, collateral_amount): (bool, u64, u64) = params; + cancellation_path(amount_to_burn, collateral_amount, is_change_needed) + }, + }, + } +} diff --git a/macros/examples/source_simf/options2.simf b/macros/examples/source_simf/options2.simf new file mode 100644 index 00000000..e7da014b --- /dev/null +++ b/macros/examples/source_simf/options2.simf @@ -0,0 +1,395 @@ +/* + * Options + * + * Important: Currently only the LBTC collateral is supported. + * + * Based on the https://blockstream.com/assets/downloads/pdf/options-whitepaper.pdf + * + * This contract implements cash-settled European-style options using covenant-locked collateral. + * + * Room for optimization: + * - https://github.com/BlockstreamResearch/simplicity-contracts/issues/2 (Use input asset to determine option covenent type) + * - https://github.com/BlockstreamResearch/simplicity-contracts/issues/3 (Simplify match token_branch in funding_path.) + * - https://github.com/BlockstreamResearch/simplicity-contracts/issues/4 (why batching is hard to implement) + * - https://github.com/BlockstreamResearch/simplicity-contracts/issues/5 (Reduce Contract Parameters) + * - https://github.com/BlockstreamResearch/simplicity-contracts/issues/21 (explains why funding is limited) + */ + +/// Assert: a == b * expected_q, via divmod +fn divmod_eq(a: u64, b: u64, expected_q: u64) { + let (q, r): (u64, u64) = jet::div_mod_64(a, b); + assert!(jet::eq_64(q, expected_q)); + assert!(jet::eq_64(r, 0)); +} + +fn get_output_script_hash(index: u32) -> u256 { + unwrap(jet::output_script_hash(index)) +} + +fn get_input_script_hash(index: u32) -> u256 { + unwrap(jet::input_script_hash(index)) +} + +fn get_output_explicit_asset_amount(index: u32) -> (u256, u64) { + let pair: (Asset1, Amount1) = unwrap(jet::output_amount(index)); + let (asset, amount): (Asset1, Amount1) = pair; + let asset_bits: u256 = unwrap_right::<(u1, u256)>(asset); + let amount: u64 = unwrap_right::<(u1, u256)>(amount); + (asset_bits, amount) +} + +fn get_input_explicit_asset_amount(index: u32) -> (u256, u64) { + let pair: (Asset1, Amount1) = unwrap(jet::input_amount(index)); + let (asset, amount): (Asset1, Amount1) = pair; + let asset_bits: u256 = unwrap_right::<(u1, u256)>(asset); + let amount: u64 = unwrap_right::<(u1, u256)>(amount); + (asset_bits, amount) +} + +fn ensure_one_bit(bit: bool) { assert!(jet::eq_1(::into(bit), 1)); } +fn ensure_zero_bit(bit: bool) { assert!(jet::eq_1(::into(bit), 0)); } + +fn increment_by(index: u32, amount: u32) -> u32 { + let (carry, result): (bool, u32) = jet::add_32(index, amount); + ensure_zero_bit(carry); + result +} + +fn ensure_input_and_output_script_hash_eq(index: u32) { + assert!(jet::eq_256(unwrap(jet::input_script_hash(index)), unwrap(jet::output_script_hash(index)))); +} + +fn ensure_output_is_op_return(index: u32) { + match jet::output_null_datum(index, 0) { + Some(entry: Option>>) => (), + None => panic!(), + } +} + +fn ensure_input_asset_eq(index: u32, expected_bits: u256) { + let asset: Asset1 = unwrap(jet::input_asset(index)); + let asset_bits: u256 = unwrap_right::<(u1, u256)>(asset); + assert!(jet::eq_256(asset_bits, expected_bits)); +} + +fn ensure_output_asset_eq(index: u32, expected_bits: u256) { + let asset: Asset1 = unwrap(jet::output_asset(index)); + let asset_bits: u256 = unwrap_right::<(u1, u256)>(asset); + assert!(jet::eq_256(asset_bits, expected_bits)); +} + +fn ensure_output_asset_with_amount_eq(index: u32, expected_bits: u256, expected_amount: u64) { + let (asset, amount): (u256, u64) = dbg!(get_output_explicit_asset_amount(index)); + assert!(jet::eq_256(asset, expected_bits)); + assert!(jet::eq_64(amount, expected_amount)); +} + +fn ensure_input_script_hash_eq(index: u32, expected: u256) { + assert!(jet::eq_256(unwrap(jet::input_script_hash(index)), expected)); +} + +fn ensure_output_script_hash_eq(index: u32, expected: u256) { + assert!(jet::eq_256(unwrap(jet::output_script_hash(index)), expected)); +} + +fn ensure_correct_change_at_index(index: u32, asset_id: u256, asset_amount_to_spend: u64, contract_script_hash: u256, is_change_needed: bool) { + let (asset_bits, available_asset_amount): (u256, u64) = get_input_explicit_asset_amount(index); + assert!(jet::eq_256(unwrap(jet::input_script_hash(index)), contract_script_hash)); + assert!(jet::eq_32(jet::current_index(), index)); + + match is_change_needed { + true => { + ensure_input_and_output_script_hash_eq(index); + + let (carry, collateral_change): (bool, u64) = jet::subtract_64(available_asset_amount, asset_amount_to_spend); + ensure_zero_bit(carry); + ensure_output_asset_with_amount_eq(index, asset_id, collateral_change); + }, + false => assert!(jet::eq_64(asset_amount_to_spend, available_asset_amount)), + } +} + +fn check_y(expected_y: Fe, actual_y: Fe) { + match jet::eq_256(expected_y, actual_y) { + true => {}, + false => { + assert!(jet::eq_256(expected_y, jet::fe_negate(actual_y))); + } + }; +} + +fn ensure_input_and_output_reissuance_token_eq(index: u32) { + let (input_asset, input_amount): (Asset1, Amount1) = unwrap(jet::input_amount(index)); + let (output_asset, output_amount): (Asset1, Amount1) = unwrap(jet::output_amount(index)); + + match (input_asset) { + Left(in_conf: Point) => { + let (input_asset_parity, input_asset_x): (u1, u256) = in_conf; + let (output_asset_parity, output_asset_x): (u1, u256) = unwrap_left::(output_asset); + + assert!(jet::eq_1(input_asset_parity, output_asset_parity)); + assert!(jet::eq_256(input_asset_x, output_asset_x)); + }, + Right(in_expl: u256) => { + let out_expl: u256 = unwrap_right::(output_asset); + assert!(jet::eq_256(in_expl, out_expl)); + } + }; + + match (input_amount) { + Left(in_conf: Point) => { + let (input_amount_parity, input_amount_x): (u1, u256) = in_conf; + let (output_amount_parity, output_amount_x): (u1, u256) = unwrap_left::(output_amount); + + assert!(jet::eq_1(input_amount_parity, output_amount_parity)); + assert!(jet::eq_256(input_amount_x, output_amount_x)); + }, + Right(in_expl: u64) => { + let out_expl: u64 = unwrap_right::(output_amount); + assert!(jet::eq_64(in_expl, out_expl)); + } + }; +} + +// Verify that a reissuance token commitment matches the expected token ID using provided blinding factors. +// Reissuance tokens are confidential because, in Elements, +// the asset must be provided in blinded form in order to reissue tokens. +// https://github.com/BlockstreamResearch/simplicity-contracts/issues/21#issuecomment-3691599583 +fn verify_token_commitment(actual_asset: Asset1, actual_amount: Amount1, expected_token_id: u256, abf: u256, vbf: u256) { + match actual_asset { + Left(conf_token: Point) => { + let amount_scalar: u256 = 1; + let (actual_ax, actual_ay): Ge = unwrap(jet::decompress(conf_token)); + + let gej_point: Gej = (jet::hash_to_curve(expected_token_id), 1); + let asset_blind_point: Gej = jet::generate(abf); + + let asset_generator: Gej = jet::gej_add(gej_point, asset_blind_point); + let (ax, ay): Ge = unwrap(jet::gej_normalize(asset_generator)); + + assert!(jet::eq_256(actual_ax, ax)); + check_y(actual_ay, ay); + + // Check amount + let conf_val: Point = unwrap_left::(actual_amount); + let (actual_vx, actual_vy): Ge = unwrap(jet::decompress(conf_val)); + + let amount_part: Gej = jet::scale(amount_scalar, asset_generator); + let vbf_part: Gej = jet::generate(vbf); + + let value_generator: Gej = jet::gej_add(amount_part, vbf_part); + let (vx, vy): Ge = unwrap(jet::gej_normalize(value_generator)); + + assert!(jet::eq_256(actual_vx, vx)); + check_y(actual_vy, vy); + }, + Right(reissuance_token: u256) => { + let expected_amount: u64 = 1; + let actual_amount: u64 = unwrap_right::(actual_amount); + + assert!(jet::eq_64(expected_amount, actual_amount)); + assert!(jet::eq_256(reissuance_token, expected_token_id)); + } + }; +} + +fn verify_output_reissuance_token(index: u32, expected_token_id: u256, abf: u256, vbf: u256) { + let (asset, amount): (Asset1, Amount1) = unwrap(jet::output_amount(index)); + verify_token_commitment(asset, amount, expected_token_id, abf, vbf); +} + +fn verify_input_reissuance_token(index: u32, expected_token_id: u256, abf: u256, vbf: u256) { + let (asset, amount): (Asset1, Amount1) = unwrap(jet::input_amount(index)); + verify_token_commitment(asset, amount, expected_token_id, abf, vbf); +} + +/* + * Funding Path + */ +fn funding_path( + expected_asset_amount: u64, + input_option_abf: u256, + input_option_vbf: u256, + input_grantor_abf: u256, + input_grantor_vbf: u256, + output_option_abf: u256, + output_option_vbf: u256, + output_grantor_abf: u256, + output_grantor_vbf: u256 +) { + ensure_input_and_output_script_hash_eq(0); + ensure_input_and_output_script_hash_eq(1); + + verify_input_reissuance_token(0, param::OPTION_REISSUANCE_TOKEN_ASSET, input_option_abf, input_option_vbf); + verify_input_reissuance_token(1, param::GRANTOR_REISSUANCE_TOKEN_ASSET, input_grantor_abf, input_grantor_vbf); + + verify_output_reissuance_token(0, param::OPTION_REISSUANCE_TOKEN_ASSET, output_option_abf, output_option_vbf); + verify_output_reissuance_token(1, param::GRANTOR_REISSUANCE_TOKEN_ASSET, output_grantor_abf, output_grantor_vbf); + + assert!(dbg!(jet::eq_256(get_output_script_hash(0), get_output_script_hash(1)))); + + assert!(jet::le_32(jet::current_index(), 1)); + + ensure_output_script_hash_eq(2, get_output_script_hash(0)); + + let (collateral_asset_bits, collateral_amount): (u256, u64) = get_output_explicit_asset_amount(2); + let option_token_amount: u64 = unwrap_right::<(u1, u256)>(unwrap(unwrap(jet::issuance_asset_amount(0)))); + let grantor_token_amount: u64 = unwrap_right::<(u1, u256)>(unwrap(unwrap(jet::issuance_asset_amount(1)))); + assert!(jet::eq_64(option_token_amount, grantor_token_amount)); + + divmod_eq(collateral_amount, param::COLLATERAL_PER_CONTRACT, option_token_amount); + divmod_eq(expected_asset_amount, param::SETTLEMENT_PER_CONTRACT, option_token_amount); + + ensure_output_asset_with_amount_eq(2, param::COLLATERAL_ASSET_ID, collateral_amount); + ensure_output_asset_with_amount_eq(3, param::OPTION_TOKEN_ASSET, option_token_amount); + ensure_output_asset_with_amount_eq(4, param::GRANTOR_TOKEN_ASSET, grantor_token_amount); +} + +/* + * Cancellation Path + */ +fn cancellation_path(amount_to_burn: u64, collateral_amount_to_withdraw: u64, is_change_needed: bool) { + let collateral_input_index: u32 = 0; + let option_input_index: u32 = 1; + let grantor_input_index: u32 = 2; + + let (burn_option_output_index, burn_grantor_output_index): (u32, u32) = match is_change_needed { + true => (1, 2), + false => (0, 1), + }; + + let expected_current_script_hash: u256 = get_input_script_hash(collateral_input_index); + + // Check and ensure collateral change + ensure_correct_change_at_index(0, param::COLLATERAL_ASSET_ID, collateral_amount_to_withdraw, expected_current_script_hash, is_change_needed); + + // Burn option and grantor tokens + ensure_output_is_op_return(burn_option_output_index); + ensure_output_is_op_return(burn_grantor_output_index); + + ensure_output_asset_with_amount_eq(burn_option_output_index, param::OPTION_TOKEN_ASSET, amount_to_burn); + ensure_output_asset_with_amount_eq(burn_grantor_output_index, param::GRANTOR_TOKEN_ASSET, amount_to_burn); + + // Ensure returned collateral amount is correct + divmod_eq(collateral_amount_to_withdraw, param::COLLATERAL_PER_CONTRACT, amount_to_burn); +} + +/* + * Exercise Path + */ +fn exercise_path(option_amount_to_burn: u64, collateral_amount_to_get: u64, asset_amount_to_pay: u64, is_change_needed: bool) { + jet::check_lock_time(param::START_TIME); + + let collateral_input_index: u32 = 0; + + let (burn_option_output_index, asset_to_covenant_output_index): (u32, u32) = match is_change_needed { + true => (1, 2), + false => (0, 1), + }; + + let expected_current_script_hash: u256 = get_input_script_hash(collateral_input_index); + + // Check and ensure collateral change + ensure_correct_change_at_index(0, param::COLLATERAL_ASSET_ID, collateral_amount_to_get, expected_current_script_hash, is_change_needed); + + // Ensure collateral and asset amounts are correct + divmod_eq(collateral_amount_to_get, param::COLLATERAL_PER_CONTRACT, option_amount_to_burn); + divmod_eq(asset_amount_to_pay, param::SETTLEMENT_PER_CONTRACT, option_amount_to_burn); + + // Burn option token + ensure_output_is_op_return(burn_option_output_index); + ensure_output_asset_with_amount_eq(burn_option_output_index, param::OPTION_TOKEN_ASSET, option_amount_to_burn); + + // Ensure settlement asset and script hash are correct + ensure_output_asset_with_amount_eq(asset_to_covenant_output_index, param::SETTLEMENT_ASSET_ID, asset_amount_to_pay); + ensure_output_script_hash_eq(asset_to_covenant_output_index, expected_current_script_hash); +} + +/* + * Settlement Path + */ +fn settlement_path(grantor_token_amount_to_burn: u64, asset_amount: u64, is_change_needed: bool) { + jet::check_lock_time(param::START_TIME); + + let target_asset_input_index: u32 = 0; + + let burn_grantor_output_index: u32 = match is_change_needed { + true => 1, + false => 0, + }; + + let expected_current_script_hash: u256 = get_input_script_hash(target_asset_input_index); + + // Check and ensure settlement asset change + ensure_correct_change_at_index(0, param::SETTLEMENT_ASSET_ID, asset_amount, expected_current_script_hash, is_change_needed); + + // Ensure settlement asset and grantor token amounts are correct + divmod_eq(asset_amount, param::SETTLEMENT_PER_CONTRACT, grantor_token_amount_to_burn); + + // Burn grantor token + ensure_output_is_op_return(burn_grantor_output_index); + ensure_output_asset_with_amount_eq(burn_grantor_output_index, param::GRANTOR_TOKEN_ASSET, grantor_token_amount_to_burn); +} + +/* + * Expiry Path + */ +fn expiry_path(grantor_token_amount_to_burn: u64, collateral_amount: u64, is_change_needed: bool) { + jet::check_lock_time(param::EXPIRY_TIME); + + let collateral_input_index: u32 = 0; + + let burn_grantor_output_index: u32 = match is_change_needed { + true => 1, + false => 0, + }; + + let expected_current_script_hash: u256 = get_input_script_hash(collateral_input_index); + + // Check and ensure collateral change + ensure_correct_change_at_index(0, param::COLLATERAL_ASSET_ID, collateral_amount, expected_current_script_hash, is_change_needed); + + // Ensure collateral amount is correct + divmod_eq(collateral_amount, param::COLLATERAL_PER_CONTRACT, grantor_token_amount_to_burn); + + // Burn grantor token + ensure_output_is_op_return(burn_grantor_output_index); + ensure_output_asset_with_amount_eq(burn_grantor_output_index, param::GRANTOR_TOKEN_ASSET, grantor_token_amount_to_burn); +} + +fn main() { + match witness::PATH { + Left(left_or_right: Either<(u64, u256, u256, u256, u256, u256, u256, u256, u256), Either<(bool, u64, u64, u64), (bool, u64, u64)>>) => match left_or_right { + Left(params: (u64, u256, u256, u256, u256, u256, u256, u256, u256)) => { + let (expected_asset_amount, input_option_abf, input_option_vbf, input_grantor_abf, input_grantor_vbf, output_option_abf, output_option_vbf, output_grantor_abf, output_grantor_vbf): (u64, u256, u256, u256, u256, u256, u256, u256, u256) = params; + funding_path( + expected_asset_amount, + input_option_abf, input_option_vbf, + input_grantor_abf, input_grantor_vbf, + output_option_abf, output_option_vbf, + output_grantor_abf, output_grantor_vbf + ); + }, + Right(exercise_or_settlement: Either<(bool, u64, u64, u64), (bool, u64, u64)>) => match exercise_or_settlement { + Left(params: (bool, u64, u64, u64)) => { + let (is_change_needed, amount_to_burn, collateral_amount, asset_amount): (bool, u64, u64, u64) = dbg!(params); + exercise_path(amount_to_burn, collateral_amount, asset_amount, is_change_needed) + }, + Right(params: (bool, u64, u64)) => { + let (is_change_needed, amount_to_burn, asset_amount): (bool, u64, u64) = dbg!(params); + settlement_path(amount_to_burn, asset_amount, is_change_needed) + }, + }, + }, + Right(left_or_right: Either<(bool, u64, u64), (bool, u64, u64)>) => match left_or_right { + Left(params: (bool, u64, u64)) => { + let (is_change_needed, grantor_token_amount_to_burn, collateral_amount): (bool, u64, u64) = params; + expiry_path(grantor_token_amount_to_burn, collateral_amount, is_change_needed) + }, + Right(params: (bool, u64, u64)) => { + let (is_change_needed, amount_to_burn, collateral_amount): (bool, u64, u64) = params; + cancellation_path(amount_to_burn, collateral_amount, is_change_needed) + }, + }, + } +} diff --git a/macros/examples/test_roundtrip.rs b/macros/examples/test_roundtrip.rs new file mode 100644 index 00000000..b9a8f857 --- /dev/null +++ b/macros/examples/test_roundtrip.rs @@ -0,0 +1,20 @@ +use simplicityhl_macros::*; + +include_simf!( + "/Users/ikripaka/Documents/Work_dl/SimplicityHL-copy/macros/examples/source_simf/options.simf" +); + +fn main() -> Result<(), String> { + // let original_witness = derived_options::OptionsWitness { + // path: simplicityhl::either::Either::Right(simplicityhl::either::Either::Left(( + // true, 100, 200, + // ))), + // }; + // + // let witness_values = original_witness.build_witness(); + // + // let recovered_witness = derived_options::OptionsWitness::from_witness(&witness_values)?; + // assert_eq!(original_witness, recovered_witness); + + Ok(()) +} diff --git a/macros/src/codegen/mod.rs b/macros/src/codegen/mod.rs new file mode 100644 index 00000000..2932f616 --- /dev/null +++ b/macros/src/codegen/mod.rs @@ -0,0 +1,459 @@ +use crate::codegen::types::RustType; +use crate::convert_error_to_syn; +use crate::parse::SimfContent; +use quote::{format_ident, quote}; +use simplicityhl::str::WitnessName; +use simplicityhl::{AbiMeta, Parameters, ResolvedType, TemplateProgram, WitnessTypes}; +use std::error::Error; + +// TODO(Illia): add bincode generation feature (i.e. require bincode dependencies) +// TODO(Illia): add conditional compilation for simplicity-core to e included automatically + +// TODO(Illia): automatically derive bincode implementation +// TODO(Illia): extract either:serde feature and use it when simplicityhl has serde feature +// TODO(Illia): add features + +mod types; + +pub fn compile_program(content: &SimfContent) -> syn::Result { + compile_program_inner(content).map_err(|e| convert_error_to_syn(e)) +} + +fn compile_program_inner(content: &SimfContent) -> Result> { + let program = content.content.as_str(); + Ok(TemplateProgram::new(program)?.generate_abi_meta()?) +} + +pub fn gen_helpers( + simf_content: SimfContent, + meta: AbiMeta, +) -> syn::Result { + gen_helpers_inner(simf_content, meta).map_err(|e| convert_error_to_syn(e)) +} + +struct DerivedMeta { + contract_source_const_name: proc_macro2::Ident, + args_struct: ConvertedMeta, + witness_struct: ConvertedMeta, + simf_content: SimfContent, + abi_meta: AbiMeta, +} + +impl DerivedMeta { + fn try_from(simf_content: SimfContent, abi_meta: AbiMeta) -> syn::Result { + let args_struct = ConvertedMeta::generate_args_struct( + &simf_content.contract_name, + &abi_meta.param_types, + )?; + let witness_struct = ConvertedMeta::generate_witness_struct( + &simf_content.contract_name, + &abi_meta.witness_types, + )?; + let contract_source_const_name = format_ident!( + "{}_CONTRACT_SOURCE", + simf_content.contract_name.to_uppercase() + ); + Ok(DerivedMeta { + contract_source_const_name, + args_struct, + witness_struct, + simf_content, + abi_meta, + }) + } +} + +fn gen_helpers_inner( + simf_content: SimfContent, + meta: AbiMeta, +) -> Result> { + let mod_ident = format_ident!("derived_{}", simf_content.contract_name); + + let derived_meta = DerivedMeta::try_from(simf_content, meta)?; + + let program_helpers = construct_program_helpers(&derived_meta); + let witness_helpers = construct_witness_helpers(&derived_meta)?; + let arguments_helpers = construct_argument_helpers(&derived_meta)?; + + Ok(quote! { + pub mod #mod_ident{ + #program_helpers + + #witness_helpers + + #arguments_helpers + } + }) +} + +fn construct_program_helpers(derived_meta: &DerivedMeta) -> proc_macro2::TokenStream { + let contract_content = &derived_meta.simf_content.content; + let error_msg = format!( + "INTERNAL: expected '{}' Program to compile successfully.", + derived_meta.simf_content.contract_name + ); + let contract_source_name = &derived_meta.contract_source_const_name; + let contract_arguments_struct_name = &derived_meta.args_struct.struct_name; + + quote! { + use simplicityhl::elements::Address; + use simplicityhl::simplicity::bitcoin::XOnlyPublicKey; + use simplicityhl_core::{create_p2tr_address, load_program, ProgramError, SimplicityNetwork}; + use simplicityhl::CompiledProgram; + + pub const #contract_source_name: &str = #contract_content; + + /// Get the options template program for instantiation. + /// + /// # Panics + /// - if the embedded source fails to compile (should never happen). + #[must_use] + pub fn get_template_program() -> ::simplicityhl::TemplateProgram { + ::simplicityhl::TemplateProgram::new(#contract_source_name).expect(#error_msg) + } + + /// Derive P2TR address for an option offer contract. + /// + /// # Errors + /// + /// Returns error if program compilation fails. + pub fn get_option_offer_address( + x_only_public_key: &XOnlyPublicKey, + arguments: &#contract_arguments_struct_name, + network: SimplicityNetwork, + ) -> Result { + Ok(create_p2tr_address( + get_loaded_program(arguments)?.commit().cmr(), + x_only_public_key, + network.address_params(), + )) + } + + /// Compile option offer program with the given arguments. + /// + /// # Errors + /// + /// Returns error if compilation fails. + pub fn get_loaded_program( + arguments: &#contract_arguments_struct_name, + ) -> Result { + load_program(#contract_source_name, arguments.build_arguments()) + } + + /// Get compiled option offer program, panicking on failure. + /// + /// # Panics + /// + /// Panics if program instantiation fails. + #[must_use] + pub fn get_compiled_program(arguments: &#contract_arguments_struct_name) -> CompiledProgram { + let program = get_template_program(); + + program + .instantiate(arguments.build_arguments(), true) + .unwrap() + } + } +} + +struct WitnessField { + witness_simf_name: String, + struct_rust_field: proc_macro2::Ident, + rust_type: RustType, +} + +impl WitnessField { + fn new(witness_name: &WitnessName, resolved_type: &ResolvedType) -> syn::Result { + let (witness_simf_name, struct_rust_field) = { + let w_name = witness_name.to_string(); + let r_name = format_ident!("{}", w_name.to_lowercase()); + (w_name, r_name) + }; + + let rust_type = RustType::from_resolved_type(resolved_type)?; + + Ok(Self { + witness_simf_name, + struct_rust_field, + rust_type, + }) + } + + /// Generate the conversion code from Rust value to Simplicity Value + fn to_token_stream(&self) -> proc_macro2::TokenStream { + let witness_name = &self.witness_simf_name; + let field_name = &self.struct_rust_field; + let conversion = self + .rust_type + .generate_to_simplicity_conversion(quote! { self.#field_name }); + + quote! { + ( + ::simplicityhl::str::WitnessName::from_str_unchecked(#witness_name), + #conversion + ) + } + } +} + +fn construct_witness_helpers(derived_meta: &DerivedMeta) -> syn::Result { + let GeneratedWitnessTokens { + imports, + struct_token_stream, + struct_impl, + } = derived_meta.witness_struct.generate_witness_impl()?; + + Ok(quote! { + pub use build_witness::*; + mod build_witness { + #imports + + #struct_token_stream + + #struct_impl + } + }) +} + +fn construct_argument_helpers(derived_meta: &DerivedMeta) -> syn::Result { + let GeneratedArgumentsTokens { + imports, + struct_token_stream, + struct_impl, + } = derived_meta.args_struct.generate_arguments_impl()?; + + Ok(quote! { + pub use build_arguments::*; + mod build_arguments { + #imports + + #struct_token_stream + + #struct_impl + } + }) +} + +struct ConvertedMeta { + struct_name: proc_macro2::Ident, + witness_values: Vec, +} + +struct GeneratedArgumentsTokens { + imports: proc_macro2::TokenStream, + struct_token_stream: proc_macro2::TokenStream, + struct_impl: proc_macro2::TokenStream, +} + +struct GeneratedWitnessTokens { + imports: proc_macro2::TokenStream, + struct_token_stream: proc_macro2::TokenStream, + struct_impl: proc_macro2::TokenStream, +} + +impl ConvertedMeta { + fn generate_args_struct(contract_name: &str, meta: &Parameters) -> syn::Result { + let base_name = convert_contract_name_to_struct_name(contract_name); + Ok(ConvertedMeta { + struct_name: format_ident!("{}Arguments", base_name), + witness_values: ConvertedMeta::generate_witness_fields(meta.iter())?, + }) + } + + fn generate_witness_struct( + contract_name: &str, + meta: &WitnessTypes, + ) -> syn::Result { + let base_name = convert_contract_name_to_struct_name(contract_name); + Ok(ConvertedMeta { + struct_name: format_ident!("{}Witness", base_name), + witness_values: ConvertedMeta::generate_witness_fields(meta.iter())?, + }) + } + + fn generate_witness_fields<'a>( + iter: impl Iterator, + ) -> syn::Result> { + iter.map(|(name, resolved_type)| WitnessField::new(name, resolved_type)) + .collect() + } + + fn generate_arguments_impl(&self) -> syn::Result { + let generated_struct = self.generate_struct_token_stream(); + let struct_name = &self.struct_name; + let tuples: Vec = self.construct_witness_tuples(); + let (arguments_conversion_from_args_map, struct_to_return): ( + proc_macro2::TokenStream, + proc_macro2::TokenStream, + ) = self.generate_from_args_conversion_with_param_name("args"); + + Ok(GeneratedArgumentsTokens { + imports: quote! { + use std::collections::HashMap; + use simplicityhl::{Arguments, Value, ResolvedType}; + use simplicityhl::value::{UIntValue, ValueInner}; + use simplicityhl::num::U256; + use simplicityhl::str::WitnessName; + use simplicityhl::types::TypeConstructible; + use simplicityhl::value::ValueConstructible; + use bincode::*; + }, + struct_token_stream: quote! { + #generated_struct + }, + struct_impl: quote! { + impl #struct_name { + /// Build Simplicity arguments for contract instantiation. + #[must_use] + pub fn build_arguments(&self) -> simplicityhl::Arguments { + simplicityhl::Arguments::from(HashMap::from([ + #(#tuples),* + ])) + } + + /// Build struct from Simplicity Arguments. + /// + /// # Errors + /// + /// Returns error if any required witness is missing, has wrong type, or has invalid value. + pub fn from_arguments(args: &Arguments) -> Result { + #arguments_conversion_from_args_map + + Ok(#struct_to_return) + } + } + }, + }) + } + + fn generate_witness_impl(&self) -> syn::Result { + let generated_struct = self.generate_struct_token_stream(); + let struct_name = &self.struct_name; + let tuples: Vec = self.construct_witness_tuples(); + let (arguments_conversion_from_args_map, struct_to_return): ( + proc_macro2::TokenStream, + proc_macro2::TokenStream, + ) = self.generate_from_args_conversion_with_param_name("witness"); + + Ok(GeneratedWitnessTokens { + imports: quote! { + use std::collections::HashMap; + use simplicityhl::{WitnessValues, Value, ResolvedType}; + use simplicityhl::value::{UIntValue, ValueInner}; + use simplicityhl::num::U256; + use simplicityhl::str::WitnessName; + use simplicityhl::types::TypeConstructible; + use simplicityhl::value::ValueConstructible; + }, + struct_token_stream: quote! { + #generated_struct + }, + struct_impl: quote! { + impl #struct_name { + /// Build Simplicity witness values for contract execution. + #[must_use] + pub fn build_witness(&self) -> simplicityhl::WitnessValues { + simplicityhl::WitnessValues::from(HashMap::from([ + #(#tuples),* + ])) + } + + /// Build struct from Simplicity WitnessValues. + /// + /// # Errors + /// + /// Returns error if any required witness is missing, has wrong type, or has invalid value. + pub fn from_witness(witness: &WitnessValues) -> Result { + #arguments_conversion_from_args_map + + Ok(#struct_to_return) + } + } + }, + }) + } + + fn generate_struct_token_stream(&self) -> proc_macro2::TokenStream { + let name = format_ident!("{}", self.struct_name); + let fields: Vec = self + .witness_values + .iter() + .map(|field| { + let field_name = format_ident!("{}", field.struct_rust_field); + let field_type = field.rust_type.to_type_token_stream(); + quote! { pub #field_name: #field_type } + }) + .collect(); + quote! { + // #[derive(bincode::Encode, bincode::Decode)] + #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] + pub struct #name { + #(#fields),* + } + } + } + + #[inline] + fn construct_witness_tuples(&self) -> Vec { + self.witness_values + .iter() + .map(|x| x.to_token_stream()) + .collect() + } + + /// Generate conversion code from Arguments/WitnessValues back to struct fields. + /// Returns a tuple of (extraction_code, struct_initialization_code). + fn generate_from_args_conversion_with_param_name( + &self, + param_name: &str, + ) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { + let param_ident = format_ident!("{}", param_name); + let field_extractions: Vec = self + .witness_values + .iter() + .map(|field| { + let field_name = &field.struct_rust_field; + let witness_name = &field.witness_simf_name; + let extraction = field + .rust_type + .generate_from_value_extraction(param_ident.clone(), witness_name); + quote! { + let #field_name = #extraction?; + } + }) + .collect(); + + let field_names: Vec = self + .witness_values + .iter() + .map(|field| format_ident!("{}", field.struct_rust_field)) + .collect(); + + let extractions = quote! { + #(#field_extractions)* + }; + + let struct_init = quote! { + Self { + #(#field_names),* + } + }; + + (extractions, struct_init) + } +} + +fn convert_contract_name_to_struct_name(contract_name: &str) -> String { + let words: Vec = contract_name + .split('_') + .filter(|w| !w.is_empty()) + .map(|word| { + let mut chars = word.chars(); + match chars.next() { + None => String::new(), + Some(first) => first.to_uppercase().collect::() + chars.as_str(), + } + }) + .collect(); + words.join("") +} diff --git a/macros/src/codegen/types.rs b/macros/src/codegen/types.rs new file mode 100644 index 00000000..7ec0dbe5 --- /dev/null +++ b/macros/src/codegen/types.rs @@ -0,0 +1,703 @@ +use quote::quote; +use simplicityhl::ResolvedType; + +/// Recursive Rust type representation for code generation +#[derive(Debug, Clone)] +#[non_exhaustive] +pub enum RustType { + Bool, + U8, + U16, + U32, + U64, + U128, + U256Array, + Array(Box, usize), + Tuple(Vec), + Either(Box, Box), + Option(Box), +} + +impl RustType { + pub fn from_resolved_type(ty: &ResolvedType) -> syn::Result { + use simplicityhl::types::{TypeInner, UIntType}; + + match ty.as_inner() { + TypeInner::Boolean => Ok(RustType::Bool), + TypeInner::UInt(uint_ty) => match uint_ty { + UIntType::U1 => Ok(RustType::Bool), + UIntType::U2 | UIntType::U4 | UIntType::U8 => Ok(RustType::U8), + UIntType::U16 => Ok(RustType::U16), + UIntType::U32 => Ok(RustType::U32), + UIntType::U64 => Ok(RustType::U64), + UIntType::U128 => Ok(RustType::U128), + UIntType::U256 => Ok(RustType::U256Array), + }, + TypeInner::Either(left, right) => { + let left_ty = Self::from_resolved_type(left)?; + let right_ty = Self::from_resolved_type(right)?; + Ok(RustType::Either(Box::new(left_ty), Box::new(right_ty))) + } + TypeInner::Option(inner) => { + let inner_ty = Self::from_resolved_type(inner)?; + Ok(RustType::Option(Box::new(inner_ty))) + } + TypeInner::Tuple(elements) => { + let element_types: syn::Result> = elements + .iter() + .map(|e| Self::from_resolved_type(e)) + .collect(); + Ok(RustType::Tuple(element_types?)) + } + TypeInner::Array(element, size) => { + let element_ty = Self::from_resolved_type(element)?; + Ok(RustType::Array(Box::new(element_ty), *size)) + } + TypeInner::List(_, _) => Err(syn::Error::new( + proc_macro2::Span::call_site(), + "List types are not yet supported in macro conversions", + )), + _ => Err(syn::Error::new( + proc_macro2::Span::call_site(), + "Unsupported type in macro conversions", + )), + } + } + + /// Generate the Rust type as a TokenStream for struct field declarations + pub fn to_type_token_stream(&self) -> proc_macro2::TokenStream { + match self { + RustType::Bool => quote! { bool }, + RustType::U8 => quote! { u8 }, + RustType::U16 => quote! { u16 }, + RustType::U32 => quote! { u32 }, + RustType::U64 => quote! { u64 }, + RustType::U128 => quote! { u128 }, + RustType::U256Array => quote! { [u8; 32] }, + RustType::Array(element, size) => { + let element_ty = element.to_type_token_stream(); + quote! { [#element_ty; #size] } + } + RustType::Tuple(elements) => { + let element_types: Vec<_> = + elements.iter().map(|e| e.to_type_token_stream()).collect(); + quote! { (#(#element_types),*) } + } + RustType::Either(left, right) => { + let left_ty = left.to_type_token_stream(); + let right_ty = right.to_type_token_stream(); + quote! { ::simplicityhl::either::Either<#left_ty, #right_ty> } + } + RustType::Option(inner) => { + let inner_ty = inner.to_type_token_stream(); + quote! { Option<#inner_ty> } + } + } + } + + /// Generate conversion code from Rust value to Simplicity Value + /// `value_expr` is the expression that produces the Rust value to convert + pub fn generate_to_simplicity_conversion( + &self, + value_expr: proc_macro2::TokenStream, + ) -> proc_macro2::TokenStream { + match self { + RustType::Bool => { + quote! { Value::from(#value_expr) } + } + RustType::U8 => { + quote! { Value::from(UIntValue::U8(#value_expr)) } + } + RustType::U16 => { + quote! { Value::from(UIntValue::U16(#value_expr)) } + } + RustType::U32 => { + quote! { Value::from(UIntValue::U32(#value_expr)) } + } + RustType::U64 => { + quote! { Value::from(UIntValue::U64(#value_expr)) } + } + RustType::U128 => { + quote! { Value::from(UIntValue::U128(#value_expr)) } + } + RustType::U256Array => { + quote! { Value::from(UIntValue::U256(U256::from_byte_array(#value_expr))) } + } + RustType::Array(element, size) => { + // For arrays, we need to generate inline conversions + let indices: Vec<_> = (0..*size).map(syn::Index::from).collect(); + let element_conversions: Vec<_> = indices + .iter() + .map(|idx| { + let elem_expr = quote! { #value_expr[#idx] }; + element.generate_to_simplicity_conversion(elem_expr) + }) + .collect(); + + // Generate the element type token stream for type inference + let elem_ty_generation = element.generate_simplicity_type_construction(); + + quote! { + { + let elements = [#(#element_conversions),*]; + Value::array(elements, #elem_ty_generation) + } + } + } + RustType::Tuple(elements) => { + if elements.is_empty() { + quote! { Value::unit() } + } else { + let tuple_conversions = elements.iter().enumerate().map(|(i, elem_ty)| { + let idx = syn::Index::from(i); + let elem_expr = quote! { #value_expr.#idx }; + elem_ty.generate_to_simplicity_conversion(elem_expr) + }); + + quote! { + Value::tuple([#(#tuple_conversions),*]) + } + } + } + RustType::Either(left, right) => { + let left_conv = left.generate_to_simplicity_conversion(quote! { left_val }); + let right_conv = right.generate_to_simplicity_conversion(quote! { right_val }); + let left_ty = left.generate_simplicity_type_construction(); + let right_ty = right.generate_simplicity_type_construction(); + + quote! { + match &#value_expr { + ::simplicityhl::either::Either::Left(left_val) => { + Value::left( + #left_conv, + #right_ty + ) + } + ::simplicityhl::either::Either::Right(right_val) => { + Value::right( + #left_ty, + #right_conv + ) + } + } + } + } + RustType::Option(inner) => { + let inner_conv = inner.generate_to_simplicity_conversion(quote! { inner_val }); + let inner_ty = inner.generate_simplicity_type_construction(); + + quote! { + match &#value_expr { + None => { + Value::none(#inner_ty) + } + Some(inner_val) => { + Value::some(#inner_conv) + } + } + } + } + } + } + + pub fn generate_simplicity_type_construction(&self) -> proc_macro2::TokenStream { + match self { + RustType::Bool => { + quote! { ResolvedType::boolean() } + } + RustType::U8 => { + quote! { ResolvedType::u8() } + } + RustType::U16 => { + quote! { ResolvedType::u16() } + } + RustType::U32 => { + quote! { ResolvedType::u32() } + } + RustType::U64 => { + quote! { ResolvedType::u64() } + } + RustType::U128 => { + quote! { ResolvedType::u128() } + } + RustType::U256Array => { + quote! { ResolvedType::u256() } + } + RustType::Array(element, size) => { + let elem_ty = element.generate_simplicity_type_construction(); + quote! { ResolvedType::array(#elem_ty, #size) } + } + RustType::Tuple(elements) => { + let elem_types: Vec<_> = elements + .iter() + .map(|e| e.generate_simplicity_type_construction()) + .collect(); + quote! { ResolvedType::tuple([#(#elem_types),*]) } + } + RustType::Either(left, right) => { + let left_ty = left.generate_simplicity_type_construction(); + let right_ty = right.generate_simplicity_type_construction(); + quote! { ResolvedType::either(#left_ty, #right_ty) } + } + RustType::Option(inner) => { + let inner_ty = inner.generate_simplicity_type_construction(); + quote! { ResolvedType::option(#inner_ty) } + } + } + } + + pub fn generate_from_value_extraction( + &self, + args_expr: proc_macro2::Ident, + witness_name: &str, + ) -> proc_macro2::TokenStream { + match self { + RustType::Bool => { + quote! { + { + let witness_name = WitnessName::from_str_unchecked(#witness_name); + let value = #args_expr + .get(&witness_name) + .ok_or_else(|| format!("Missing witness: {}", #witness_name))?; + match value.inner() { + simplicityhl::value::ValueInner::Boolean(b) => Ok(*b), + _ => Err(format!("Wrong type for {}: expected bool", #witness_name)), + } + } + } + } + RustType::U8 => { + quote! { + { + let witness_name = WitnessName::from_str_unchecked(#witness_name); + let value = #args_expr + .get(&witness_name) + .ok_or_else(|| format!("Missing witness: {}", #witness_name))?; + match value.inner() { + simplicityhl::value::ValueInner::UInt(UIntValue::U8(v)) => Ok(*v), + _ => Err(format!("Wrong type for {}: expected U8", #witness_name)), + } + } + } + } + RustType::U16 => { + quote! { + { + let witness_name = WitnessName::from_str_unchecked(#witness_name); + let value = #args_expr + .get(&witness_name) + .ok_or_else(|| format!("Missing witness: {}", #witness_name))?; + match value.inner() { + simplicityhl::value::ValueInner::UInt(UIntValue::U16(v)) => Ok(*v), + _ => Err(format!("Wrong type for {}: expected U16", #witness_name)), + } + } + } + } + RustType::U32 => { + quote! { + { + let witness_name = WitnessName::from_str_unchecked(#witness_name); + let value = #args_expr + .get(&witness_name) + .ok_or_else(|| format!("Missing witness: {}", #witness_name))?; + match value.inner() { + simplicityhl::value::ValueInner::UInt(UIntValue::U32(v)) => Ok(*v), + _ => Err(format!("Wrong type for {}: expected U32", #witness_name)), + } + } + } + } + RustType::U64 => { + quote! { + { + let witness_name = WitnessName::from_str_unchecked(#witness_name); + let value = #args_expr + .get(&witness_name) + .ok_or_else(|| format!("Missing witness: {}", #witness_name))?; + match value.inner() { + simplicityhl::value::ValueInner::UInt(UIntValue::U64(v)) => Ok(*v), + _ => Err(format!("Wrong type for {}: expected U64", #witness_name)), + } + } + } + } + RustType::U128 => { + quote! { + { + let witness_name = WitnessName::from_str_unchecked(#witness_name); + let value = #args_expr + .get(&witness_name) + .ok_or_else(|| format!("Missing witness: {}", #witness_name))?; + match value.inner() { + simplicityhl::value::ValueInner::UInt(UIntValue::U128(v)) => Ok(*v), + _ => Err(format!("Wrong type for {}: expected U128", #witness_name)), + } + } + } + } + RustType::U256Array => { + quote! { + { + let witness_name = WitnessName::from_str_unchecked(#witness_name); + let value = #args_expr + .get(&witness_name) + .ok_or_else(|| format!("Missing witness: {}", #witness_name))?; + match value.inner() { + simplicityhl::value::ValueInner::UInt(UIntValue::U256(u256)) => Ok(u256.to_byte_array()), + _ => Err(format!("Wrong type for {}: expected U256", #witness_name)), + } + } + } + } + RustType::Array(element, size) => { + let elem_extraction = (0..*size).map(|i| { + // For each element, generate extraction code + // This is a simplified version - for complex nested types, + // we'd need to extract from array values + element.generate_inline_array_element_extraction(quote! { arr_value }, i) + }); + + quote! { + { + let witness_name = WitnessName::from_str_unchecked(#witness_name); + let value = #args_expr + .get(&witness_name) + .ok_or_else(|| format!("Missing witness: {}", #witness_name))?; + match value.inner() { + simplicityhl::value::ValueInner::Array(arr_value) => { + if arr_value.len() != #size { + return Err(format!("Wrong array length for {}: expected {}, got {}", #witness_name, #size, arr_value.len())); + } + Ok([#(#elem_extraction),*]) + } + _ => Err(format!("Wrong type for {}: expected Array", #witness_name)), + } + } + } + } + RustType::Tuple(elements) => { + let elem_extractions: Vec<_> = elements + .iter() + .enumerate() + .map(|(i, elem_ty)| { + elem_ty.generate_inline_tuple_element_extraction(quote! { tuple_value }, i) + }) + .collect(); + + quote! { + { + let witness_name = WitnessName::from_str_unchecked(#witness_name); + let value = #args_expr + .get(&witness_name) + .ok_or_else(|| format!("Missing witness: {}", #witness_name))?; + match value.inner() { + simplicityhl::value::ValueInner::Tuple(tuple_value) => { + if tuple_value.len() != #(elements.len()) { + return Err(format!("Wrong tuple length for {}", #witness_name)); + } + Ok((#(#elem_extractions),*)) + } + _ => Err(format!("Wrong type for {}: expected Tuple", #witness_name)), + } + } + } + } + //TODO: redo + RustType::Either(left, right) => { + let left_extraction = left.generate_inline_either_extraction(quote! { left_val }); + let right_extraction = + right.generate_inline_either_extraction(quote! { right_val }); + + quote! { + { + let witness_name = WitnessName::from_str_unchecked(#witness_name); + let value = #args_expr + .get(&witness_name) + .ok_or_else(|| format!("Missing witness: {}", #witness_name))?; + match value.inner() { + simplicityhl::value::ValueInner::Either(either_val) => { + match either_val { + ::simplicityhl::either::Either::Left(left_val) => { + Ok(::simplicityhl::either::Either::Left(#left_extraction?)) + } + ::simplicityhl::either::Either::Right(right_val) => { + Ok(::simplicityhl::either::Either::Right(#right_extraction?)) + } + } + } + _ => Err(format!("Wrong type for {}: expected Either", #witness_name)), + } + } + } + } + // TODO: redo + RustType::Option(inner) => { + let inner_extraction = inner.generate_inline_either_extraction(quote! { some_val }); + + quote! { + { + let witness_name = WitnessName::from_str_unchecked(#witness_name); + let value = #args_expr + .get(&witness_name) + .ok_or_else(|| format!("Missing witness: {}", #witness_name))?; + match value.inner() { + simplicityhl::value::ValueInner::Option(opt_val) => { + match opt_val { + None => Ok(None), + Some(some_val) => Ok(Some(#inner_extraction?)), + } + } + _ => Err(format!("Wrong type for {}: expected Option", #witness_name)), + } + } + } + } + } + } + + pub fn generate_inline_array_element_extraction( + &self, + arr_expr: proc_macro2::TokenStream, + index: usize, + ) -> proc_macro2::TokenStream { + match self { + RustType::Bool => quote! { + match #arr_expr[#index].inner() { + simplicityhl::value::ValueInner::Boolean(b) => *b, + _ => return Err(format!("Wrong element type at index {}", #index)), + } + }, + RustType::U8 => quote! { + match #arr_expr[#index].inner() { + simplicityhl::value::ValueInner::UInt(UIntValue::U8(v)) => *v, + _ => return Err(format!("Wrong element type at index {}", #index)), + } + }, + RustType::U16 => quote! { + match #arr_expr[#index].inner() { + simplicityhl::value::ValueInner::UInt(UIntValue::U16(v)) => *v, + _ => return Err(format!("Wrong element type at index {}", #index)), + } + }, + RustType::U32 => quote! { + match #arr_expr[#index].inner() { + simplicityhl::value::ValueInner::UInt(UIntValue::U32(v)) => *v, + _ => return Err(format!("Wrong element type at index {}", #index)), + } + }, + RustType::U64 => quote! { + match #arr_expr[#index].inner() { + simplicityhl::value::ValueInner::UInt(UIntValue::U64(v)) => *v, + _ => return Err(format!("Wrong element type at index {}", #index)), + } + }, + RustType::U128 => quote! { + match #arr_expr[#index].inner() { + simplicityhl::value::ValueInner::UInt(UIntValue::U128(v)) => *v, + _ => return Err(format!("Wrong element type at index {}", #index)), + } + }, + RustType::U256Array => quote! { + match #arr_expr[#index].inner() { + simplicityhl::value::ValueInner::UInt(UIntValue::U256(u256)) => u256.to_byte_array(), + _ => return Err(format!("Wrong element type at index {}", #index)), + } + }, + _ => quote! { + return Err(format!("Complex array element extraction not yet supported at index {}", #index)) + }, + } + } + + pub fn generate_inline_tuple_element_extraction( + &self, + tuple_expr: proc_macro2::TokenStream, + index: usize, + ) -> proc_macro2::TokenStream { + match self { + RustType::Bool => quote! { + match #tuple_expr[#index].inner() { + simplicityhl::value::ValueInner::Boolean(b) => *b, + _ => return Err(format!("Wrong tuple element type at index {}", #index)), + } + }, + RustType::U8 => quote! { + match #tuple_expr[#index].inner() { + simplicityhl::value::ValueInner::UInt(UIntValue::U8(v)) => *v, + _ => return Err(format!("Wrong tuple element type at index {}", #index)), + } + }, + RustType::U16 => quote! { + match #tuple_expr[#index].inner() { + simplicityhl::value::ValueInner::UInt(UIntValue::U16(v)) => *v, + _ => return Err(format!("Wrong tuple element type at index {}", #index)), + } + }, + RustType::U32 => quote! { + match #tuple_expr[#index].inner() { + simplicityhl::value::ValueInner::UInt(UIntValue::U32(v)) => *v, + _ => return Err(format!("Wrong tuple element type at index {}", #index)), + } + }, + RustType::U64 => quote! { + match #tuple_expr[#index].inner() { + simplicityhl::value::ValueInner::UInt(UIntValue::U64(v)) => *v, + _ => return Err(format!("Wrong tuple element type at index {}", #index)), + } + }, + RustType::U128 => quote! { + match #tuple_expr[#index].inner() { + simplicityhl::value::ValueInner::UInt(UIntValue::U128(v)) => *v, + _ => return Err(format!("Wrong tuple element type at index {}", #index)), + } + }, + RustType::U256Array => quote! { + match #tuple_expr[#index].inner() { + simplicityhl::value::ValueInner::UInt(UIntValue::U256(u256)) => u256.to_byte_array(), + _ => return Err(format!("Wrong tuple element type at index {}", #index)), + } + }, + RustType::Tuple(elements) => { + let elem_extractions: Vec<_> = elements + .iter() + .enumerate() + .map(|(i, elem_ty)| { + elem_ty.generate_inline_tuple_element_extraction(quote! { nested_tuple }, i) + }) + .collect(); + + quote! { + match #tuple_expr[#index].inner() { + simplicityhl::value::ValueInner::Tuple(nested_tuple) => { + (#(#elem_extractions),*) + } + _ => return Err(format!("Wrong tuple element type at index {}", #index)), + } + } + } + RustType::Either(left, right) => { + let left_extraction = left.generate_inline_either_extraction(quote! { left_val }); + let right_extraction = + right.generate_inline_either_extraction(quote! { right_val }); + + quote! { + match #tuple_expr[#index].inner() { + simplicityhl::value::ValueInner::Either(either_val) => { + match either_val { + ::simplicityhl::either::Either::Left(left_val) => { + ::simplicityhl::either::Either::Left(match { #left_extraction } { + Ok(v) => v, + Err(e) => return Err(e), + }) + } + ::simplicityhl::either::Either::Right(right_val) => { + ::simplicityhl::either::Either::Right(match { #right_extraction } { + Ok(v) => v, + Err(e) => return Err(e), + }) + } + } + } + _ => return Err(format!("Wrong tuple element type at index {}", #index)), + } + } + } + _ => quote! { + return Err(format!("Complex tuple element extraction not yet supported at index {}", #index)) + }, + } + } + + pub fn generate_inline_either_extraction( + &self, + val_expr: proc_macro2::TokenStream, + ) -> proc_macro2::TokenStream { + match self { + RustType::Bool => quote! { + match #val_expr.inner() { + simplicityhl::value::ValueInner::Boolean(b) => Ok(*b), + _ => Err("Wrong either branch type: expected bool".to_string()), + } + }, + RustType::U8 => quote! { + match #val_expr.inner() { + simplicityhl::value::ValueInner::UInt(UIntValue::U8(v)) => Ok(*v), + _ => Err("Wrong either branch type: expected U8".to_string()), + } + }, + RustType::U16 => quote! { + match #val_expr.inner() { + simplicityhl::value::ValueInner::UInt(UIntValue::U16(v)) => Ok(*v), + _ => Err("Wrong either branch type: expected U16".to_string()), + } + }, + RustType::U32 => quote! { + match #val_expr.inner() { + simplicityhl::value::ValueInner::UInt(UIntValue::U32(v)) => Ok(*v), + _ => Err("Wrong either branch type: expected U32".to_string()), + } + }, + RustType::U64 => quote! { + match #val_expr.inner() { + simplicityhl::value::ValueInner::UInt(UIntValue::U64(v)) => Ok(*v), + _ => Err("Wrong either branch type: expected U64".to_string()), + } + }, + RustType::U128 => quote! { + match #val_expr.inner() { + simplicityhl::value::ValueInner::UInt(UIntValue::U128(v)) => Ok(*v), + _ => Err("Wrong either branch type: expected U128".to_string()), + } + }, + RustType::U256Array => quote! { + match #val_expr.inner() { + simplicityhl::value::ValueInner::UInt(UIntValue::U256(u256)) => Ok(u256.to_byte_array()), + _ => Err("Wrong either branch type: expected U256".to_string()), + } + }, + RustType::Tuple(elements) => { + let elem_extractions: Vec<_> = elements + .iter() + .enumerate() + .map(|(i, elem_ty)| { + elem_ty.generate_inline_tuple_element_extraction(quote! { tuple_value }, i) + }) + .collect(); + + quote! { + match #val_expr.inner() { + simplicityhl::value::ValueInner::Tuple(tuple_value) => { + Ok((#(#elem_extractions),*)) + } + _ => Err("Wrong either branch type: expected Tuple".to_string()), + } + } + } + RustType::Either(left, right) => { + let left_extraction = left.generate_inline_either_extraction(quote! { left_val }); + let right_extraction = + right.generate_inline_either_extraction(quote! { right_val }); + + quote! { + match #val_expr.inner() { + simplicityhl::value::ValueInner::Either(either_val) => { + match either_val { + ::simplicityhl::either::Either::Left(left_val) => { + Ok(::simplicityhl::either::Either::Left(#left_extraction?)) + } + ::simplicityhl::either::Either::Right(right_val) => { + Ok(::simplicityhl::either::Either::Right(#right_extraction?)) + } + } + } + _ => Err("Wrong either branch type: expected Either".to_string()), + } + } + } + _ => quote! { + Err("Complex either branch extraction not yet supported".to_string()) + }, + } + } +} diff --git a/macros/src/lib.rs b/macros/src/lib.rs new file mode 100644 index 00000000..54dea80a --- /dev/null +++ b/macros/src/lib.rs @@ -0,0 +1,51 @@ +extern crate proc_macro; + +use quote::__private::Span; +use std::fmt::Display; +use syn::Expr; + +mod codegen; +mod parse; + +#[proc_macro] +pub fn include_simf(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + match include_simf_impl(input.into()) { + Ok(ts) => ts.into(), + Err(e) => e.to_compile_error().into(), + } +} + +// #[proc_macro] +// pub fn include_simf_source(input: proc_macro::TokenStream) -> proc_macro::TokenStream { +// match include_simf_source_impl(input.into()) { +// Ok(ts) => ts.into(), +// Err(e) => e.to_compile_error().into(), +// } +// } + +fn include_simf_impl(input: proc_macro2::TokenStream) -> syn::Result { + let input = syn::parse2::(input)?; + + let simf_content = parse::eval_path_expr(input)?; + let abi_meta = codegen::compile_program(&simf_content)?; + let generated = codegen::gen_helpers(simf_content, abi_meta)?; + + Ok(generated) +} + +// TODO: implement parsing of a constant +// fn include_simf_source_impl(input: proc_macro2::TokenStream) -> syn::Result { +// let simf_content = if let Ok(item_const) = syn::parse2::(input.clone()) { +// parse::eval_const_expr(item_const)? +// }; +// +// let abi_meta = codegen::compile_program(&simf_content)?; +// let generated = codegen::gen_helpers(&simf_content, abi_meta)?; +// +// Ok(generated) +// } + +#[inline] +fn convert_error_to_syn(err: E) -> syn::Error { + syn::Error::new(Span::call_site(), err) +} diff --git a/macros/src/parse.rs b/macros/src/parse.rs new file mode 100644 index 00000000..a0331b4c --- /dev/null +++ b/macros/src/parse.rs @@ -0,0 +1,197 @@ +use crate::convert_error_to_syn; +use proc_macro2::Span; +use std::fs::File; +use std::io; +use std::io::{ErrorKind, Read}; +use std::path::PathBuf; +use syn::spanned::Spanned; +use syn::{Expr, ExprLit, Lit}; + +pub struct SimfContent { + pub content: String, + pub contract_name: String, +} + +pub fn eval_path_expr(expr: Expr) -> syn::Result { + let span = expr.span(); + let str_literal = match expr { + Expr::Lit(ExprLit { + lit: Lit::Str(s), .. + }) => Ok(s.value()), + _ => Err(syn::Error::new(expr.span(), "Expected string literal")), + }?; + + // return Err(syn::Error::new(span, format!("error, '{}', '{:?}'", span.file(), span.local_file()))); + let path = validate_path(span.file(), str_literal)?; + extract_content_from_path(&path).map_err(|e| convert_error_to_syn(e)) +} + +// TODO: come up with an idea of how to parse constant values and evaluate constant values that are passed inside +// pub const OPTION_SOURCE: &str = include_str!("source_simf/options.simf"); +// include_simf_source!(OPTION_SOURCE); + +/// Prepares a contract name for use as a Rust module/identifier. +/// +/// Converts the input to a valid lowercase Rust identifier by: +/// - Trimming whitespace +/// - Converting to lowercase +/// - Replacing invalid characters with underscores +/// - Ensuring it starts with a letter or underscore (not a digit) +/// - Validating it's not a reserved keyword +/// +/// # Returns +/// A valid lowercase Rust identifier, or an error if the name cannot be normalized +/// +/// # Examples +/// - `"MyContract"` → `"mycontract"` +/// - `"My-Contract-V2"` → `"my_contract_v2"` +/// - `"123Invalid"` → Error (starts with digit) +/// - `"valid_name"` → `"valid_name"` +pub fn prepare_contract_name(name: &str) -> io::Result { + let trimmed = name.trim_matches(|c: char| c.is_whitespace()); + if trimmed.is_empty() { + return Err(io::Error::new( + io::ErrorKind::Other, + "Contract name cannot be empty", + )); + } + + let mut result = trimmed.to_lowercase(); + + result = result + .chars() + .map(|c| { + if c.is_alphanumeric() || c == '_' { + c + } else { + '_' + } + }) + .collect(); + + while result.contains("__") { + result = result.replace("__", "_"); + } + + result = result.trim_matches('_').to_string(); + + if result.chars().next().map_or(false, |c| c.is_ascii_digit()) { + result = format!("_{}", result); + } + + if is_reserved_keyword(&result) { + return Err(io::Error::new( + io::ErrorKind::Other, + format!("Contract name '{}' is a reserved Rust keyword", result), + )); + } + + if !is_valid_rust_identifier(&result) { + return Err(io::Error::new( + io::ErrorKind::Other, + format!("Contract name '{}' is not a valid Rust identifier", result), + )); + } + + Ok(result) +} + +/// Checks if a string is a valid Rust identifier +#[inline] +fn is_valid_rust_identifier(s: &str) -> bool { + if s.is_empty() { + return false; + } + + let first = s.chars().next().unwrap(); + // First char must be letter or underscore + if !first.is_alphabetic() && first != '_' { + return false; + } + + s.chars().all(|c| c.is_alphanumeric() || c == '_') +} + +/// Checks if a string is a Rust reserved keyword (only checks keywords, not format) +/// +/// This function validates against Rust's actual reserved keywords. +/// Valid identifiers like "hello" will return false (not a keyword).#[inline] +fn is_reserved_keyword(s: &str) -> bool { + syn::parse_str::(s).is_err() +} + +#[inline] +fn validate_path(local_file: String, literal: String) -> syn::Result { + let mut path = PathBuf::from(literal.clone()); + + if !path.is_absolute() { + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").map_err(|_| { + syn::Error::new( + proc_macro2::Span::call_site(), + "CARGO_MANIFEST_DIR not set - macro must be used within a Cargo workspace", + ) + })?; + + let mut path_local = PathBuf::from(manifest_dir); + path_local.push(literal); + + // let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").map_err(|_| { + // syn::Error::new( + // proc_macro2::Span::call_site(), + // "CARGO_MANIFEST_DIR not set - macro must be used within a Cargo workspace", + // ) + // })?; + // let mut path_local = PathBuf::from(manifest_dir); + // path_local.push(local_file.clone()); + // if let Some(x) = path_local.parent() { + // path_local = PathBuf::from(x); + // } + // + // path_local.push(path); + // return Err(syn::Error::new( + // Span::call_site(), + // format!( + // "path_local: {:?}, local_file: {}, literal: {}", + // path_local, local_file, literal + // ), + // )); + + path = path_local; + } + + if is_not_a_file(&path) { + return Err(convert_error_to_syn(format!( + "File not found, look path: '{:?}', is file: '{}', canonical: '{:?}'", + path, + path.is_file(), + path.canonicalize() + ))); + } + Ok(path) +} + +fn extract_content_from_path(path: &PathBuf) -> std::io::Result { + let contract_name = { + let name = path + .file_prefix() + .ok_or(io::Error::new( + ErrorKind::Other, + format!("No file prefix in file: '{:?}'", path), + ))? + .to_string_lossy(); + prepare_contract_name(name.as_ref())? + }; + + let mut content = String::new(); + let mut x = File::open(path)?; + x.read_to_string(&mut content)?; + Ok(SimfContent { + content, + contract_name, + }) +} + +#[inline] +fn is_not_a_file(path: &PathBuf) -> bool { + !path.is_file() +} diff --git a/src/lib.rs b/src/lib.rs index 702089a5..e26473c9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -89,6 +89,14 @@ impl TemplateProgram { debug_symbols: self.simfony.debug_symbols(self.file.as_ref()), simplicity: commit, witness_types: self.simfony.witness_types().shallow_clone(), + parameter_types: self.simfony.parameters().shallow_clone(), + }) + } + + pub fn generate_abi_meta(&self) -> Result { + Ok(AbiMeta { + witness_types: self.simfony.witness_types().shallow_clone(), + param_types: self.parameters().shallow_clone(), }) } } @@ -99,6 +107,7 @@ pub struct CompiledProgram { simplicity: Arc>, witness_types: WitnessTypes, debug_symbols: DebugSymbols, + parameter_types: Parameters, } impl CompiledProgram { @@ -162,6 +171,19 @@ impl CompiledProgram { debug_symbols: self.debug_symbols.clone(), }) } + + pub fn generate_abi_meta(&self) -> Result { + Ok(AbiMeta { + witness_types: self.witness_types.shallow_clone(), + param_types: self.parameter_types.shallow_clone(), + }) + } +} + +#[derive(Clone, Debug)] +pub struct AbiMeta { + pub witness_types: WitnessTypes, + pub param_types: Parameters, } /// A SimplicityHL program, compiled to Simplicity and satisfied with witness data. diff --git a/src/main.rs b/src/main.rs index 1135a311..4aa1d558 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ use base64::display::Base64Display; use base64::engine::general_purpose::STANDARD; use clap::{Arg, ArgAction, Command}; -use simplicityhl::{Arguments, CompiledProgram}; +use simplicityhl::{AbiMeta, Arguments, CompiledProgram}; use std::{env, fmt}; #[cfg_attr(feature = "serde", derive(serde::Serialize))] @@ -12,6 +12,8 @@ struct Output { program: String, /// Simplicity witness result, base64 encoded, if the .wit file was provided. witness: Option, + /// Simplicity program ABI metadata to the program which the user provides. + abi_meta: Option, } impl fmt::Display for Output { @@ -20,6 +22,9 @@ impl fmt::Display for Output { if let Some(witness) = &self.witness { writeln!(f, "Witness:\n{}", witness)?; } + if let Some(witness) = &self.abi_meta { + writeln!(f, "ABI meta:\n{:?}", witness)?; + } Ok(()) } } @@ -59,6 +64,12 @@ fn main() -> Result<(), Box> { .action(ArgAction::SetTrue) .help("Output in JSON"), ) + .arg( + Arg::new("abi") + .long("abi") + .action(ArgAction::SetTrue) + .help("Additional ABI .simf contract types"), + ) }; let matches = command.get_matches(); @@ -68,6 +79,7 @@ fn main() -> Result<(), Box> { let prog_text = std::fs::read_to_string(prog_path).map_err(|e| e.to_string())?; let include_debug_symbols = matches.get_flag("debug"); let output_json = matches.get_flag("json"); + let abi_param = matches.get_flag("abi"); let compiled = CompiledProgram::new(prog_text, Arguments::default(), include_debug_symbols)?; @@ -103,9 +115,16 @@ fn main() -> Result<(), Box> { } }; + let abi_opt = if abi_param { + Some(compiled.generate_abi_meta()?) + } else { + None + }; + let output = Output { program: Base64Display::new(&program_bytes, &STANDARD).to_string(), witness: witness_bytes.map(|bytes| Base64Display::new(&bytes, &STANDARD).to_string()), + abi_meta: abi_opt, }; if output_json { diff --git a/src/serde.rs b/src/serde.rs index 2a12550b..d79b72cd 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -1,13 +1,13 @@ use std::collections::HashMap; use std::fmt; -use serde::{de, ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer}; - use crate::parse::ParseFromStr; use crate::str::WitnessName; use crate::types::ResolvedType; use crate::value::Value; use crate::witness::{Arguments, WitnessValues}; +use crate::AbiMeta; +use serde::{de, ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer}; struct WitnessMapVisitor; @@ -43,6 +43,32 @@ impl<'de> Deserialize<'de> for WitnessValues { } } +impl Serialize for AbiMeta { + fn serialize(&self, serializer: S) -> Result + where + S: ::serde::Serializer, + { + use ::serde::ser::SerializeStruct; + use std::collections::HashMap; + + let mut state = serializer.serialize_struct("AbiMeta", 2)?; + + let witness_types_converted = self + .witness_types + .iter() + .map(|(w_name, w_type)| (w_name.to_string(), w_type.to_string())) + .collect::>(); + state.serialize_field("witness_types", &witness_types_converted)?; + let witness_types_converted = self + .param_types + .iter() + .map(|(w_name, w_type)| (w_name.to_string(), w_type.to_string())) + .collect::>(); + state.serialize_field("parameter_types", &witness_types_converted)?; + state.end() + } +} + impl<'de> Deserialize<'de> for Arguments { fn deserialize(deserializer: D) -> Result where