From bcdbb37e9e4cdf31994e1ab350d463cc55e5e314 Mon Sep 17 00:00:00 2001 From: Sdoba16 Date: Mon, 30 Mar 2026 14:02:33 +0300 Subject: [PATCH] Add usage of pragma version. Add floating versions. Add version change for examples tooling. Add bash script for downloading certain 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/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 | 52 +++ src/error.rs | 21 + src/lexer.rs | 51 ++- src/lib.rs | 90 +++- src/parse.rs | 57 ++- src/tracker.rs | 14 +- src/witness.rs | 26 +- sym-build.sh | 55 +++ update_examples.py | 31 ++ 34 files changed, 708 insertions(+), 177 deletions(-) create mode 100644 .github/workflows/check-versions.yml create mode 100644 sym-build.sh 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 8ad3298a..3c65fc14 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" @@ -253,32 +253,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", @@ -305,9 +305,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", @@ -316,6 +316,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" @@ -335,9 +347,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", ] @@ -350,9 +362,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" @@ -365,61 +377,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", @@ -427,39 +435,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", @@ -467,13 +484,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" @@ -501,30 +524,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]] @@ -534,10 +568,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" @@ -590,35 +630,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]] @@ -637,7 +695,7 @@ dependencies = [ "bitcoin_hashes", "byteorder", "elements", - "getrandom", + "getrandom 0.2.17", "ghost-cell", "hex-conservative", "miniscript", @@ -647,9 +705,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", @@ -660,13 +718,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", @@ -686,15 +745,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]] @@ -705,20 +764,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", @@ -727,15 +775,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" @@ -745,40 +793,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", @@ -786,22 +831,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" @@ -812,6 +866,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" @@ -875,3 +938,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 38366446..e59017ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,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..7acaed7b 100644 --- a/examples/array_fold.simf +++ b/examples/array_fold.simf @@ -1,3 +1,5 @@ +pragma 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..7487fa20 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 +pragma 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..ca14d5e1 100644 --- a/examples/cat.simf +++ b/examples/cat.simf @@ -1,3 +1,5 @@ +pragma 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..b288e0f2 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. */ +pragma 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..978ee8af 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 */ +pragma 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..49124f87 100644 --- a/examples/hash_loop.simf +++ b/examples/hash_loop.simf @@ -1,3 +1,5 @@ +pragma 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..bcf0e0cd 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. */ +pragma 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..9e55142d 100644 --- a/examples/htlc.simf +++ b/examples/htlc.simf @@ -9,6 +9,8 @@ * * https://docs.ivylang.org/bitcoin/language/ExampleContracts.html#htlc */ +pragma 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..02dbc8cb 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. */ +pragma 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..a3b3a670 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. */ +pragma 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..2a528cfd 100644 --- a/examples/p2ms.simf +++ b/examples/p2ms.simf @@ -6,6 +6,8 @@ * * https://docs.ivylang.org/bitcoin/language/ExampleContracts.html#lockwithmultisig */ +pragma 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..e00b1505 100644 --- a/examples/p2pk.simf +++ b/examples/p2pk.simf @@ -5,6 +5,8 @@ * * https://docs.ivylang.org/bitcoin/language/ExampleContracts.html#lockwithpublickey */ +pragma 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..030540fa 100644 --- a/examples/p2pkh.simf +++ b/examples/p2pkh.simf @@ -6,6 +6,8 @@ * * https://docs.ivylang.org/bitcoin/language/ExampleContracts.html#lockwithpublickeyhash */ +pragma 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/presigned_vault.simf b/examples/presigned_vault.simf index acd2ee28..5d147e4e 100644 --- a/examples/presigned_vault.simf +++ b/examples/presigned_vault.simf @@ -16,6 +16,8 @@ * * https://docs.ivylang.org/bitcoin/language/ExampleContracts.html#vaultspend */ +pragma 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..d18b3441 100644 --- a/examples/reveal_collision.simf +++ b/examples/reveal_collision.simf @@ -8,6 +8,8 @@ * * https://docs.ivylang.org/bitcoin/language/ExampleContracts.html#revealcollision */ +pragma 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..d0fef030 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 */ +pragma 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..c148f882 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. */ +pragma 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..86a6e858 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. */ +pragma 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..df649322 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. */ +pragma 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..0c17e7c2 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. */ +pragma 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..d497e489 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. */ +pragma 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..1ecc43a9 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 */ +pragma 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 3c59f2f7..b79c4257 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -719,6 +719,58 @@ 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 pragma requirement (e.g., "^0.5.0" or ">=0.4.0") + match semver::VersionReq::parse(&v) { + Ok(req) if req.matches(&compiler_semver) => { + // The compiler satisfies the pragma. Proceed! + } + Ok(_) => { + // Valid semver, but the compiler doesn't match the requirement + return Err(Error::VersionMismatch { + expected: v.to_string(), + compiler: compiler_version.to_string(), + }) + .with_span(span); + } + Err(_) => { + // The user typed garbage like `pragma version hello;` + return Err(Error::CannotParse(format!( + "Invalid version requirement: {}", + v + ))) + .with_span(span); + } + } + } + (None, _) | (_, None) => { + // Find where the first actual code item starts (ignoring comments) + let first_item_span = from + .items() + .first() + .map(|item| match item { + parse::Item::TypeAlias(t) => *t.as_ref(), + parse::Item::Function(f) => *f.as_ref(), + parse::Item::Module => Span::new(0, 0), + }) + .unwrap_or(Span::new(0, 0)); + + let insertion_point = Span::new(first_item_span.start, first_item_span.start); + + return Err(Error::MissingPragma { + 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 1ded6fdd..03b55d1e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -398,6 +398,14 @@ impl fmt::Display for ErrorCollector { /// Records _what_ happened but not where. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub enum Error { + VersionMismatch { + expected: String, + compiler: String, + }, + MissingPragma { + compiler: String, + }, + InvalidPragmaSyntax(String), ArraySizeNonZero(usize), ListBoundPow2(usize), BitStringPow2(usize), @@ -444,6 +452,19 @@ pub enum Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + Error::VersionMismatch { expected, compiler } => write!( + f, + "Version mismatch: Contract requires SimplicityHL version {}, but you are running compiler version {}", + expected, compiler + ), + Error::MissingPragma { compiler } => write!( + f, + "Missing pragma: Contract is missing a version declaration. Please add `pragma version {};` to the top of your file.", + compiler + ), + Error::InvalidPragmaSyntax(msg) => { + write!(f, "Invalid pragma syntax: {}", msg) + } Error::ArraySizeNonZero(size) => write!( f, "Expected a non-negative integer as array size, found {size}" diff --git a/src/lexer.rs b/src/lexer.rs index fef18927..702ebed3 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -17,6 +17,8 @@ pub enum Token<'src> { Mod, Const, Match, + Pragma, + Version, // Control symbols Arrow, @@ -33,6 +35,10 @@ pub enum Token<'src> { RBrace, LAngle, RAngle, + Dot, + Minus, + Caret, + Tilde, // Number literals DecLiteral(Decimal), @@ -69,6 +75,8 @@ impl<'src> fmt::Display for Token<'src> { Token::Mod => write!(f, "mod"), Token::Const => write!(f, "const"), Token::Match => write!(f, "match"), + Token::Pragma => write!(f, "pragma"), + Token::Version => write!(f, "version"), Token::Arrow => write!(f, "->"), Token::Colon => write!(f, ":"), @@ -84,6 +92,10 @@ impl<'src> fmt::Display for Token<'src> { Token::RBrace => write!(f, "}}"), Token::LAngle => write!(f, "<"), Token::RAngle => write!(f, ">"), + Token::Dot => write!(f, "."), + Token::Minus => write!(f, "-"), + Token::Caret => write!(f, "^"), + Token::Tilde => write!(f, "~"), Token::DecLiteral(s) => write!(f, "{}", s), Token::HexLiteral(s) => write!(f, "0x{}", s), @@ -142,6 +154,8 @@ pub fn lexer<'src>( "match" => Token::Match, "true" => Token::Bool(true), "false" => Token::Bool(false), + "pragma" => Token::Pragma, + "version" => Token::Version, _ => Token::Ident(s), }); @@ -173,6 +187,10 @@ pub fn lexer<'src>( just("}").to(Token::RBrace), just("<").to(Token::LAngle), just(">").to(Token::RAngle), + just(".").to(Token::Dot), + just("-").to(Token::Minus), + just("^").to(Token::Caret), + just("~").to(Token::Tilde), )); let comment = just("//") @@ -242,7 +260,7 @@ pub fn lex<'src>(input: &'src str) -> (Option>, Vec bool { matches!( s, - "fn" | "let" | "type" | "mod" | "const" | "match" | "true" | "false" + "fn" | "let" | "type" | "mod" | "const" | "match" | "true" | "false" | "pragma" | "version" ) } @@ -353,4 +371,35 @@ mod tests { assert!(lex_errs.is_empty()); } + + #[test] + fn test_lex_pragma_exact() { + let input = "pragma 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::Pragma, "First token should be Pragma"); + assert_eq!(tokens[1], Token::Version, "Second token should be Version"); + assert_eq!(*tokens.last().unwrap(), Token::Semi, "Last token should be a Semicolon"); + } + + #[test] + fn test_lex_pragma_identifier_boundary() { + let input = "let pragma_var = version_num;"; + let (tokens, errors) = lex(input); + + assert!(errors.is_empty(), "Expected no errors, found: {:?}", errors); + + let tokens = tokens.unwrap(); + assert_eq!(tokens[0], Token::Let); + + // Ensures the lexer doesn't accidentally trigger on variables containing the word "pragma" + // Adjust the string payload if your Token::Ident uses a different wrapping (like a custom Symbol struct) + assert_eq!(tokens[1], Token::Ident("pragma_var")); + assert_eq!(tokens[2], Token::Eq); // Assuming '=' is Token::Eq + assert_eq!(tokens[3], Token::Ident("version_num")); + assert_eq!(tokens[4], Token::Semi); + } } diff --git a/src/lib.rs b/src/lib.rs index b4a1032a..bd4aac41 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -321,7 +321,16 @@ 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("pragma version")) + .collect::>() + .join("\n"); + + let current_version = env!("CARGO_PKG_VERSION"); + let injected_text = format!("pragma version {};\n{}", current_version, clean_text); + + let program = match TemplateProgram::new(injected_text.as_str()) { Ok(x) => x, Err(error) => panic!("{error}"), }; @@ -649,14 +658,19 @@ pub(crate) mod tests { #[test] fn empty_function_body_nonempty_return() { - let prog_text = r#"fn my_true() -> bool { + let prog_text = format!( + "pragma 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(), @@ -860,4 +874,74 @@ fn main() { regression_test("transfer_with_timeout"); } } + + // PRAGMA VERSION TESTS + + #[test] + fn test_pragma_missing() { + let missing = "fn main() {}"; + let err = TemplateProgram::new(missing).unwrap_err(); + assert!(err.contains("Missing pragma")); + } + + #[test] + fn test_pragma_exact_match() { + let exact = format!("pragma version {};\nfn main() {{}}", env!("CARGO_PKG_VERSION")); + assert!(TemplateProgram::new(exact.as_str()).is_ok()); + } + + #[test] + fn test_pragma_mismatch_too_old() { + let too_old = "pragma version 0.0.1;\nfn main() {}"; + let err = TemplateProgram::new(too_old).unwrap_err(); + assert!(err.contains("Version mismatch")); + } + + #[test] + fn test_pragma_mismatch_too_new() { + let too_new = "pragma version 99.99.99;\nfn main() {}"; + let err = TemplateProgram::new(too_new).unwrap_err(); + assert!(err.contains("Version mismatch")); + } + + #[test] + fn test_pragma_operator_caret() { + let caret = format!("pragma version ^{};\nfn main() {{}}", env!("CARGO_PKG_VERSION")); + assert!(TemplateProgram::new(caret.as_str()).is_ok()); + } + + #[test] + fn test_pragma_operator_gte() { + let gte = format!("pragma version >={};\nfn main() {{}}", env!("CARGO_PKG_VERSION")); + assert!(TemplateProgram::new(gte.as_str()).is_ok()); + } + + #[test] + fn test_pragma_operator_tilde() { + let tilde = format!("pragma version ~{};\nfn main() {{}}", env!("CARGO_PKG_VERSION")); + assert!(TemplateProgram::new(tilde.as_str()).is_ok()); + } + + #[test] + fn test_pragma_operator_less_than_current_fails() { + let less_than = format!("pragma version <{};\nfn main() {{}}", env!("CARGO_PKG_VERSION")); + let err = TemplateProgram::new(less_than.as_str()).unwrap_err(); + assert!(err.contains("Version mismatch")); + } + + #[test] + fn test_pragma_syntax_garbage_version() { + let garbage = "pragma version i-love-rust;\nfn main() {}"; + let err = TemplateProgram::new(garbage).unwrap_err(); + // The parser should catch this before the version logic even sees it + assert!(err.contains("Invalid") || err.contains("Version") || err.contains("pragma")); + } + + #[test] + fn test_pragma_syntax_missing_semicolon() { + let no_semi = format!("pragma version {}\nfn main() {{}}", env!("CARGO_PKG_VERSION")); + let err = TemplateProgram::new(no_semi.as_str()).unwrap_err(); + // Chumsky should complain about an expected semicolon + assert!(err.contains("Expected") || err.contains("found")); + } } diff --git a/src/parse.rs b/src/parse.rs index c42c6f3d..1a524337 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -31,18 +31,27 @@ use crate::types::{AliasedType, BuiltinAlias, TypeConstructible}; /// 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, "pragma version {};\n", version)?; + } for item in self.items() { writeln!(f, "{item}")?; } @@ -1135,6 +1147,33 @@ impl ChumskyParse for Program { where I: ValueInput<'tokens, Token = Token<'src>, Span = Span>, { + let version_part = select! { + Token::DecLiteral(s) => s.to_string(), + Token::Ident(s) => s.to_string(), + Token::Dot => ".".to_string(), + Token::Minus => "-".to_string(), + Token::Caret => "^".to_string(), + Token::Tilde => "~".to_string(), + Token::LAngle => "<".to_string(), + Token::RAngle => ">".to_string(), + Token::Eq => "=".to_string(), + }; + + let version_string = version_part + .repeated() + .at_least(1) + .collect::>() + .map(|parts| parts.join("")) + .labelled("version string (e.g., 0.5.0-rc.0)"); + + let pragma = just(Token::Pragma) + .ignore_then(just(Token::Version)) + .ignore_then(version_string) + .then_ignore(just(Token::Semi)) + .map_with(|version, e| (version, e.span())) + .or_not() + .labelled("pragma declaration"); + let skip_until_next_item = any() .then( any() @@ -1144,11 +1183,16 @@ impl ChumskyParse for Program { // 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 pragma and the items + pragma + .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(), }) @@ -1935,6 +1979,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 4a6f693a..ee0772e9 100644 --- a/src/tracker.rs +++ b/src/tracker.rs @@ -472,7 +472,12 @@ mod tests { #[test] fn test_debug_and_jet_tracing() { - let program = TemplateProgram::new(TEST_PROGRAM).unwrap(); + let program_text = format!( + "pragma 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(); @@ -541,7 +546,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!( + "pragma 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(); diff --git a/src/witness.rs b/src/witness.rs index 6d6ffefc..f2b58f03 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!( + "pragma 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!( + "pragma 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!( + "pragma 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/sym-build.sh b/sym-build.sh new file mode 100644 index 00000000..c71ff3a9 --- /dev/null +++ b/sym-build.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [ "$#" -eq 0 ]; then + echo "Usage: sym-build [additional args...]" + exit 1 +fi + +TARGET_FILE=$1 + +# 1. Extract the version string safely +PRAGMA_VERSION=$(awk '/pragma version/ {gsub(/;/, "", $3); print $3; exit}' "$TARGET_FILE" || true) + +if [ -z "$PRAGMA_VERSION" ]; then + echo "Error: Could not find 'pragma version' in $TARGET_FILE." + echo "Please add it to the top of your file." + exit 1 +fi + +# --- NEW: Enforce Strict Versioning for the Root Contract --- +if [[ "$PRAGMA_VERSION" =~ [\^\<\>\=] ]]; then + echo "Error: The root contract ($TARGET_FILE) must use an exact compiler version to guarantee determinism." + echo "Floating versions (like $PRAGMA_VERSION) are only allowed in imported libraries." + echo "Please change the pragma in this file to an exact version (e.g., pragma version 0.5.0;)." + exit 1 +fi +# ------------------------------------------------------------ + +echo "--> Detected pragma version: $PRAGMA_VERSION" + +# 2. Silently ensure the correct version is downloaded via asdf +echo "--> Syncing compiler environment..." +asdf install simc "$PRAGMA_VERSION" + +# 3. Handle the Compiler Bootstrapping Problem +if [[ "$PRAGMA_VERSION" == "0.4."* ]]; then + echo "--> Legacy compiler detected. Temporarily stripping pragma for backward compatibility..." + + # Create a temporary file in the same directory (preserves relative paths for --wit) + TMP_FILE="${TARGET_FILE}.legacy.tmp" + + # Ensure the temp file gets deleted even if the compiler crashes + trap 'rm -f "$TMP_FILE"' EXIT + + # Copy the file but remove the pragma line + awk '!/pragma version/' "$TARGET_FILE" > "$TMP_FILE" + + # Run the legacy compiler with the temporary file, passing any extra args + echo "--> Compiling with simc v$PRAGMA_VERSION" + ASDF_SIMC_VERSION="$PRAGMA_VERSION" asdf exec simc "$TMP_FILE" "${@:2}" +else + # Run the modern compiler normally + echo "--> Compiling with simc v$PRAGMA_VERSION" + ASDF_SIMC_VERSION="$PRAGMA_VERSION" asdf exec simc "$TARGET_FILE" "${@:2}" +fi \ No newline at end of file diff --git a/update_examples.py b/update_examples.py new file mode 100644 index 00000000..44d23b27 --- /dev/null +++ b/update_examples.py @@ -0,0 +1,31 @@ +#!/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' + +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() + + # Check if the pragma exists at all + if re.search(r'pragma version[^;]*;', content): + # It exists, so we update it + updated = re.sub(r'pragma version[^;]*;', f'pragma version {new_version};', content) + else: + # pragma is missing, adding it to the top + updated = f"pragma version {new_version};\n\n" + content + + with open(filepath, 'w') as f: + f.write(updated) + +print(f"Successfully updated all examples to use pragma version {new_version};") \ No newline at end of file