From 975817b7b9055b48ccfc750f45d7259860c140f0 Mon Sep 17 00:00:00 2001 From: Sdoba16 Date: Mon, 30 Mar 2026 14:02:33 +0300 Subject: [PATCH] Add versioning to simplicity contracts. Added tests and tooling for examples to have proper version --- .github/workflows/check-versions.yml | 33 ++ Cargo.lock | 411 +++++++++++------- Cargo.toml | 1 + examples/array_fold.simf | 2 + examples/array_fold_2n.simf | 2 + examples/cat.simf | 2 + examples/ctv.simf | 2 + examples/escrow_with_delay.simf | 2 + examples/hash_loop.simf | 2 + examples/hodl_vault.simf | 2 + examples/htlc.simf | 2 + examples/last_will.simf | 2 + examples/non_interactive_fee_bump.simf | 1 + examples/p2ms.simf | 2 + examples/p2pk.simf | 2 + examples/p2pkh.simf | 2 + examples/pattern_matching.simf | 2 + examples/presigned_vault.simf | 2 + examples/reveal_collision.simf | 2 + examples/reveal_fix_point.simf | 2 + examples/sighash_all_anyonecanpay.simf | 2 + examples/sighash_all_anyprevout.simf | 2 + examples/sighash_all_anyprevoutanyscript.simf | 2 + examples/sighash_none.simf | 2 + examples/sighash_single.simf | 2 + examples/transfer_with_timeout.simf | 2 + src/ast.rs | 70 +++ src/error.rs | 132 ++++++ src/lexer.rs | 67 +++ src/lib.rs | 99 ++++- src/parse.rs | 44 +- src/tracker.rs | 21 +- src/witness.rs | 26 +- update_examples.py | 32 ++ 34 files changed, 803 insertions(+), 178 deletions(-) create mode 100644 .github/workflows/check-versions.yml create mode 100644 update_examples.py diff --git a/.github/workflows/check-versions.yml b/.github/workflows/check-versions.yml new file mode 100644 index 00000000..63dd4a22 --- /dev/null +++ b/.github/workflows/check-versions.yml @@ -0,0 +1,33 @@ +name: Check Example Versions + +on: + push: + branches: [ "main", "master" ] + pull_request: + branches: [ "main", "master" ] + +jobs: + verify-pragmas: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Extract version from Cargo.toml + id: get_version + run: | + # Grabs the first 'version = "X.Y.Z"' it sees in Cargo.toml + VERSION=$(awk -F '\"' '/^version/ {print $2; exit}' Cargo.toml) + echo "COMPILER_VERSION=$VERSION" >> $GITHUB_ENV + echo "Detected Cargo version: $VERSION" + + - name: Run the example updater script + run: python3 update_examples.py ${{ env.COMPILER_VERSION }} + + - name: Fail if examples are out of date + run: | + # Only check the examples directory for changes! + if ! git diff --exit-code examples/; then + echo "::error::The pragma versions in the examples/ folder are incorrect or missing!" + exit 1 + fi \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index ebf72651..397a0ede 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "aho-corasick" -version = "1.0.5" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -19,9 +19,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "anstream" -version = "0.6.18" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -34,53 +34,53 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.7" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", - "once_cell", - "windows-sys", + "once_cell_polyfill", + "windows-sys 0.61.2", ] [[package]] name = "ar_archive_writer" -version = "0.2.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c269894b6fe5e9d7ada0cf69b5bf847ff35bc25fc271f08e1d080fce80339a" +checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b" dependencies = [ "object", ] [[package]] name = "arbitrary" -version = "1.1.3" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a7924531f38b1970ff630f03eb20a2fde69db5c590c93b0f3482e95dcc5fd60" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" dependencies = [ "derive_arbitrary", ] @@ -103,9 +103,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.21.3" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64" @@ -115,15 +115,15 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bech32" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" +checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" [[package]] name = "bitcoin" -version = "0.32.3" +version = "0.32.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0032b0e8ead7074cda7fc4f034409607e3f03a6f71d66ade8a307f79b4d99e73" +checksum = "1e499f9fc0407f50fe98af744ab44fa67d409f76b6772e1689ec8485eb0c0f66" dependencies = [ "base58ck", "bech32", @@ -144,9 +144,9 @@ checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2" [[package]] name = "bitcoin-io" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "340e09e8399c7bd8912f495af6aa58bea0c9214773417ffaa8f6460f93aaee56" +checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" [[package]] name = "bitcoin-private" @@ -165,9 +165,9 @@ dependencies = [ [[package]] name = "bitcoin_hashes" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" dependencies = [ "bitcoin-io", "hex-conservative", @@ -175,21 +175,21 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.55" +version = "1.2.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" dependencies = [ "find-msvc-tools", "jobserver", @@ -199,9 +199,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chumsky" @@ -210,7 +210,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acc17a6284abccac6e50db35c1cee87f605474a72939b959a3a67d9371800efd" dependencies = [ "hashbrown", - "regex-automata", + "regex-automata 0.3.9", "serde", "stacker", "unicode-ident", @@ -219,18 +219,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.37" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.37" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", @@ -240,9 +240,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "codegen" @@ -256,32 +256,32 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "derive_arbitrary" -version = "1.1.3" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9a577516173adb681466d517d39bd468293bc2c2a16439375ef0f35bba45f3d" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] name = "either" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "elements" -version = "0.25.0" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "739a0201c8b2d1e35e6509872ddb8250dd37b38d2a462b9cea05988bf9630196" +checksum = "81b2569d3495bfdfce36c504fd4d78752ff4a7699f8a33e6f3ee523bddf9f6ad" dependencies = [ "bech32", "bitcoin", @@ -308,9 +308,9 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "js-sys", @@ -319,6 +319,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + [[package]] name = "ghost-cell" version = "0.2.6" @@ -338,9 +350,9 @@ dependencies = [ [[package]] name = "hex-conservative" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" dependencies = [ "arrayvec", ] @@ -353,9 +365,9 @@ checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" @@ -368,61 +380,57 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jobserver" -version = "0.1.32" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ + "getrandom 0.3.4", "libc", ] [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "cc4c90f45aa2e6eacbe8645f77fdea542ac97a494bcd117a67df9ff4d611f995" dependencies = [ + "once_cell", "wasm-bindgen", ] [[package]] name = "libc" -version = "0.2.180" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libfuzzer-sys" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404" +checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d" dependencies = [ "arbitrary", "cc", ] -[[package]] -name = "log" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" - [[package]] name = "memchr" -version = "2.6.3" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "miniscript" -version = "12.3.1" +version = "12.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82911d2fb527bb9aacd2446d2f517aff3f8e3846ace1b3c24258b61ea3cce2bc" +checksum = "487906208f38448e186e3deb02f2b8ef046a9078b0de00bdb28bf4fb9b76951c" dependencies = [ "bech32", "bitcoin", @@ -430,39 +438,48 @@ dependencies = [ [[package]] name = "object" -version = "0.32.2" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] [[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 = "psm" -version = "0.1.28" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d11f2fedc3b7dafdc2851bc52f277377c5473d378859be234bc7ebb593144d01" +checksum = "3852766467df634d74f0b2d7819bf8dc483a0eb2e3b0f50f756f9cfe8b0d18d8" dependencies = [ "ar_archive_writer", "cc", @@ -470,13 +487,19 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rand" version = "0.8.5" @@ -504,30 +527,41 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.17", ] [[package]] name = "regex" -version = "1.9.5" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.14", + "regex-syntax 0.8.10", ] [[package]] name = "regex-automata" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" +checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.7.5", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.10", ] [[package]] @@ -537,10 +571,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] -name = "ryu" -version = "1.0.15" +name = "regex-syntax" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "santiago" @@ -593,35 +633,53 @@ dependencies = [ "secp256k1-sys", ] +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" -version = "1.0.188" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +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.188" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn", ] [[package]] name = "serde_json" -version = "1.0.105" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", - "ryu", + "memchr", "serde", + "serde_core", + "zmij", ] [[package]] @@ -640,7 +698,7 @@ dependencies = [ "bitcoin_hashes", "byteorder", "elements", - "getrandom", + "getrandom 0.2.17", "ghost-cell", "hex-conservative", "miniscript", @@ -650,9 +708,9 @@ dependencies = [ [[package]] name = "simplicity-sys" -version = "0.6.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875630d128f19818161cefe0a3d910b6aae921d8246711db574a689cb2c11747" +checksum = "e3401ee7331f183a5458c0f5a4b3d5d00bde0fd12e2e03728c537df34efae289" dependencies = [ "bitcoin_hashes", "cc", @@ -663,13 +721,14 @@ name = "simplicityhl" version = "0.5.0-rc.0" dependencies = [ "arbitrary", - "base64 0.21.3", + "base64 0.21.7", "chumsky", "clap", "either", - "getrandom", + "getrandom 0.2.17", "itertools", "miniscript", + "semver", "serde", "serde_json", "simplicity-lang", @@ -689,15 +748,15 @@ dependencies = [ [[package]] name = "stacker" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1f8b29fb42aafcea4edeeb6b2f2d7ecd0d969c48b4cf0d2e64aafc471dd6e59" +checksum = "08d74a23609d509411d10e2176dc2a4346e3b4aea2e7b1869f19fdedbc71c013" dependencies = [ "cc", "cfg-if", "libc", "psm", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -708,20 +767,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.31" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -730,15 +778,15 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.11" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] name = "utf8parse" @@ -748,40 +796,37 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasm-bindgen" -version = "0.2.92" +name = "wasip2" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ - "cfg-if", - "wasm-bindgen-macro", + "wit-bindgen", ] [[package]] -name = "wasm-bindgen-backend" -version = "0.2.92" +name = "wasm-bindgen" +version = "0.2.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "6523d69017b7633e396a89c5efab138161ed5aafcbc8d3e5c5a42ae38f50495a" dependencies = [ - "bumpalo", - "log", + "cfg-if", "once_cell", - "proc-macro2", - "quote", - "syn 2.0.31", + "rustversion", + "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "4e3a6c758eb2f701ed3d052ff5737f5bfe6614326ea7f3bbac7156192dc32e67" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -789,22 +834,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "921de2737904886b52bcbb237301552d05969a6f9c40d261eb0533c8b055fedf" dependencies = [ + "bumpalo", "proc-macro2", "quote", - "syn 2.0.31", - "wasm-bindgen-backend", + "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "a93e946af942b58934c604527337bad9ae33ba1d5c6900bbb41c2c07c2364a93" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" @@ -815,6 +869,15 @@ 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" @@ -878,3 +941,35 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 1d8709f0..4e0c3638 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ itertools = "0.13.0" arbitrary = { version = "1", optional = true, features = ["derive"] } clap = "4.5.37" chumsky = "0.11.2" +semver = "1.0.27" [target.wasm32-unknown-unknown.dependencies] getrandom = { version = "0.2", features = ["js"] } diff --git a/examples/array_fold.simf b/examples/array_fold.simf index e671493c..d3e0da46 100644 --- a/examples/array_fold.simf +++ b/examples/array_fold.simf @@ -1,3 +1,5 @@ +#![compiler_version("0.5.0-rc.0")] + fn sum(elt: u32, acc: u32) -> u32 { let (_, acc): (bool, u32) = jet::add_32(elt, acc); acc diff --git a/examples/array_fold_2n.simf b/examples/array_fold_2n.simf index 9d86325c..d78fb8e4 100644 --- a/examples/array_fold_2n.simf +++ b/examples/array_fold_2n.simf @@ -1,5 +1,7 @@ // From https://github.com/BlockstreamResearch/SimplicityHL/issues/153 +#![compiler_version("0.5.0-rc.0")] + fn sum(elt: u32, acc: u32) -> u32 { let (_, acc): (bool, u32) = jet::add_32(elt, acc); acc diff --git a/examples/cat.simf b/examples/cat.simf index f6c5e76c..f430c44b 100644 --- a/examples/cat.simf +++ b/examples/cat.simf @@ -1,3 +1,5 @@ +#![compiler_version("0.5.0-rc.0")] + fn main() { let ab: u16 = <(u8, u8)>::into((0x10, 0x01)); let c: u16 = 0x1001; diff --git a/examples/ctv.simf b/examples/ctv.simf index 3eadb44c..439938a8 100644 --- a/examples/ctv.simf +++ b/examples/ctv.simf @@ -5,6 +5,8 @@ * we require the user to specify all the components of the sighash * that they want to commit. */ +#![compiler_version("0.5.0-rc.0")] + fn main() { let ctx: Ctx8 = jet::sha_256_ctx_8_init(); let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, jet::version()); diff --git a/examples/escrow_with_delay.simf b/examples/escrow_with_delay.simf index 67dd3d3e..5d742c84 100644 --- a/examples/escrow_with_delay.simf +++ b/examples/escrow_with_delay.simf @@ -7,6 +7,8 @@ * * https://docs.ivylang.org/bitcoin/language/ExampleContracts.html#escrowwithdelay */ +#![compiler_version("0.5.0-rc.0")] + fn not(bit: bool) -> bool { ::into(jet::complement_1(::into(bit))) } diff --git a/examples/hash_loop.simf b/examples/hash_loop.simf index f554d894..2c3043c4 100644 --- a/examples/hash_loop.simf +++ b/examples/hash_loop.simf @@ -1,3 +1,5 @@ +#![compiler_version("0.5.0-rc.0")] + // Add counter to streaming hash and finalize when the loop exists fn hash_counter_8(ctx: Ctx8, unused: (), byte: u8) -> Either { let new_ctx: Ctx8 = jet::sha_256_ctx_8_add_1(ctx, byte); diff --git a/examples/hodl_vault.simf b/examples/hodl_vault.simf index fdc29ae5..21591149 100644 --- a/examples/hodl_vault.simf +++ b/examples/hodl_vault.simf @@ -8,6 +8,8 @@ * the use of old data. The transaction is timelocked to the oracle height, * which means that the transaction becomes valid after the oracle height. */ +#![compiler_version("0.5.0-rc.0")] + fn checksig(pk: Pubkey, sig: Signature) { let msg: u256 = jet::sig_all_hash(); jet::bip_0340_verify((pk, msg), sig); diff --git a/examples/htlc.simf b/examples/htlc.simf index 2ee98c5e..55b4ead3 100644 --- a/examples/htlc.simf +++ b/examples/htlc.simf @@ -9,6 +9,8 @@ * * https://docs.ivylang.org/bitcoin/language/ExampleContracts.html#htlc */ +#![compiler_version("0.5.0-rc.0")] + fn sha2(string: u256) -> u256 { let hasher: Ctx8 = jet::sha_256_ctx_8_init(); let hasher: Ctx8 = jet::sha_256_ctx_8_add_32(hasher, string); diff --git a/examples/last_will.simf b/examples/last_will.simf index 01007b07..2e405a26 100644 --- a/examples/last_will.simf +++ b/examples/last_will.simf @@ -5,6 +5,8 @@ * days. The owner has to repeat the covenant when he moves the coins with his * hot key. The owner can break out of the covenant with his cold key. */ +#![compiler_version("0.5.0-rc.0")] + fn checksig(pk: Pubkey, sig: Signature) { let msg: u256 = jet::sig_all_hash(); jet::bip_0340_verify((pk, msg), sig); diff --git a/examples/non_interactive_fee_bump.simf b/examples/non_interactive_fee_bump.simf index 5188633c..dcea090a 100644 --- a/examples/non_interactive_fee_bump.simf +++ b/examples/non_interactive_fee_bump.simf @@ -11,6 +11,7 @@ * This enables miners to maximize their fees from transactions without needing external fee bumping services like * sponsors, Child-Pays-For-Parent (CPFP), or anchor outputs, simplifying fee management for transaction inclusion. */ +#![compiler_version("0.5.0-rc.0")] // This function computes a signature hash for transactions that allows non-interactive fee bumping. // It omits certain fields from the transaction that can be modified by anyone, diff --git a/examples/p2ms.simf b/examples/p2ms.simf index d95c1a0e..0ceb9f75 100644 --- a/examples/p2ms.simf +++ b/examples/p2ms.simf @@ -6,6 +6,8 @@ * * https://docs.ivylang.org/bitcoin/language/ExampleContracts.html#lockwithmultisig */ +#![compiler_version("0.5.0-rc.0")] + fn not(bit: bool) -> bool { ::into(jet::complement_1(::into(bit))) } diff --git a/examples/p2pk.simf b/examples/p2pk.simf index 7e40dd55..8fc38035 100644 --- a/examples/p2pk.simf +++ b/examples/p2pk.simf @@ -5,6 +5,8 @@ * * https://docs.ivylang.org/bitcoin/language/ExampleContracts.html#lockwithpublickey */ +#![compiler_version("0.5.0-rc.0")] + fn main() { jet::bip_0340_verify((param::ALICE_PUBLIC_KEY, jet::sig_all_hash()), witness::ALICE_SIGNATURE) } diff --git a/examples/p2pkh.simf b/examples/p2pkh.simf index 42f527a9..0f49b3d5 100644 --- a/examples/p2pkh.simf +++ b/examples/p2pkh.simf @@ -6,6 +6,8 @@ * * https://docs.ivylang.org/bitcoin/language/ExampleContracts.html#lockwithpublickeyhash */ +#![compiler_version("0.5.0-rc.0")] + fn sha2(string: u256) -> u256 { let hasher: Ctx8 = jet::sha_256_ctx_8_init(); let hasher: Ctx8 = jet::sha_256_ctx_8_add_32(hasher, string); diff --git a/examples/pattern_matching.simf b/examples/pattern_matching.simf index 706996fa..eefe77a5 100644 --- a/examples/pattern_matching.simf +++ b/examples/pattern_matching.simf @@ -1,3 +1,5 @@ +#![compiler_version("0.5.0-rc.0")] + fn main() { let complex_pattern: Either<(u32, u32, (u1, u1)), [u1; 8]> = Left((32, 3, (0, 1))); diff --git a/examples/presigned_vault.simf b/examples/presigned_vault.simf index acd2ee28..138448f2 100644 --- a/examples/presigned_vault.simf +++ b/examples/presigned_vault.simf @@ -16,6 +16,8 @@ * * https://docs.ivylang.org/bitcoin/language/ExampleContracts.html#vaultspend */ +#![compiler_version("0.5.0-rc.0")] + fn checksig(pk: Pubkey, sig: Signature) { let msg: u256 = jet::sig_all_hash(); jet::bip_0340_verify((pk, msg), sig); diff --git a/examples/reveal_collision.simf b/examples/reveal_collision.simf index bec8b1f3..cff6c587 100644 --- a/examples/reveal_collision.simf +++ b/examples/reveal_collision.simf @@ -8,6 +8,8 @@ * * https://docs.ivylang.org/bitcoin/language/ExampleContracts.html#revealcollision */ +#![compiler_version("0.5.0-rc.0")] + fn not(bit: bool) -> bool { ::into(jet::complement_1(::into(bit))) } diff --git a/examples/reveal_fix_point.simf b/examples/reveal_fix_point.simf index 02f5da8d..045646b2 100644 --- a/examples/reveal_fix_point.simf +++ b/examples/reveal_fix_point.simf @@ -8,6 +8,8 @@ * * https://docs.ivylang.org/bitcoin/language/ExampleContracts.html#revealfixedpoint */ +#![compiler_version("0.5.0-rc.0")] + fn sha2(string: u256) -> u256 { let hasher: Ctx8 = jet::sha_256_ctx_8_init(); let hasher: Ctx8 = jet::sha_256_ctx_8_add_32(hasher, string); diff --git a/examples/sighash_all_anyonecanpay.simf b/examples/sighash_all_anyonecanpay.simf index ed1f6e2f..e6531b65 100644 --- a/examples/sighash_all_anyonecanpay.simf +++ b/examples/sighash_all_anyonecanpay.simf @@ -2,6 +2,8 @@ * This program verifies a Schnorr signature based on * SIGHASH_ALL | SIGHASH_ANYONECANPAY. */ +#![compiler_version("0.5.0-rc.0")] + fn main() { let ctx: Ctx8 = jet::sha_256_ctx_8_init(); // Blockchain diff --git a/examples/sighash_all_anyprevout.simf b/examples/sighash_all_anyprevout.simf index e5448792..c8a87231 100644 --- a/examples/sighash_all_anyprevout.simf +++ b/examples/sighash_all_anyprevout.simf @@ -2,6 +2,8 @@ * This program verifies a Schnorr signature based on * SIGHASH_ALL | SIGHASH_ANYPREVOUT. */ +#![compiler_version("0.5.0-rc.0")] + fn main() { let ctx: Ctx8 = jet::sha_256_ctx_8_init(); // Blockchain diff --git a/examples/sighash_all_anyprevoutanyscript.simf b/examples/sighash_all_anyprevoutanyscript.simf index fb7ab0a7..3116152e 100644 --- a/examples/sighash_all_anyprevoutanyscript.simf +++ b/examples/sighash_all_anyprevoutanyscript.simf @@ -2,6 +2,8 @@ * This program verifies a Schnorr signature based on * SIGHASH_ALL | SIGHASH_ANYPREVOUTANYSCRIPT. */ +#![compiler_version("0.5.0-rc.0")] + fn main() { let ctx: Ctx8 = jet::sha_256_ctx_8_init(); // Blockchain diff --git a/examples/sighash_none.simf b/examples/sighash_none.simf index 3ae78c45..8ac01b4d 100644 --- a/examples/sighash_none.simf +++ b/examples/sighash_none.simf @@ -2,6 +2,8 @@ * This program verifies a Schnorr signature based on * SIGHASH_NONE. */ +#![compiler_version("0.5.0-rc.0")] + fn main() { let ctx: Ctx8 = jet::sha_256_ctx_8_init(); // Blockchain diff --git a/examples/sighash_single.simf b/examples/sighash_single.simf index db06aa0a..e91f3ed4 100644 --- a/examples/sighash_single.simf +++ b/examples/sighash_single.simf @@ -2,6 +2,8 @@ * This program verifies a Schnorr signature based on * SIGHASH_SINGLE. */ +#![compiler_version("0.5.0-rc.0")] + fn main() { let ctx: Ctx8 = jet::sha_256_ctx_8_init(); // Blockchain diff --git a/examples/transfer_with_timeout.simf b/examples/transfer_with_timeout.simf index 91f6f69e..88f6cd18 100644 --- a/examples/transfer_with_timeout.simf +++ b/examples/transfer_with_timeout.simf @@ -12,6 +12,8 @@ * * https://docs.ivylang.org/bitcoin/language/ExampleContracts.html#transferwithtimeout */ +#![compiler_version("0.5.0-rc.0")] + fn checksig(pk: Pubkey, sig: Signature) { let msg: u256 = jet::sig_all_hash(); jet::bip_0340_verify((pk, msg), sig); diff --git a/src/ast.rs b/src/ast.rs index 20dd2a70..9d11c203 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -719,6 +719,76 @@ trait AbstractSyntaxTree: Sized { impl Program { pub fn analyze(from: &parse::Program) -> Result { + let compiler_version = env!("CARGO_PKG_VERSION"); + + match (from.version(), from.version_span()) { + (Some(v), Some(span)) => { + // Parse the compiler's exact version + let compiler_semver = semver::Version::parse(compiler_version) + .expect("CARGO_PKG_VERSION is always valid semver"); + + // Parse the version requirement (e.g., "^0.5.0" or ">=0.4.0") + match semver::VersionReq::parse(v) { + Ok(req) if req.matches(&compiler_semver) => {} + Ok(_) => { + let req_str = v.to_string(); + let comp_str = compiler_version.to_string(); + + if req_str.starts_with('>') + || req_str.starts_with(">=") + || req_str.starts_with('^') + { + return Err(Error::SimcVersionCompilerTooOld { + required: req_str, + current: comp_str, + }) + .with_span(span); + } else if req_str.starts_with('<') || req_str.starts_with("<=") { + return Err(Error::SimcVersionContractTooOld { + required: req_str, + current: comp_str, + }) + .with_span(span); + } else if req_str.starts_with('=') + || req_str.chars().next().is_some_and(|c| c.is_ascii_digit()) + { + return Err(Error::SimcVersionExactMismatch { + required: req_str, + current: comp_str, + }) + .with_span(span); + } + return Err(Error::SimcVersionIncompatible { + required: req_str, + current: comp_str, + }) + .with_span(span); + } + Err(e) => { + return Err(Error::InvalidSimcVersionSyntax(e.to_string())).with_span(span); + } + } + } + (None, _) | (_, None) => { + // Find where the first actual code item starts (ignoring comments) + let first_item_span = + from.items() + .first() + .map_or(Span::new(0, 0), |item| match item { + parse::Item::TypeAlias(t) => *t.as_ref(), + parse::Item::Function(f) => *f.as_ref(), + parse::Item::Module => Span::new(0, 0), + }); + + let insertion_point = Span::new(first_item_span.start, first_item_span.start); + + return Err(Error::MissingSimcVersion { + compiler: compiler_version.to_string(), + }) + .with_span(insertion_point); + } + } + let unit = ResolvedType::unit(); let mut scope = Scope::default(); let items = from diff --git a/src/error.rs b/src/error.rs index b4d5a4b7..1c3cbce4 100644 --- a/src/error.rs +++ b/src/error.rs @@ -406,6 +406,27 @@ impl fmt::Display for ErrorCollector { /// Records _what_ happened but not where. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub enum Error { + MissingSimcVersion { + compiler: String, + }, + InvalidSimcVersionSyntax(String), + SimcVersionExactMismatch { + required: String, + current: String, + }, + SimcVersionCompilerTooOld { + required: String, + current: String, + }, + SimcVersionContractTooOld { + required: String, + current: String, + }, + SimcVersionIncompatible { + required: String, + current: String, + }, + ArraySizeNonZero(usize), ListBoundPow2(usize), BitStringPow2(usize), @@ -453,6 +474,13 @@ pub enum Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + Error::MissingSimcVersion { compiler } => write!(f, "Missing compiler version: Contract must declare a version, e.g., `#![compiler_version(\"{}\")]`", compiler), + Error::InvalidSimcVersionSyntax(err) => write!(f, "Invalid version syntax: {}", err), + Error::SimcVersionExactMismatch { required, current } => write!(f, "Exact version mismatch: Contract requires {}, but compiler is {}", required, current), + Error::SimcVersionCompilerTooOld { required, current } => write!(f, "Compiler too old: Contract requires {}, but compiler is {}. Please upgrade.", required, current), + Error::SimcVersionContractTooOld { required, current } => write!(f, "Contract too old: Contract requires {}, which is not supported by compiler {}.", required, current), + Error::SimcVersionIncompatible { required, current } => write!(f, "Incompatible version: Contract requirement '{}' is not satisfied by compiler {}.", required, current), + Error::ArraySizeNonZero(size) => write!( f, "Expected a non-negative integer as array size, found {size}" @@ -792,4 +820,108 @@ let x: u32 = Left( assert_eq!(&expected[1..], &error.to_string()); } + + #[test] + fn display_compiler_version_missing() { + let file = "fn main() {}"; + let error = Error::MissingSimcVersion { + compiler: "0.5.0".to_string(), + } + .with_span(Span::new(0, 0)) + .with_file(Arc::from(file)); + + let expected = r#" + | +1 | fn main() {} + | ^ Missing compiler version: Contract must declare a version, e.g., `#![compiler_version("0.5.0")]`"#; + + assert_eq!(&expected[1..], &error.to_string()); + } + + #[test] + fn display_compiler_version_invalid_syntax() { + let file = "#![compiler_version(\"abc\")]\nfn main() {}"; + let error = Error::InvalidSimcVersionSyntax("unexpected character 'a'".to_string()) + .with_span(Span::new(0, 27)) + .with_file(Arc::from(file)); + + let expected = r#" + | +1 | #![compiler_version("abc")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid version syntax: unexpected character 'a'"#; + + assert_eq!(&expected[1..], &error.to_string()); + } + + #[test] + fn display_compiler_version_exact_mismatch() { + let file = "#![compiler_version(\"= 0.4.0\")]\nfn main() {}"; + let error = Error::SimcVersionExactMismatch { + required: "= 0.4.0".to_string(), + current: "0.5.0".to_string(), + } + .with_span(Span::new(0, 31)) + .with_file(Arc::from(file)); + + let expected = r#" + | +1 | #![compiler_version("= 0.4.0")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Exact version mismatch: Contract requires = 0.4.0, but compiler is 0.5.0"#; + + assert_eq!(&expected[1..], &error.to_string()); + } + + #[test] + fn display_compiler_version_too_old() { + let file = "#![compiler_version(\">= 0.6.0\")]\nfn main() {}"; + let error = Error::SimcVersionCompilerTooOld { + required: ">= 0.6.0".to_string(), + current: "0.5.0".to_string(), + } + .with_span(Span::new(0, 32)) + .with_file(Arc::from(file)); + + let expected = r#" + | +1 | #![compiler_version(">= 0.6.0")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Compiler too old: Contract requires >= 0.6.0, but compiler is 0.5.0. Please upgrade."#; + + assert_eq!(&expected[1..], &error.to_string()); + } + + #[test] + fn display_compiler_version_contract_too_old() { + let file = "#![compiler_version(\"< 0.4.0\")]\nfn main() {}"; + let error = Error::SimcVersionContractTooOld { + required: "< 0.4.0".to_string(), + current: "0.5.0".to_string(), + } + .with_span(Span::new(0, 31)) + .with_file(Arc::from(file)); + + let expected = r#" + | +1 | #![compiler_version("< 0.4.0")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Contract too old: Contract requires < 0.4.0, which is not supported by compiler 0.5.0."#; + + assert_eq!(&expected[1..], &error.to_string()); + } + + #[test] + fn display_compiler_version_incompatible() { + let file = "#![compiler_version(\"~0.6.0\")]\nfn main() {}"; + let error = Error::SimcVersionIncompatible { + required: "~0.6.0".to_string(), + current: "0.5.0".to_string(), + } + .with_span(Span::new(0, 30)) + .with_file(Arc::from(file)); + + let expected = r#" + | +1 | #![compiler_version("~0.6.0")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Incompatible version: Contract requirement '~0.6.0' is not satisfied by compiler 0.5.0."#; + + assert_eq!(&expected[1..], &error.to_string()); + } } diff --git a/src/lexer.rs b/src/lexer.rs index f4326320..0b006678 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -36,11 +36,14 @@ pub enum Token<'src> { RBrace, LAngle, RAngle, + Hash, + Bang, // Number literals DecLiteral(Decimal), HexLiteral(Hexadecimal), BinLiteral(Binary), + StringLiteral(&'src str), // Boolean literal Bool(bool), @@ -88,10 +91,13 @@ impl<'src> fmt::Display for Token<'src> { Token::RBrace => write!(f, "}}"), Token::LAngle => write!(f, "<"), Token::RAngle => write!(f, ">"), + Token::Hash => write!(f, "#"), + Token::Bang => write!(f, "!"), Token::DecLiteral(s) => write!(f, "{}", s), Token::HexLiteral(s) => write!(f, "0x{}", s), Token::BinLiteral(s) => write!(f, "0b{}", s), + Token::StringLiteral(s) => write!(f, "\"{}\"", s), Token::Ident(s) => write!(f, "{}", s), @@ -149,6 +155,11 @@ pub fn lexer<'src>( _ => Token::Ident(s), }); + let string_literal = just('"') + .ignore_then(any().and_is(just('"').not()).repeated().to_slice()) + .then_ignore(just('"')) + .map(Token::StringLiteral); + let jet = just("jet") .ignore_then(just("::")) .ignore_then(text::ident()) @@ -178,6 +189,8 @@ pub fn lexer<'src>( just("}").to(Token::RBrace), just("<").to(Token::LAngle), just(">").to(Token::RAngle), + just("#").to(Token::Hash), + just("!").to(Token::Bang), )); let comment = just("//") @@ -204,6 +217,7 @@ pub fn lexer<'src>( param, macros, keyword, + string_literal, hex, bin, num, @@ -358,4 +372,57 @@ mod tests { assert!(lex_errs.is_empty()); } + + #[test] + fn test_lex_compiler_version_exact() { + let input = "#![compiler_version(\"0.5.0\")]"; + let (tokens, errors) = lex(input); + + assert!(errors.is_empty(), "Expected no errors, found: {:?}", errors); + + let tokens = tokens.unwrap(); + assert_eq!(tokens[0], Token::Hash); + assert_eq!(tokens[1], Token::Bang); + assert_eq!(tokens[2], Token::LBracket); + assert_eq!(tokens[3], Token::Ident("compiler_version")); + assert_eq!(tokens[4], Token::LParen); + assert_eq!(tokens[5], Token::StringLiteral("0.5.0")); + assert_eq!(tokens[6], Token::RParen); + assert_eq!(tokens[7], Token::RBracket); + } + + #[test] + fn test_lex_compiler_version_complex_string() { + // Proves the lexer captures all symbols (>, =, <, ^, ~) inside the quotes as a single string token + let input = "#![compiler_version(\">= 0.0.1, <= 1.0.0, ^0.5.0, ~0.6.0\")]"; + let (tokens, errors) = lex(input); + + assert!(errors.is_empty(), "Expected no errors, found: {:?}", errors); + + let tokens = tokens.unwrap(); + assert_eq!( + tokens[5], + Token::StringLiteral(">= 0.0.1, <= 1.0.0, ^0.5.0, ~0.6.0") + ); + } + + #[test] + fn test_lex_identifier_boundary() { + // Ensures the lexer doesn't accidentally break if a user creates a variable named `compiler_version` + let input = "let compiler_version = 1;"; + let (tokens, errors) = lex(input); + + assert!(errors.is_empty(), "Expected no errors, found: {:?}", errors); + + let tokens = tokens.unwrap(); + assert_eq!(tokens[0], Token::Let); + assert_eq!(tokens[1], Token::Ident("compiler_version")); + assert_eq!(tokens[2], Token::Eq); + + assert_eq!( + tokens[3], + Token::DecLiteral(crate::str::Decimal::from_str_unchecked("1")) + ); + assert_eq!(tokens[4], Token::Semi); + } } diff --git a/src/lib.rs b/src/lib.rs index 6e9bca76..94e89e67 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -323,7 +323,19 @@ pub(crate) mod tests { } pub fn template_text(program_text: Cow) -> Self { - let program = match TemplateProgram::new(program_text.as_ref()) { + let clean_text = program_text + .lines() + .filter(|line| !line.trim_start().starts_with("#![compiler_version")) + .collect::>() + .join("\n"); + + let current_version = env!("CARGO_PKG_VERSION"); + let injected_text = format!( + "#![compiler_version(\"{}\")]\n{}", + current_version, clean_text + ); + + let program = match TemplateProgram::new(injected_text.as_str()) { Ok(x) => x, Err(error) => panic!("{error}"), }; @@ -658,14 +670,19 @@ pub(crate) mod tests { #[test] fn empty_function_body_nonempty_return() { - let prog_text = r#"fn my_true() -> bool { + let prog_text = format!( + "#![compiler_version(\"{}\")]\n{}", + env!("CARGO_PKG_VERSION"), + r#" +fn my_true() -> bool { // function body is empty, although function must return `bool` } fn main() { assert!(my_true()); } -"#; +"# + ); match SatisfiedProgram::new( prog_text, Arguments::default(), @@ -869,4 +886,80 @@ fn main() { regression_test("transfer_with_timeout"); } } + + #[test] + fn test_compiler_version_missing() { + let missing = "fn main() {}"; + let err = TemplateProgram::new(missing).unwrap_err(); + assert!(err.contains("Missing compiler version")); + } + + #[test] + fn test_compiler_version_exact_match() { + let exact = format!( + "#![compiler_version(\"{}\")]\nfn main() {{}}", + env!("CARGO_PKG_VERSION") + ); + assert!(TemplateProgram::new(exact.as_str()).is_ok()); + } + + #[test] + fn test_compiler_version_mismatch_too_old() { + // We require 99.99.99, meaning the current compiler is TOO OLD + let too_old = "#![compiler_version(\">= 99.99.99\")]\nfn main() {}"; + let err = TemplateProgram::new(too_old).unwrap_err(); + assert!( + err.contains("Compiler too old"), + "Expected 'Compiler too old', got: {}", + err + ); + } + + #[test] + fn test_compiler_version_contract_too_old() { + // We strictly require an ancient version, meaning the contract is TOO OLD for this compiler + let contract_too_old = "#![compiler_version(\"< 0.0.1\")]\nfn main() {}"; + let err = TemplateProgram::new(contract_too_old).unwrap_err(); + assert!( + err.contains("Contract too old"), + "Expected 'Contract too old', got: {}", + err + ); + } + + #[test] + fn test_compiler_version_exact_mismatch() { + let exact_mismatch = "#![compiler_version(\"= 0.0.1\")]\nfn main() {}"; + let err = TemplateProgram::new(exact_mismatch).unwrap_err(); + assert!( + err.contains("Exact version mismatch"), + "Expected 'Exact version mismatch', got: {}", + err + ); + } + + #[test] + fn test_compiler_version_operator_caret() { + let caret = format!( + "#![compiler_version(\"^{}\")]\nfn main() {{}}", + env!("CARGO_PKG_VERSION") + ); + assert!(TemplateProgram::new(caret.as_str()).is_ok()); + } + + #[test] + fn test_compiler_version_operator_gte() { + let gte = format!( + "#![compiler_version(\">={}\")]\nfn main() {{}}", + env!("CARGO_PKG_VERSION") + ); + assert!(TemplateProgram::new(gte.as_str()).is_ok()); + } + + #[test] + fn test_compiler_version_syntax_garbage_version() { + let garbage = "#![compiler_version(\"i-love-rust\")]\nfn main() {}"; + let err = TemplateProgram::new(garbage).unwrap_err(); + assert!(err.contains("Invalid version syntax")); + } } diff --git a/src/parse.rs b/src/parse.rs index a6eebd18..32d8d168 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -31,18 +31,27 @@ use crate::types::{AliasedType, BuiltinAlias, TypeConstructible, UIntType}; /// A program is a sequence of items. #[derive(Clone, Debug)] pub struct Program { + version: Option<(String, Span)>, items: Arc<[Item]>, span: Span, } impl Program { + pub fn version(&self) -> Option<&str> { + self.version.as_ref().map(|(v, _)| v.as_str()) + } + + pub fn version_span(&self) -> Option { + self.version.as_ref().map(|(_, s)| *s) + } + /// Access the items of the program. pub fn items(&self) -> &[Item] { &self.items } } -impl_eq_hash!(Program; items); +impl_eq_hash!(Program; version, items); /// An item is a component of a program. #[derive(Clone, Debug, Eq, PartialEq, Hash)] @@ -544,6 +553,9 @@ impl ModuleAssignment { impl fmt::Display for Program { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(version) = &self.version() { + writeln!(f, "#![compiler_version(\"{}\")]\n", version)?; + } for item in self.items() { writeln!(f, "{item}")?; } @@ -1126,20 +1138,37 @@ impl ChumskyParse for Program { where I: ValueInput<'tokens, Token = Token<'src>, Span = Span>, { + // Parses: # ! [ compiler_version ( "..." ) ] + let compiler_version_attr = just(Token::Hash) + .ignore_then(just(Token::Bang)) + .ignore_then(just(Token::LBracket)) + .ignore_then(just(Token::Ident("compiler_version"))) + .ignore_then(just(Token::LParen)) + .ignore_then(select! { Token::StringLiteral(s) => s.to_string() }) + .then_ignore(just(Token::RParen)) + .then_ignore(just(Token::RBracket)) + .map_with(|version, e| (version, e.span())) + .or_not() // Making it optional, just like we agreed! + .labelled("compiler version attribute"); + let skip_until_next_item = any() .then( any() .filter(|t| !matches!(t, Token::Fn | Token::Type | Token::Mod)) .repeated(), ) - // map to empty module .map_with(|_, _| Item::Module); - Item::parser() - .recover_with(via_parser(skip_until_next_item)) - .repeated() - .collect::>() - .map_with(|items, e| Program { + // Combine the attribute and the items + compiler_version_attr + .then( + Item::parser() + .recover_with(via_parser(skip_until_next_item)) + .repeated() + .collect::>(), + ) + .map_with(|(version, items), e| Program { + version, items: Arc::from(items), span: e.span(), }) @@ -1942,6 +1971,7 @@ impl<'a> arbitrary::Arbitrary<'a> for Program { .map(|_| Item::arbitrary(u)) .collect::>>()?; Ok(Self { + version: Some(("0.4.1".to_string(), Span::DUMMY)), items, span: Span::DUMMY, }) diff --git a/src/tracker.rs b/src/tracker.rs index 631a4d62..6ab2d87f 100644 --- a/src/tracker.rs +++ b/src/tracker.rs @@ -450,7 +450,12 @@ mod tests { #[test] fn test_debug_and_jet_tracing() { - let program = TemplateProgram::new(TEST_PROGRAM).unwrap(); + let program_text = format!( + "#![compiler_version(\"{}\")]\n{}", + env!("CARGO_PKG_VERSION"), + TEST_PROGRAM + ); + let program = TemplateProgram::new(program_text.as_str()).unwrap(); let program = program.instantiate(Arguments::default(), true).unwrap(); let satisfied = program.satisfy(WitnessValues::default()).unwrap(); @@ -519,7 +524,12 @@ mod tests { fn test_arith_jet_trace_regression() { let env = create_test_env(); - let program = TemplateProgram::new(TEST_ARITHMETIC_JETS).unwrap(); + let program_text = format!( + "#![compiler_version(\"{}\")]\n{}", + env!("CARGO_PKG_VERSION"), + TEST_ARITHMETIC_JETS + ); + let program = TemplateProgram::new(program_text.as_str()).unwrap(); let program = program.instantiate(Arguments::default(), true).unwrap(); let satisfied = program.satisfy(WitnessValues::default()).unwrap(); @@ -573,7 +583,12 @@ mod tests { let env = create_test_env(); - let program = TemplateProgram::new(TEST_FULL_MULTIPLY_JETS).unwrap(); + let program_text = format!( + "#![compiler_version(\"{}\")]\n{}", + env!("CARGO_PKG_VERSION"), + TEST_FULL_MULTIPLY_JETS + ); + let program = TemplateProgram::new(program_text.as_str()).unwrap(); let program = program.instantiate(Arguments::default(), true).unwrap(); let satisfied = program.satisfy(WitnessValues::default()).unwrap(); diff --git a/src/witness.rs b/src/witness.rs index 6d6ffefc..24137799 100644 --- a/src/witness.rs +++ b/src/witness.rs @@ -226,10 +226,14 @@ mod tests { #[test] fn witness_reuse() { - let s = r#"fn main() { + let s = format!( + "#![compiler_version(\"{}\")]\n{}", + env!("CARGO_PKG_VERSION"), + r#"fn main() { assert!(jet::eq_32(witness::A, witness::A)); -}"#; - let program = parse::Program::parse_from_str(s).expect("parsing works"); +}"# + ); + let program = parse::Program::parse_from_str(&s).expect("parsing works"); match ast::Program::analyze(&program).map_err(Error::from) { Ok(_) => panic!("Witness reuse was falsely accepted"), Err(Error::WitnessReused(..)) => {} @@ -239,9 +243,13 @@ mod tests { #[test] fn witness_type_mismatch() { - let s = r#"fn main() { + let s = format!( + "#![compiler_version(\"{}\")]\n{}", + env!("CARGO_PKG_VERSION"), + r#"fn main() { assert!(jet::is_zero_32(witness::A)); -}"#; +}"# + ); let witness = WitnessValues::from(HashMap::from([( WitnessName::from_str_unchecked("A"), @@ -258,13 +266,17 @@ mod tests { #[test] fn witness_outside_main() { - let s = r#"fn f() -> u32 { + let s = format!( + "#![compiler_version(\"{}\")]\n{}", + env!("CARGO_PKG_VERSION"), + r#"fn f() -> u32 { witness::OUTPUT_OF_F } fn main() { assert!(jet::is_zero_32(f())); -}"#; +}"# + ); match CompiledProgram::new(s, Arguments::default(), false) { Ok(_) => panic!("Witness outside main was falsely accepted"), diff --git a/update_examples.py b/update_examples.py new file mode 100644 index 00000000..29809b34 --- /dev/null +++ b/update_examples.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +import os +import re +import sys + +if len(sys.argv) < 2: + print("Usage: python3 update_examples.py ") + sys.exit(1) + +new_version = sys.argv[1] +examples_dir = 'examples' + +pattern = r'#!\[compiler_version\("[^"]*"\)\]' +syntax = f'#![compiler_version("{new_version}")]' + +for root, _, files in os.walk(examples_dir): + for file in files: + if file.endswith('.simf'): + filepath = os.path.join(root, file) + with open(filepath, 'r') as f: + content = f.read() + + if re.search(pattern, content): + updated = re.sub(pattern, syntax, content) + + else: + updated = f"{syntax}\n\n" + content + + with open(filepath, 'w') as f: + f.write(updated) + +print(f"Successfully updated all examples to use {syntax}") \ No newline at end of file