diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 6a67f92..072dd91 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -16,7 +16,7 @@ concurrency: env: CARGO_TERM_COLOR: always - SIMPLEX_VERSION: 0.0.12 + SIMPLEX_VERSION: 0.2.0 jobs: lint: @@ -52,6 +52,11 @@ jobs: run: | simplex build + - name: Check contracts formatting + shell: bash + run: | + simplex fmt --check + - name: Check formatting run: | cargo fmt --all --check diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3045107..610a52e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,7 +17,7 @@ concurrency: env: CARGO_TERM_COLOR: always RUST_BACKTRACE: full - SIMPLEX_VERSION: 0.0.12 + SIMPLEX_VERSION: 0.2.0 jobs: simplex-tests: @@ -64,4 +64,4 @@ jobs: env: SIMPLEX_TEST_VERBOSE: ${{ runner.debug == '1' && '-v' || '' }} run: | - simplex test --test-threads 4 "${SIMPLEX_TEST_VERBOSE}" -- --release + simplex test --test-threads 2 "${SIMPLEX_TEST_VERBOSE}" -- --release diff --git a/CHANGELOG.md b/CHANGELOG.md index 287b248..9b54eb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## Unreleased + +### Added +- Extended functions for retrieving information from `OP_RETURN`. +- Conversion and bit functions for `u2` and `u4`. +- Functions for asserting script hashes. + +### Changed +- Bumped Simplex to 0.2.0. +- Aligned existing testing API with changes in Simplex. + ## [0.2.0] ### Breaking changes diff --git a/Cargo.lock b/Cargo.lock index f188c5c..e2e789a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -133,7 +133,7 @@ version = "0.1.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "365c0acd5b2e8dd0111a46c4faea83fb3cfb6e39a49a7c73a06e090db7b2eff0" dependencies = [ - "bitcoin_hashes", + "bitcoin_hashes 0.14.101", ] [[package]] @@ -156,17 +156,36 @@ checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" [[package]] name = "bip39" -version = "2.2.2" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90dbd31c98227229239363921e60fcf5e558e43ec69094d46fc4996f08d1d5bc" +checksum = "4559bb9225f8401dcea601cf8dc9ed81ea0dadd9cf7755fdefdd93ef7961e19e" dependencies = [ - "bitcoin_hashes", - "rand", - "rand_core", + "bitcoin_hashes 0.14.101", + "rand 0.8.8", + "rand_core 0.6.4", "serde", "unicode-normalization", ] +[[package]] +name = "bit-set" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56d87354e4229f54a44f7bf2435906a4656dba36026ab6eaca629a2c436a691c" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5727b15fa97d4f4fee0a3b7c3d550ed0269f54329207b86388de918604e31269" +dependencies = [ + "borsh", + "serde", +] + [[package]] name = "bitcoin" version = "0.32.102" @@ -176,9 +195,10 @@ dependencies = [ "base58ck", "base64 0.21.7", "bech32", + "bitcoin-consensus-encoding", "bitcoin-io", "bitcoin-units", - "bitcoin_hashes", + "bitcoin_hashes 0.14.101", "hex-conservative 0.2.3", "hex_lit", "secp256k1", @@ -191,11 +211,17 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9daa31138eb443d5751b207f3f64154e2bb09cd59960562ccc7a7112be38147f" dependencies = [ - "bitcoin-internals", + "bitcoin-internals 0.7.0", "hex-conservative 1.3.0", "serde", ] +[[package]] +name = "bitcoin-internals" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d573f4cf32996a8dce612e4348cece65a241f1882ed594047c9ba348e8869fa5" + [[package]] name = "bitcoin-internals" version = "0.7.0" @@ -238,6 +264,18 @@ dependencies = [ "serde", ] +[[package]] +name = "bitcoin_hashes" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5304e53726dbe5f93141535e102ed97b5bf4714fbecefdda8f9fb98d7fdaff0e" +dependencies = [ + "bitcoin-consensus-encoding", + "bitcoin-internals 0.6.0", + "hex-conservative 1.3.0", + "serde", +] + [[package]] name = "bitcoincore-rpc" version = "0.19.0" @@ -301,11 +339,35 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.10.4" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "borsh" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "553c5d846a6ba5150c65e3b1b8ec073bcf1abc20f9b7220de384a4443ea4e20a" +dependencies = [ + "borsh-derive", + "bytes", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +checksum = "12cdfe656708a01f89b451a7d36466e6fe6c414de0aa18fc54f864f6f9ca9f56" dependencies = [ - "generic-array", + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 3.0.9", ] [[package]] @@ -336,6 +398,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + [[package]] name = "cc" version = "1.9.0" @@ -354,6 +422,23 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cpufeatures", + "rand_core 0.10.1", +] + [[package]] name = "chumsky" version = "0.11.2" @@ -404,12 +489,24 @@ dependencies = [ "cc", ] +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + [[package]] name = "colorchoice" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "const_format" version = "0.2.36" @@ -431,11 +528,17 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "core_detect" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" + [[package]] name = "cpufeatures" -version = "0.2.17" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" dependencies = [ "libc", ] @@ -473,23 +576,32 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-common" -version = "0.1.7" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ - "generic-array", - "typenum", + "hybrid-array", +] + +[[package]] +name = "ctutils" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03bb0e1cc970d482d121d9a1744999169b69a07470b3d644a7894e53fcaf4574" +dependencies = [ + "cmov", ] [[package]] name = "digest" -version = "0.10.7" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ "block-buffer", + "const-oid", "crypto-common", - "subtle", + "ctutils", ] [[package]] @@ -540,12 +652,16 @@ dependencies = [ [[package]] name = "elements" -version = "0.25.3" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c63a04238f4b7f3564fe9705921452c2900efd00982545e5c634fac9024fe2" +checksum = "34e00f7ddcd826d2f4c9bd72d79a6b6991a0ac4286e7e2b2ff8eecd130549073" dependencies = [ "bech32", "bitcoin", + "bitcoin-consensus-encoding", + "bitcoin-internals 0.6.0", + "bitcoin_hashes 1.2.0", + "hex-conservative 1.3.0", "secp256k1-zkp", "serde", "serde_json", @@ -553,9 +669,9 @@ dependencies = [ [[package]] name = "elements-miniscript" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "571fa105690f83c7833df2109eb2e14ca0e62d633d2624ffcb166ff18a3da870" +checksum = "0c18e931c81e6e7e58d63b6fea1c57f5534717f0e6721741c4fce72a6c547e19" dependencies = [ "bitcoin", "elements", @@ -598,11 +714,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" dependencies = [ "byteorder", - "rand", + "rand 0.8.8", "rustc-hex", "static_assertions", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foldhash" version = "0.1.5" @@ -645,16 +767,6 @@ dependencies = [ "slab", ] -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - [[package]] name = "getrandom" version = "0.2.17" @@ -677,6 +789,7 @@ dependencies = [ "cfg-if", "libc", "r-efi", + "rand_core 0.10.1", ] [[package]] @@ -685,12 +798,6 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8449d342b1c67f49169e92e71deb7b9b27f30062301a16dbc27a4cc8d2351b7" -[[package]] -name = "glob" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" - [[package]] name = "globset" version = "0.4.20" @@ -764,9 +871,9 @@ checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" [[package]] name = "hmac" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" dependencies = [ "digest", ] @@ -780,6 +887,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "hybrid-array" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" +dependencies = [ + "typenum", +] + [[package]] name = "ignore" version = "0.4.33" @@ -1093,12 +1209,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.37" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +checksum = "2bfe0f4c752e450fc2faf62654f1c134747922825d5b04ca717b8874f41a40c0" dependencies = [ "proc-macro2", - "syn 2.0.119", + "syn 3.0.9", ] [[package]] @@ -1130,6 +1246,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8530004ccb15eae51c7e40009fbe317f341f804db54dc033eec1c50be28cfa0" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags 2.13.2", + "chacha20", + "core_detect", + "num-traits", + "rand 0.10.3", + "rand_xorshift", + "regex-syntax 0.8.11", + "rusty-fork", + "tempfile", + "unarray", +] + [[package]] name = "psm" version = "0.1.32" @@ -1140,6 +1276,12 @@ dependencies = [ "cc", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.47" @@ -1169,7 +1311,18 @@ checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" dependencies = [ "libc", "rand_chacha", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", ] [[package]] @@ -1179,7 +1332,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -1191,6 +1344,21 @@ dependencies = [ "getrandom 0.2.17", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_xorshift" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60aa6af80be32871323012e02e6e65f8a7cc7890931ae421d217ad8fe0df2ccf" +dependencies = [ + "rand_core 0.10.1", +] + [[package]] name = "regex-automata" version = "0.3.9" @@ -1313,6 +1481,18 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" +[[package]] +name = "rusty-fork" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "same-file" version = "1.0.6" @@ -1328,8 +1508,8 @@ version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ - "bitcoin_hashes", - "rand", + "bitcoin_hashes 0.14.101", + "rand 0.8.8", "secp256k1-sys", "serde", ] @@ -1350,7 +1530,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "403f6ab6ef5189ded7d11cb384a96a9f871001e50b391a8fcbb310c9ebc2099b" dependencies = [ "bitcoin-private", - "rand", + "rand 0.8.8", "secp256k1", "secp256k1-zkp-sys", "serde", @@ -1426,9 +1606,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.9" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +checksum = "47d7069beb7d6ac7b9acd1039986e73443f24234f41074da099d6f994ac9ad19" dependencies = [ "cfg-if", "cpufeatures", @@ -1443,36 +1623,34 @@ checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "simplicity-lang" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13ed081e3046d66c146d7201bcbf3b655ca3436cb83f6efc26d7895bd2b79d06" +version = "0.9.0" +source = "git+https://github.com/BlockstreamResearch/rust-simplicity.git?rev=2c5a066#2c5a066cc8f40cbadcefaa604742cbae99bd2557" dependencies = [ "bitcoin", - "bitcoin_hashes", + "bitcoin_hashes 1.2.0", "byteorder", "elements", "getrandom 0.2.17", "ghost-cell", - "hex-conservative 0.2.3", + "hex-conservative 1.3.0", "miniscript", "simplicity-sys", ] [[package]] name = "simplicity-sys" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d1ec5477c7650b8ef511aa56dccb28f2e8cdb6e87f260ecffdaf0ebfef2d3b" +version = "0.8.0" +source = "git+https://github.com/BlockstreamResearch/rust-simplicity.git?rev=2c5a066#2c5a066cc8f40cbadcefaa604742cbae99bd2557" dependencies = [ - "bitcoin_hashes", + "bitcoin_hashes 1.2.0", "cc", ] [[package]] name = "simplicityhl" -version = "0.7.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae77168fa3367d234b11bb3999cbd6e04d7180db0b9e11f83de259528e3b23d1" +checksum = "9e110f12190a4df92970b50d701f7da979638a60c5bf8294d55e4a55aceebf3e" dependencies = [ "ariadne", "base64 0.21.7", @@ -1496,7 +1674,7 @@ dependencies = [ "num-bigint", "num-traits", "primitive-types", - "rand", + "rand 0.8.8", "secp256k1-zkp", "smplx-std", ] @@ -1509,11 +1687,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smplx-build" -version = "0.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e9561b9da1d788d3e777f8a59b085dd13a6fbace5b109f2b0576069f9f36ff" +version = "0.2.0" +source = "git+https://github.com/BlockstreamResearch/smplx?branch=fix%2Fbump-simplicity-lang#08da700f347486578deafb7306a36986da7fa742" dependencies = [ - "glob", "globwalk", "pathdiff", "prettyplease", @@ -1522,7 +1698,7 @@ dependencies = [ "serde", "serde_json", "simplicityhl", - "syn 2.0.119", + "syn 3.0.9", "thiserror", "toml", "toml_edit", @@ -1530,20 +1706,18 @@ dependencies = [ [[package]] name = "smplx-macros" -version = "0.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f802ff11501371c4c822f0ecdbf7e9e65180ccbbb43b55f252c9a00cb849d24" +version = "0.2.0" +source = "git+https://github.com/BlockstreamResearch/smplx?branch=fix%2Fbump-simplicity-lang#08da700f347486578deafb7306a36986da7fa742" dependencies = [ "smplx-build", "smplx-test", - "syn 2.0.119", + "syn 3.0.9", ] [[package]] name = "smplx-regtest" -version = "0.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "881db46f30a472018c26c5f245637a722ccba36a873b845d5e6376e7bd5e1106" +version = "0.2.0" +source = "git+https://github.com/BlockstreamResearch/smplx?branch=fix%2Fbump-simplicity-lang#08da700f347486578deafb7306a36986da7fa742" dependencies = [ "electrsd", "hex", @@ -1557,12 +1731,10 @@ dependencies = [ [[package]] name = "smplx-sdk" -version = "0.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98b85112288eccd99ba34bd852681d78c3581a52709eab0ce52a073b9d961d8e" +version = "0.2.0" +source = "git+https://github.com/BlockstreamResearch/smplx?branch=fix%2Fbump-simplicity-lang#08da700f347486578deafb7306a36986da7fa742" dependencies = [ "bip39", - "bitcoin_hashes", "bitcoincore-rpc", "dyn-clone", "elements-miniscript", @@ -1570,18 +1742,18 @@ dependencies = [ "minreq 3.0.0", "serde", "serde_json", - "sha2", "simplicityhl", "thiserror", ] [[package]] name = "smplx-std" -version = "0.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e05855ac57a1b5f737a4bc244ebf5924453957a2066a0bb0fd8df033498386ba" +version = "0.2.0" +source = "git+https://github.com/BlockstreamResearch/smplx?branch=fix%2Fbump-simplicity-lang#08da700f347486578deafb7306a36986da7fa742" dependencies = [ "either", + "rand 0.10.3", + "rand_core 0.10.1", "serde", "simplicityhl", "smplx-macros", @@ -1591,18 +1763,19 @@ dependencies = [ [[package]] name = "smplx-test" -version = "0.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a66ea20c2701a90faf10f9a18176058dd7a82222797d5ed6da9c83f0f1ca032" +version = "0.2.0" +source = "git+https://github.com/BlockstreamResearch/smplx?branch=fix%2Fbump-simplicity-lang#08da700f347486578deafb7306a36986da7fa742" dependencies = [ "electrsd", "proc-macro2", + "proptest", "quote", + "rand 0.10.3", "serde", "simplicityhl", "smplx-regtest", "smplx-sdk", - "syn 2.0.119", + "syn 3.0.9", "thiserror", "toml", ] @@ -1707,26 +1880,17 @@ checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" [[package]] name = "toml" -version = "0.9.12+spec-1.1.0" +version = "1.1.8+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +checksum = "20489e00e4d8741d6be680764cc12e270655e375a20d1011e844a9c3379e678d" dependencies = [ "indexmap", "serde_core", "serde_spanned", - "toml_datetime 0.7.5+spec-1.1.0", + "toml_datetime", "toml_parser", "toml_writer", - "winnow 0.7.15", -] - -[[package]] -name = "toml_datetime" -version = "0.7.5+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" -dependencies = [ - "serde_core", + "winnow", ] [[package]] @@ -1745,10 +1909,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3641d5bbb5349a79e1020a242d251efbc546ad8048d133958323ce9c40a9c9c" dependencies = [ "indexmap", - "toml_datetime 1.1.2+spec-1.1.0", + "toml_datetime", "toml_parser", "toml_writer", - "winnow 1.0.4", + "winnow", ] [[package]] @@ -1757,7 +1921,7 @@ version = "1.1.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c" dependencies = [ - "winnow 1.0.4", + "winnow", ] [[package]] @@ -1784,6 +1948,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-ident" version = "1.0.27" @@ -1830,10 +2000,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] -name = "version_check" -version = "0.9.5" +name = "wait-timeout" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] [[package]] name = "walkdir" @@ -2023,12 +2196,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "winnow" -version = "0.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" - [[package]] name = "winnow" version = "1.0.4" diff --git a/Cargo.toml b/Cargo.toml index 36dee43..083ed8d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,10 @@ keywords = ["simplicity", "liquid", "elements", "smart-contracts"] categories = ["cryptography::cryptocurrencies"] [dependencies] -smplx-std = { version = "0.0.12" } +# TODO: Replace this with a new Simplex version that includes the fix from simplicity-lang +# (For now, we must use a patched smplx dependency because of a breaking change in `ExecutionError` +# where the fix is represented). +smplx-std = { git = "https://github.com/BlockstreamResearch/smplx", branch = "fix/bump-simplicity-lang", features = ["test"] } [dev-dependencies] anyhow = { version = "1.0.101" } @@ -20,3 +23,7 @@ primitive-types = { version = "0.14" } num-bigint = { version = "0.5.1" } num-traits = { version = "0.2.19" } secp256k1-zkp = { version = "0.11.0", features = ["rand"] } + +# TODO: Remove this patch only when SimplicityHL uses a simplicity-lang release with the pruning fix. +[patch.crates-io] +simplicity-lang = { git = "https://github.com/BlockstreamResearch/rust-simplicity.git", rev = "2c5a066" } \ No newline at end of file diff --git a/README.md b/README.md index 63ec08a..79db4f9 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,12 @@ simf/lib ├── u1 │ └── convert.simf │ └── Conversions from `u1` to other uint types and `bool`. +├── u2 +├── u4 +│ ├── bit.simf +│ │ └── Basic bitwise operations (`and`, `or`, `eq`). +│ └── convert.simf +│ └── Conversions between `u2`, `u4` and other uint types. ├── u8 ├── u16 ├── u32 @@ -46,23 +52,25 @@ simf/lib │ │ │ └── Functions for asserting `Amount` in inputs and outputs. │ │ ├── asset_id.simf │ │ │ └── Functions for asserting `AssetId` in inputs and outputs. -│ │ └── asset_id_with_amount.simf -│ │ └── Functions for asserting `Amount` and `AssetId` in inputs and outputs. -│ └── getters -│ ├── amount.simf -│ │ └── Functions for retrieving `Amount` in inputs and outputs. -│ ├── asset_id.simf -│ │ └── Functions for retrieving `AssetId` in inputs and outputs. -│ ├── asset_id_with_amount.simf -│ │ └── Functions for retrieving `Amount` and `AssetId` in inputs and outputs. -│ └── script_hash.simf -│ └── Functions for retrieving hash of the scriptPubKey in inputs and outputs. +│ │ ├── asset_id_with_amount.simf +│ │ │ └── Functions for asserting `Amount` and `AssetId` in inputs and outputs. +│ │ └── script_hash.simf +│ │ └── Functions for asserting hash of the scriptPubKey in inputs and outputs. +│ ├── getters +│ │ ├── amount.simf +│ │ │ └── Functions for retrieving `Amount` in inputs and outputs. +│ │ ├── asset_id.simf +│ │ │ └── Functions for retrieving `AssetId` in inputs and outputs. +│ │ ├── asset_id_with_amount.simf +│ │ │ └── Functions for retrieving `Amount` and `AssetId` in inputs and outputs. +│ │ └── script_hash.simf +│ │ └── Functions for retrieving hash of the scriptPubKey in inputs and outputs. +│ └── op_return.simf +│ └── Utilities for detecting and enforcing `OP_RETURN` (null data) outputs. ├── asserts.simf │ └── Assertion helpers: `assert_eq_*` for uints and `bool`, plus `assert_none_*` for `Option`. -├── binary.simf -│ └── Basic binary logic operations: `and`, `or`, `not`, `xor`. -└── op_return.simf - └── Utilities for detecting and enforcing `OP_RETURN` (null data) outputs. +└── binary.simf + └── Basic binary logic operations: `and`, `or`, `not`, `xor`. ``` ## Installation @@ -81,7 +89,7 @@ simplex install https://github.com/BlockstreamResearch/simplicityhl-std ``` > [!NOTE] -> The library works with Simplex versions 0.0.11 and 0.0.12. +> The library contracts work with Simplex version 0.0.7 or higher. ## Documentation diff --git a/Simplex.toml b/Simplex.toml index c522f64..555b27b 100644 --- a/Simplex.toml +++ b/Simplex.toml @@ -6,7 +6,7 @@ # out_dir = "./src/artifacts" # [dependencies] -# some_dep = { git = "", path = "" } +# some_dep = { git = "", path = "", = "", package = "" } # [regtest] # mnemonic = "exist carry drive collect lend cereal occur much tiger just involve mean" @@ -25,6 +25,11 @@ # url = "" # network = "" +# [test.fuzz] +# cases = 10 +# seed = 12345 +# network = "" + # [test.rpc] # url = "" # username = "" diff --git a/WISHLIST.md b/WISHLIST.md index 8f0db3e..62ed2ad 100644 --- a/WISHLIST.md +++ b/WISHLIST.md @@ -65,7 +65,7 @@ Q. Aren't the first three directly implemented by jets? Do we just need to `unwr Input/output by index. * Get script hash (implemented: `utxo::getters::script_hash`) -* Check that the script hash is equal to the passed value +* Check that the script hash is equal to the passed value (implemented: `utxo::asserts::script_hash`) * Check that the input script hash equals the output script hash * Same as prior function, but also check that the values are equal to the passed value diff --git a/docs/stdlib.json b/docs/stdlib.json index 133053e..2ec57e2 100644 --- a/docs/stdlib.json +++ b/docs/stdlib.json @@ -440,6 +440,27 @@ "output_type": "Confidential1", "description": "Returns the confidential input asset_id at the current_index.\n\n## Panics\nPanics if asset_id is explicit." }, + { + "simplicityhl_name": "assert_script_hash", + "section": "Script hash asserts", + "input_type": "u32, bool, u256", + "output_type": "()", + "description": "Asserts that the SHA256 hash of the scriptPubKey at the specified input or output index equals the expected `u256` value.\n\n## Panics\nThe assertion fails." + }, + { + "simplicityhl_name": "assert_input_script_hash", + "section": "Script hash asserts", + "input_type": "u32, u256", + "output_type": "()", + "description": "Asserts that the SHA256 hash of the scriptPubKey of the input at the given index equals the expected `u256` value.\n\n## Panics\nThe assertion fails." + }, + { + "simplicityhl_name": "assert_output_script_hash", + "section": "Script hash asserts", + "input_type": "u32, u256", + "output_type": "()", + "description": "Asserts that the SHA256 hash of the scriptPubKey of the output at the given index equals the expected `u256` value.\n\n## Panics\nThe assertion fails." + }, { "simplicityhl_name": "get_script_hash", "section": "Script hash getters", @@ -489,6 +510,62 @@ "output_type": "bool", "description": "Return the logical XOR of the two given values." }, + { + "simplicityhl_name": "get_entry_option", + "section": "OP_RETURN", + "input_type": "u32, u32", + "output_type": "Option", + "description": "Return specified entry at the given OP_RETURN output and given index. Return `None` if the output doesn't exist, isn't null data, or has no such entry." + }, + { + "simplicityhl_name": "pushed_data_from_entry", + "section": "OP_RETURN", + "input_type": "OpReturnEntry", + "output_type": "Option<(u2, u256)>", + "description": "Return `(x, hash)` from the given entry if it is pushed data and `None` otherwise. `hash` is the SHA256 hash of the pushed data and `x` indicates how the data was pushed: 0 is an immediate 0 to 75 bytes, 1 is an OP_PUSHDATA1, 2 is an OP_PUSHDATA2 and 3 is an OP_PUSHDATA4." + }, + { + "simplicityhl_name": "get_pushed_data", + "section": "OP_RETURN", + "input_type": "u32, u32", + "output_type": "Option<(u2, u256)>", + "description": "Return `(x, hash)` if the entry at the given index of the given output is pushed data. `hash` is the SHA256 hash of the pushed data and `x` indicates how the data was pushed: 0 is an immediate 0 to 75 bytes, 1 is an OP_PUSHDATA1, 2 is an OP_PUSHDATA2 and 3 is an OP_PUSHDATA4." + }, + { + "simplicityhl_name": "op_x_minus_1_from_entry", + "section": "OP_RETURN", + "input_type": "OpReturnEntry", + "output_type": "Option", + "description": "Return `x - 1` from the given entry if it is OP_x for x in the range 1..=16 and `None` otherwise." + }, + { + "simplicityhl_name": "get_op_x_minus_1", + "section": "OP_RETURN", + "input_type": "u32, u32", + "output_type": "Option", + "description": "Return `x - 1` if the given entry at the given output is OP_x for x in the range 1..=16 and `None` otherwise." + }, + { + "simplicityhl_name": "negate_or_reserved_from_entry", + "section": "OP_RETURN", + "input_type": "OpReturnEntry", + "output_type": "Option", + "description": "Return `Some(0)` if the given entry is OP_1NEGATE, `Some(1)` if the entry is OP_RESERVED and `None` otherwise." + }, + { + "simplicityhl_name": "get_negate_or_reserved", + "section": "OP_RETURN", + "input_type": "u32, u32", + "output_type": "Option", + "description": "Return `Some(0)` if the the entry at the given entry of the given output is OP_1NEGATE, `Some(1)` if the entry is OP_RESERVED and `None` otherwise." + }, + { + "simplicityhl_name": "kind_of_entry", + "section": "OP_RETURN", + "input_type": "OpReturnEntry", + "output_type": "u8", + "description": "Return the kind of the entry. 0 is pushed data, 1 is OP_1..OP_16 and 2 is OP_1NEGATE or OP_RESERVED." + }, { "simplicityhl_name": "is_output_op_return", "section": "OP_RETURN", @@ -496,6 +573,13 @@ "output_type": "bool", "description": "Return `true` if the output at the given index is an OP_RETURN (null data) output, `false` otherwise (including if the output does not exist)." }, + { + "simplicityhl_name": "has_entry", + "section": "OP_RETURN", + "input_type": "u32, u32", + "output_type": "bool", + "description": "Return true if the given output is an OP_RETURN output that has an entry at `entry_index`." + }, { "simplicityhl_name": "assert_output_is_op_return", "section": "OP_RETURN", @@ -503,6 +587,13 @@ "output_type": "()", "description": "Assert that the output at the given index is an OP_RETURN (null data) output.\n\n## Panics\nThe assertion fails." }, + { + "simplicityhl_name": "assert_has_entry", + "section": "OP_RETURN", + "input_type": "u32, u32", + "output_type": "()", + "description": "Assert that the given output is an OP_RETURN that has an entry at `entry_index`.\n\n## Panics\nThe assertion fails." + }, { "simplicityhl_name": "ge_to_point", "section": "secp256k1 operations", @@ -678,6 +769,160 @@ "output_type": "bool", "description": "Convert a `u1` value to `bool`." }, + { + "simplicityhl_name": "and_2", + "section": "`u2` bit logic", + "input_type": "u2, u2", + "output_type": "u2", + "description": "Bitwise AND of two `u2` values." + }, + { + "simplicityhl_name": "or_2", + "section": "`u2` bit logic", + "input_type": "u2, u2", + "output_type": "u2", + "description": "Bitwise OR of two `u2` values." + }, + { + "simplicityhl_name": "eq_2", + "section": "`u2` bit logic", + "input_type": "u2, u2", + "output_type": "bool", + "description": "Check whether two `u2` values are equal." + }, + { + "simplicityhl_name": "u2_to_u8", + "section": "`u2` conversions", + "input_type": "u2", + "output_type": "u8", + "description": "Widen a `u2` value to a `u8` value, zero-extending the high bits." + }, + { + "simplicityhl_name": "u2_to_u16", + "section": "`u2` conversions", + "input_type": "u2", + "output_type": "u16", + "description": "Widen a `u2` value to a `u16` value, zero-extending the high bits." + }, + { + "simplicityhl_name": "u2_to_u32", + "section": "`u2` conversions", + "input_type": "u2", + "output_type": "u32", + "description": "Widen a `u2` value to a `u32` value, zero-extending the high bits." + }, + { + "simplicityhl_name": "u2_to_u64", + "section": "`u2` conversions", + "input_type": "u2", + "output_type": "u64", + "description": "Widen a `u2` value to a `u64` value, zero-extending the high bits." + }, + { + "simplicityhl_name": "u2_to_u128", + "section": "`u2` conversions", + "input_type": "u2", + "output_type": "u128", + "description": "Widen a `u2` value to a `u128` value, zero-extending the high bits." + }, + { + "simplicityhl_name": "u2_to_u256", + "section": "`u2` conversions", + "input_type": "u2", + "output_type": "u256", + "description": "Widen a `u2` value to a `u256` value, zero-extending the high bits." + }, + { + "simplicityhl_name": "split_u2_into_u1", + "section": "`u2` conversions", + "input_type": "u2", + "output_type": "(u1, u1)", + "description": "Split a `u2` value into two `u1` words, most-significant first." + }, + { + "simplicityhl_name": "safe_u2_to_u1", + "section": "`u2` conversions", + "input_type": "u2", + "output_type": "u1", + "description": "Narrow a `u2` value to a `u1` value.\n\n## Panics\nPanics if the value does not fit in `u1`." + }, + { + "simplicityhl_name": "and_4", + "section": "`u4` bit logic", + "input_type": "u4, u4", + "output_type": "u4", + "description": "Bitwise AND of two `u4` values." + }, + { + "simplicityhl_name": "or_4", + "section": "`u4` bit logic", + "input_type": "u4, u4", + "output_type": "u4", + "description": "Bitwise OR of two `u4` values." + }, + { + "simplicityhl_name": "eq_4", + "section": "`u4` bit logic", + "input_type": "u4, u4", + "output_type": "bool", + "description": "Check whether two `u4` values are equal." + }, + { + "simplicityhl_name": "u4_to_u8", + "section": "`u4` conversions", + "input_type": "u4", + "output_type": "u8", + "description": "Widen a `u4` value to a `u8` value, zero-extending the high bits." + }, + { + "simplicityhl_name": "u4_to_u16", + "section": "`u4` conversions", + "input_type": "u4", + "output_type": "u16", + "description": "Widen a `u4` value to a `u16` value, zero-extending the high bits." + }, + { + "simplicityhl_name": "u4_to_u32", + "section": "`u4` conversions", + "input_type": "u4", + "output_type": "u32", + "description": "Widen a `u4` value to a `u32` value, zero-extending the high bits." + }, + { + "simplicityhl_name": "u4_to_u64", + "section": "`u4` conversions", + "input_type": "u4", + "output_type": "u64", + "description": "Widen a `u4` value to a `u64` value, zero-extending the high bits." + }, + { + "simplicityhl_name": "u4_to_u128", + "section": "`u4` conversions", + "input_type": "u4", + "output_type": "u128", + "description": "Widen a `u4` value to a `u128` value, zero-extending the high bits." + }, + { + "simplicityhl_name": "u4_to_u256", + "section": "`u4` conversions", + "input_type": "u4", + "output_type": "u256", + "description": "Widen a `u4` value to a `u256` value, zero-extending the high bits." + }, + { + "simplicityhl_name": "split_u4_into_u1", + "section": "`u4` conversions", + "input_type": "u4", + "output_type": "(u1, u1, u1, u1)", + "description": "Split a `u4` value into four `u1` words, most-significant first." + }, + { + "simplicityhl_name": "safe_u4_to_u1", + "section": "`u4` conversions", + "input_type": "u4", + "output_type": "u1", + "description": "Narrow a `u4` value to a `u1` value.\n\n## Panics\nPanics if the value does not fit in `u1`." + }, { "simplicityhl_name": "checked_add_8", "section": "`u8` arithmetic", diff --git a/simf/lib/op_return.simf b/simf/lib/op_return.simf deleted file mode 100644 index d6e1d85..0000000 --- a/simf/lib/op_return.simf +++ /dev/null @@ -1,12 +0,0 @@ -/// Returns true if the specified output is an OP_RETURN (null data) output -pub fn is_output_op_return(output_index: u32) -> bool { - match jet::output_null_datum(output_index, 0) { - Some(entry: Option>>) => true, - None => false, - } -} - -/// Asserts that the specified output is an OP_RETURN (null data) output -pub fn assert_output_is_op_return(output_index: u32) { - assert!(is_output_op_return(output_index)); -} diff --git a/simf/lib/u2/bit.simf b/simf/lib/u2/bit.simf new file mode 100644 index 0000000..e4671ba --- /dev/null +++ b/simf/lib/u2/bit.simf @@ -0,0 +1,25 @@ +use crate::lib::binary::and; + +/// Bitwise AND of two 2-bit values +pub fn and_2(a: u2, b: u2) -> u2 { + let (a_high, a_low): (u1, u1) = ::into(a); + let (b_high, b_low): (u1, u1) = ::into(b); + + <(u1, u1)>::into((jet::and_1(a_high, b_high), jet::and_1(a_low, b_low))) +} + +/// Bitwise OR of two 2-bit values +pub fn or_2(a: u2, b: u2) -> u2 { + let (a_high, a_low): (u1, u1) = ::into(a); + let (b_high, b_low): (u1, u1) = ::into(b); + + <(u1, u1)>::into((jet::or_1(a_high, b_high), jet::or_1(a_low, b_low))) +} + +/// Checks if two 2-bit values are equal +pub fn eq_2(a: u2, b: u2) -> bool { + let (a_high, a_low): (u1, u1) = ::into(a); + let (b_high, b_low): (u1, u1) = ::into(b); + + and(jet::eq_1(a_high, b_high), jet::eq_1(a_low, b_low)) +} diff --git a/simf/lib/u2/convert.simf b/simf/lib/u2/convert.simf new file mode 100644 index 0000000..27251a6 --- /dev/null +++ b/simf/lib/u2/convert.simf @@ -0,0 +1,60 @@ +/// Widening uint conversions + +/// Converts u2 to u8 +pub fn u2_to_u8(a: u2) -> u8 { + <(u2, u2, u2, u2)>::into((0, 0, 0, a)) +} + +/// Converts u2 to u16 +pub fn u2_to_u16(a: u2) -> u16 { + let a_u8: u8 = u2_to_u8(a); + + jet::left_pad_low_8_16(a_u8) +} + +/// Converts u2 to u32 +pub fn u2_to_u32(a: u2) -> u32 { + let a_u8: u8 = u2_to_u8(a); + + jet::left_pad_low_8_32(a_u8) +} + +/// Converts u2 to u64 +pub fn u2_to_u64(a: u2) -> u64 { + let a_u8: u8 = u2_to_u8(a); + + jet::left_pad_low_8_64(a_u8) +} + +/// Converts u2 to u128 +pub fn u2_to_u128(a: u2) -> u128 { + let a_u64: u64 = u2_to_u64(a); + + <(u64, u64)>::into((0, a_u64)) +} + +/// Converts u2 to u256 +pub fn u2_to_u256(a: u2) -> u256 { + let a_u128: u128 = u2_to_u128(a); + + <(u128, u128)>::into((0, a_u128)) +} + +/// Splitting uint conversions + +/// Splits u2 into two u1 +pub fn split_u2_into_u1(a: u2) -> (u1, u1) { + ::into(a) +} + +/// Narrowing uint conversions + +/// Converts u2 into u1. +/// Panics if the value does not fit in u1 +pub fn safe_u2_to_u1(a: u2) -> u1 { + let (high, low): (u1, u1) = ::into(a); + + assert!(jet::eq_1(high, 0)); + + low +} diff --git a/simf/lib/u4/bit.simf b/simf/lib/u4/bit.simf new file mode 100644 index 0000000..2bb28c1 --- /dev/null +++ b/simf/lib/u4/bit.simf @@ -0,0 +1,27 @@ +use crate::lib::u4::convert::u4_to_u8; + +/// Bitwise AND of two 4-bit values +pub fn and_4(a: u4, b: u4) -> u4 { + let a_u8: u8 = u4_to_u8(a); + let b_u8: u8 = u4_to_u8(b); + + let (_, result): (u4, u4) = ::into(jet::and_8(a_u8, b_u8)); + result +} + +/// Bitwise OR of two 4-bit values +pub fn or_4(a: u4, b: u4) -> u4 { + let a_u8: u8 = u4_to_u8(a); + let b_u8: u8 = u4_to_u8(b); + + let (_, result): (u4, u4) = ::into(jet::or_8(a_u8, b_u8)); + result +} + +/// Checks if two 4-bit values are equal +pub fn eq_4(a: u4, b: u4) -> bool { + let a_u8: u8 = u4_to_u8(a); + let b_u8: u8 = u4_to_u8(b); + + jet::eq_8(a_u8, b_u8) +} diff --git a/simf/lib/u4/convert.simf b/simf/lib/u4/convert.simf new file mode 100644 index 0000000..b3c7ba2 --- /dev/null +++ b/simf/lib/u4/convert.simf @@ -0,0 +1,61 @@ +/// Widening uint conversions + +/// Converts u4 to u8 +pub fn u4_to_u8(a: u4) -> u8 { + <(u4, u4)>::into((0, a)) +} + +/// Converts u4 to u16 +pub fn u4_to_u16(a: u4) -> u16 { + let a_u8: u8 = u4_to_u8(a); + + jet::left_pad_low_8_16(a_u8) +} + +/// Converts u4 to u32 +pub fn u4_to_u32(a: u4) -> u32 { + let a_u8: u8 = u4_to_u8(a); + + jet::left_pad_low_8_32(a_u8) +} + +/// Converts u4 to u64 +pub fn u4_to_u64(a: u4) -> u64 { + let a_u8: u8 = u4_to_u8(a); + + jet::left_pad_low_8_64(a_u8) +} + +/// Converts u4 to u128 +pub fn u4_to_u128(a: u4) -> u128 { + let a_u64: u64 = u4_to_u64(a); + + <(u64, u64)>::into((0, a_u64)) +} + +/// Converts u4 to u256 +pub fn u4_to_u256(a: u4) -> u256 { + let a_u128: u128 = u4_to_u128(a); + + <(u128, u128)>::into((0, a_u128)) +} + +/// Splitting uint conversions + +/// Splits u4 into four u1 +pub fn split_u4_into_u1(a: u4) -> (u1, u1, u1, u1) { + ::into(a) +} + +/// Narrowing uint conversions + +/// Converts u4 into u1. +/// Panics if the value does not fit in u1 +pub fn safe_u4_to_u1(a: u4) -> u1 { + let u1_max: u8 = jet::left_pad_low_1_8(jet::high_1()); + let a_u8: u8 = u4_to_u8(a); + + assert!(jet::le_8(a_u8, u1_max)); + + jet::rightmost_8_1(a_u8) +} diff --git a/simf/lib/utxo/asserts/script_hash.simf b/simf/lib/utxo/asserts/script_hash.simf new file mode 100644 index 0000000..87c8d77 --- /dev/null +++ b/simf/lib/utxo/asserts/script_hash.simf @@ -0,0 +1,30 @@ +use crate::lib::utxo::getters::script_hash::{ + get_script_hash, + get_input_script_hash, + get_output_script_hash +}; +use crate::lib::asserts::assert_eq_256; + +/// Asserts that the SHA256 hash of the scriptPubKey at the specified input or output equals the expected u256 value +/// +/// Warning: using this incurs overhead, and should be used only when the input/output selection +/// needs to be determined at runtime; otherwise, use the input- or output-specific assert directly. +pub fn assert_script_hash(index: u32, is_input_index: bool, expected: u256) { + let script_hash: u256 = get_script_hash(index, is_input_index); + + assert_eq_256(script_hash, expected); +} + +/// Asserts that the SHA256 hash of the scriptPubKey of the input at the given index equals the expected u256 value +pub fn assert_input_script_hash(index: u32, expected: u256) { + let script_hash: u256 = get_input_script_hash(index); + + assert_eq_256(script_hash, expected); +} + +/// Asserts that the SHA256 hash of the scriptPubKey of the output at the given index equals the expected u256 value +pub fn assert_output_script_hash(index: u32, expected: u256) { + let script_hash: u256 = get_output_script_hash(index); + + assert_eq_256(script_hash, expected); +} diff --git a/simf/lib/utxo/op_return.simf b/simf/lib/utxo/op_return.simf new file mode 100644 index 0000000..d308178 --- /dev/null +++ b/simf/lib/utxo/op_return.simf @@ -0,0 +1,119 @@ +pub type OpReturnEntry = Either<(u2, u256), Either>; + +/// Returns specified entry at the specified OP_RETURN output and entry index. +/// Returns `None` if the output doesn't exist, isn't null data, or has no such entry. +pub fn get_entry_option(output_index: u32, entry_index: u32) -> Option { + match jet::output_null_datum(output_index, entry_index) { + Some(entry_option: Option) => entry_option, + None => None, + } +} + +/// Returns `(x, hash)` from the specified entry if it is pushed data and `None` otherwise. +/// +/// `hash` is the SHA256 hash of the pushed data and `x` indicates how the data was pushed: +/// +/// 0 is an immediate 0 to 75 bytes, 1 is an OP_PUSHDATA1, +/// 2 is an OP_PUSHDATA2 and 3 is an OP_PUSHDATA4. +pub fn pushed_data_from_entry(entry: OpReturnEntry) -> Option<(u2, u256)> { + match entry { + Left(pushed_data: (u2, u256)) => Some(pushed_data), + Right(_other: Either) => None, + } +} + +/// Returns `(x, hash)` if the the entry at entry_index of the specified output is pushed data. +/// +/// `hash` is the SHA256 hash of the pushed data and `x` indicates how the data was pushed: +/// +/// 0 is an immediate 0 to 75 bytes, 1 is an OP_PUSHDATA1, +/// 2 is an OP_PUSHDATA2 and 3 is an OP_PUSHDATA4. +pub fn get_pushed_data(output_index: u32, entry_index: u32) -> Option<(u2, u256)> { + let entry: Option = get_entry_option(output_index, entry_index); + + match entry { + Some(entry: OpReturnEntry) => pushed_data_from_entry(entry), + None => None, + } +} + +/// Returns `x - 1` from the specified entry if it is `OP_x` for x in the range 1..=16 and `None` otherwise +pub fn op_x_minus_1_from_entry(entry: OpReturnEntry) -> Option { + match entry { + Left(_pushed_data: (u2, u256)) => None, + Right(right: Either) => match right { + Left(_negate_or_reserved: u1) => None, + Right(op_x_minus_1: u4) => Some(op_x_minus_1), + }, + } +} + +/// Returns `x - 1` if the specified entry at the specified output is `OP_x` for x in the range 1..=16 and `None` otherwise +pub fn get_op_x_minus_1(output_index: u32, entry_index: u32) -> Option { + let entry: Option = get_entry_option(output_index, entry_index); + + match entry { + Some(entry: OpReturnEntry) => op_x_minus_1_from_entry(entry), + None => None, + } +} + +/// Returns Some(0) if the specified entry is OP_1NEGATE, Some(1) if the entry is OP_RESERVED and `None` otherwise +pub fn negate_or_reserved_from_entry(entry: OpReturnEntry) -> Option { + match entry { + Left(_pushed_data: (u2, u256)) => None, + Right(right: Either) => match right { + Left(negate_or_reserved: u1) => Some(negate_or_reserved), + Right(_op_x_minus_1: u4) => None, + }, + } +} + +/// Returns Some(0) if the the entry at entry_index of the specified output is OP_1NEGATE, +/// Some(1) if the entry is OP_RESERVED and `None` otherwise +pub fn get_negate_or_reserved(output_index: u32, entry_index: u32) -> Option { + let entry: Option = get_entry_option(output_index, entry_index); + + match entry { + Some(entry: OpReturnEntry) => negate_or_reserved_from_entry(entry), + None => None, + } +} + +/// Returns the kind of the entry. +/// 0 is pushed data, 1 is OP_1..OP_16 and 2 is OP_1NEGATE or OP_RESERVED +pub fn kind_of_entry(entry: OpReturnEntry) -> u8 { + match entry { + Left(_pushed_data: (u2, u256)) => 0, + Right(right_branch: Either) => match right_branch { + Left(_negate_or_reserved: u1) => 2, + Right(_op_x_minus_1: u4) => 1, + }, + } +} + +/// Returns true if the specified output is an OP_RETURN (null data) output. +pub fn is_output_op_return(output_index: u32) -> bool { + match jet::output_null_datum(output_index, 0) { + Some(_entry_option: Option) => true, + None => false, + } +} + +/// Returns true if the specified output is an OP_RETURN output that has an entry at entry_index +pub fn has_entry(output_index: u32, entry_index: u32) -> bool { + match get_entry_option(output_index, entry_index) { + Some(_entry: OpReturnEntry) => true, + None => false, + } +} + +/// Asserts that the specified output is an OP_RETURN (null data) output +pub fn assert_output_is_op_return(output_index: u32) { + assert!(is_output_op_return(output_index)); +} + +/// Asserts that the specified output is an OP_RETURN output that has an entry at entry_index +pub fn assert_has_entry(output_index: u32, entry_index: u32) { + assert!(has_entry(output_index, entry_index)); +} diff --git a/simf/tests/mock.simf b/simf/tests/mock.simf index e71fdf5..f328e4d 100644 --- a/simf/tests/mock.simf +++ b/simf/tests/mock.simf @@ -1 +1 @@ -fn main() {} \ No newline at end of file +fn main() {} diff --git a/simf/tests/op_return.simf b/simf/tests/op_return.simf deleted file mode 100644 index d30a1d0..0000000 --- a/simf/tests/op_return.simf +++ /dev/null @@ -1,24 +0,0 @@ -use crate::lib::op_return::{is_output_op_return, assert_output_is_op_return}; - -use crate::tests::support::dispatch::is_selected; -use crate::tests::support::expect::assert_bool; - -fn main() { - let fn_idx: u8 = witness::FUNCTION_INDEX; - - let index: u32 = witness::INDEX; - let expected: bool = witness::EXPECTED; - - match is_selected(0, fn_idx) { - true => { - assert_bool(is_output_op_return(index), expected); - }, - false => (), - }; - match is_selected(1, fn_idx) { - true => { - assert_output_is_op_return(index); - }, - false => (), - }; -} diff --git a/simf/tests/u2/bit.simf b/simf/tests/u2/bit.simf new file mode 100644 index 0000000..c15887b --- /dev/null +++ b/simf/tests/u2/bit.simf @@ -0,0 +1,33 @@ +use crate::lib::u2::bit::{and_2, or_2, eq_2}; + +use crate::tests::support::dispatch::is_selected; +use crate::tests::support::expect::assert_bool; + +fn main() { + let fn_idx: u8 = witness::FUNCTION_INDEX; + + let a: u2 = witness::FIRST_ARG; + let b: u2 = witness::SECOND_ARG; + let expected: Option = witness::EXPECTED; + + let expected_bool: bool = witness::EXPECTED_BOOL; + + match is_selected(0, fn_idx) { + true => { + assert!(eq_2(and_2(a, b), unwrap(expected))); + }, + false => (), + }; + match is_selected(1, fn_idx) { + true => { + assert!(eq_2(or_2(a, b), unwrap(expected))); + }, + false => (), + }; + match is_selected(2, fn_idx) { + true => { + assert_bool(eq_2(a, b), expected_bool); + }, + false => (), + }; +} diff --git a/simf/tests/u2/convert.simf b/simf/tests/u2/convert.simf new file mode 100644 index 0000000..486bf8f --- /dev/null +++ b/simf/tests/u2/convert.simf @@ -0,0 +1,102 @@ +use crate::lib::u2::convert::{ + u2_to_u8, + u2_to_u16, + u2_to_u32, + u2_to_u64, + u2_to_u128, + u2_to_u256, + split_u2_into_u1, + safe_u2_to_u1 +}; +use crate::lib::asserts::{ + assert_eq_1, + assert_eq_8, + assert_eq_16, + assert_eq_32, + assert_eq_64, + assert_eq_128, + assert_eq_256 +}; + +use crate::tests::support::dispatch::is_selected; + +fn main() { + let fn_idx: u8 = witness::FUNCTION_INDEX; + + let a: u2 = witness::FIRST_ARG; + + let expected: u256 = witness::EXPECTED; + let (_, _, _, expected_64): (u64, u64, u64, u64) = ::into(expected); + + match is_selected(0, fn_idx) { + true => { + let expected: u8 = jet::rightmost_64_8(expected_64); + + assert_eq_8(u2_to_u8(a), expected); + }, + false => (), + }; + + match is_selected(1, fn_idx) { + true => { + let expected: u16 = jet::rightmost_64_16(expected_64); + + assert_eq_16(u2_to_u16(a), expected); + }, + false => (), + }; + + match is_selected(2, fn_idx) { + true => { + let expected: u32 = jet::rightmost_64_32(expected_64); + + assert_eq_32(u2_to_u32(a), expected); + }, + false => (), + }; + + match is_selected(3, fn_idx) { + true => { + assert_eq_64(u2_to_u64(a), expected_64); + }, + false => (), + }; + + match is_selected(4, fn_idx) { + true => { + let (_, expected): (u128, u128) = ::into(expected); + + assert_eq_128(u2_to_u128(a), expected); + }, + false => (), + }; + + match is_selected(5, fn_idx) { + true => { + assert_eq_256(u2_to_u256(a), expected); + }, + false => (), + }; + + match is_selected(6, fn_idx) { + true => { + let (a1, a0): (u1, u1) = split_u2_into_u1(a); + + let expected: u2 = jet::rightmost_64_2(expected_64); + let (expected1, expected0): (u1, u1) = ::into(expected); + + assert_eq_1(a1, expected1); + assert_eq_1(a0, expected0); + }, + false => (), + }; + + match is_selected(7, fn_idx) { + true => { + let expected: u1 = jet::rightmost_64_1(expected_64); + + assert_eq_1(safe_u2_to_u1(a), expected); + }, + false => (), + }; +} diff --git a/simf/tests/u4/bit.simf b/simf/tests/u4/bit.simf new file mode 100644 index 0000000..9dd9435 --- /dev/null +++ b/simf/tests/u4/bit.simf @@ -0,0 +1,33 @@ +use crate::lib::u4::bit::{and_4, or_4, eq_4}; + +use crate::tests::support::dispatch::is_selected; +use crate::tests::support::expect::assert_bool; + +fn main() { + let fn_idx: u8 = witness::FUNCTION_INDEX; + + let a: u4 = witness::FIRST_ARG; + let b: u4 = witness::SECOND_ARG; + let expected: Option = witness::EXPECTED; + + let expected_bool: bool = witness::EXPECTED_BOOL; + + match is_selected(0, fn_idx) { + true => { + assert!(eq_4(and_4(a, b), unwrap(expected))); + }, + false => (), + }; + match is_selected(1, fn_idx) { + true => { + assert!(eq_4(or_4(a, b), unwrap(expected))); + }, + false => (), + }; + match is_selected(2, fn_idx) { + true => { + assert_bool(eq_4(a, b), expected_bool); + }, + false => (), + }; +} diff --git a/simf/tests/u4/convert.simf b/simf/tests/u4/convert.simf new file mode 100644 index 0000000..1765d30 --- /dev/null +++ b/simf/tests/u4/convert.simf @@ -0,0 +1,109 @@ +use crate::lib::u4::convert::{ + u4_to_u8, + u4_to_u16, + u4_to_u32, + u4_to_u64, + u4_to_u128, + u4_to_u256, + split_u4_into_u1, + safe_u4_to_u1 +}; +use crate::lib::asserts::{ + assert_eq_1, + assert_eq_8, + assert_eq_16, + assert_eq_32, + assert_eq_64, + assert_eq_128, + assert_eq_256 +}; + +use crate::tests::support::dispatch::is_selected; + +fn main() { + let fn_idx: u8 = witness::FUNCTION_INDEX; + + let a: u4 = witness::FIRST_ARG; + + let expected: u256 = witness::EXPECTED; + let (_, _, _, expected_64): (u64, u64, u64, u64) = ::into(expected); + + match is_selected(0, fn_idx) { + true => { + let expected: u8 = jet::rightmost_64_8(expected_64); + + assert_eq_8(u4_to_u8(a), expected); + }, + false => (), + }; + + match is_selected(1, fn_idx) { + true => { + let expected: u16 = jet::rightmost_64_16(expected_64); + + assert_eq_16(u4_to_u16(a), expected); + }, + false => (), + }; + + match is_selected(2, fn_idx) { + true => { + let expected: u32 = jet::rightmost_64_32(expected_64); + + assert_eq_32(u4_to_u32(a), expected); + }, + false => (), + }; + + match is_selected(3, fn_idx) { + true => { + assert_eq_64(u4_to_u64(a), expected_64); + }, + false => (), + }; + + match is_selected(4, fn_idx) { + true => { + let (_, expected): (u128, u128) = ::into(expected); + + assert_eq_128(u4_to_u128(a), expected); + }, + false => (), + }; + + match is_selected(5, fn_idx) { + true => { + assert_eq_256(u4_to_u256(a), expected); + }, + false => (), + }; + + match is_selected(6, fn_idx) { + true => { + let (a3, a2, a1, a0): (u1, u1, u1, u1) = split_u4_into_u1(a); + + let expected: u4 = jet::rightmost_64_4(expected_64); + let ( + expected3, + expected2, + expected1, + expected0 + ): (u1, u1, u1, u1) = ::into(expected); + + assert_eq_1(a3, expected3); + assert_eq_1(a2, expected2); + assert_eq_1(a1, expected1); + assert_eq_1(a0, expected0); + }, + false => (), + }; + + match is_selected(7, fn_idx) { + true => { + let expected: u1 = jet::rightmost_64_1(expected_64); + + assert_eq_1(safe_u4_to_u1(a), expected); + }, + false => (), + }; +} diff --git a/simf/tests/utxo/asserts/script_hash.simf b/simf/tests/utxo/asserts/script_hash.simf new file mode 100644 index 0000000..4f10f6e --- /dev/null +++ b/simf/tests/utxo/asserts/script_hash.simf @@ -0,0 +1,37 @@ +use crate::lib::utxo::asserts::script_hash::{ + assert_script_hash, + assert_input_script_hash, + assert_output_script_hash +}; + +use crate::tests::support::dispatch::is_selected; + +fn main() { + let fn_idx: u8 = witness::FUNCTION_INDEX; + + let index: u32 = witness::INDEX; + let is_input_index: bool = witness::IS_INPUT_INDEX; + + let expected_hash: u256 = witness::EXPECTED; + + match is_selected(0, fn_idx) { + true => { + assert_script_hash(index, is_input_index, expected_hash); + }, + false => (), + }; + + match is_selected(1, fn_idx) { + true => { + assert_input_script_hash(index, expected_hash); + }, + false => (), + }; + + match is_selected(2, fn_idx) { + true => { + assert_output_script_hash(index, expected_hash); + }, + false => (), + }; +} diff --git a/simf/tests/utxo/getters/amount.simf b/simf/tests/utxo/getters/amount.simf index 08b6b0a..9e543c5 100644 --- a/simf/tests/utxo/getters/amount.simf +++ b/simf/tests/utxo/getters/amount.simf @@ -36,7 +36,7 @@ fn main() { confidential_amount ): Confidential1 = get_confidential_amount(index, is_input_index); - assert_eq_1(parity_bit,expected_parity); + assert_eq_1(parity_bit, expected_parity); assert_eq_256(confidential_amount, expected_confidential_amount); }, false => (), @@ -56,7 +56,7 @@ fn main() { confidential_amount ): Confidential1 = get_confidential_input_amount(index); - assert_eq_1(parity_bit,expected_parity); + assert_eq_1(parity_bit, expected_parity); assert_eq_256(confidential_amount, expected_confidential_amount); }, false => (), @@ -76,7 +76,7 @@ fn main() { confidential_amount ): Confidential1 = get_confidential_output_amount(index); - assert_eq_1(parity_bit,expected_parity); + assert_eq_1(parity_bit, expected_parity); assert_eq_256(confidential_amount, expected_confidential_amount); }, false => (), @@ -96,7 +96,7 @@ fn main() { confidential_amount ): Confidential1 = get_confidential_current_amount(); - assert_eq_1(parity_bit,expected_parity); + assert_eq_1(parity_bit, expected_parity); assert_eq_256(confidential_amount, expected_confidential_amount); }, false => (), diff --git a/simf/tests/utxo/op_return.simf b/simf/tests/utxo/op_return.simf new file mode 100644 index 0000000..0c41af6 --- /dev/null +++ b/simf/tests/utxo/op_return.simf @@ -0,0 +1,225 @@ +use crate::lib::utxo::op_return::{ + OpReturnEntry, + get_entry_option, + pushed_data_from_entry, + get_pushed_data, + op_x_minus_1_from_entry, + get_op_x_minus_1, + negate_or_reserved_from_entry, + get_negate_or_reserved, + kind_of_entry, + is_output_op_return, + has_entry, + assert_output_is_op_return, + assert_has_entry +}; + +use crate::lib::u2::bit::eq_2; +use crate::lib::u4::bit::eq_4; +use crate::lib::asserts::assert_eq_8; +use crate::lib::u8::convert::safe_u8_to_u1; + +use crate::tests::support::dispatch::is_selected; +use crate::tests::support::expect::assert_bool; + +fn eq_pushed_data(a: (u2, u256), b: (u2, u256)) -> bool { + let (indicator_a, hash_a): (u2, u256) = a; + let (indicator_b, hash_b): (u2, u256) = b; + + match eq_2(indicator_a, indicator_b) { + true => jet::eq_256(hash_a, hash_b), + false => false, + } +} + +fn eq_entry(a: OpReturnEntry, b: OpReturnEntry) -> bool { + match a { + Left(push_a: (u2, u256)) => match b { + Left(push_b: (u2, u256)) => eq_pushed_data(push_a, push_b), + Right(_other: Either) => false, + }, + Right(right_a: Either) => match b { + Left(_push_b: (u2, u256)) => false, + Right(right_b: Either) => match right_a { + Left(neg_or_res_a: u1) => match right_b { + Left(neg_or_res_b: u1) => jet::eq_1(neg_or_res_a, neg_or_res_b), + Right(_op_x_minus_1: u4) => false, + }, + Right(op_x_minus_1_a: u4) => match right_b { + Left(_neg_or_res: u1) => false, + Right(op_x_minus_1_b: u4) => eq_4(op_x_minus_1_a, op_x_minus_1_b), + }, + }, + }, + } +} + +fn eq_entry_option(a: Option, b: Option) -> bool { + match a { + Some(entry_a: OpReturnEntry) => match b { + Some(entry_b: OpReturnEntry) => eq_entry(entry_a, entry_b), + None => false, + }, + None => match b { + Some(_b: OpReturnEntry) => false, + None => true, + }, + } +} + +fn assert_pushed_data( + expected_value: Option, + expected_hash: u256, + actual_pushed_data: Option<(u2, u256)> +) { + match expected_value { + Some(expected: u8) => { + let (_, _, _, expected_indicator): (u2, u2, u2, u2) = ::into(expected); + + assert!( + eq_pushed_data(unwrap(actual_pushed_data), (expected_indicator, expected_hash)) + ); + }, + None => { + assert!(is_none::<(u2, u256)>(actual_pushed_data)); + }, + } +} + +fn assert_op_x_minus_1(expected_value: Option, actual_op_x_minus_1: Option) { + match expected_value { + Some(expected: u8) => { + let (_, expected): (u4, u4) = ::into(expected); + assert!(eq_4(unwrap(actual_op_x_minus_1), expected)); + }, + None => { + assert!(is_none::(actual_op_x_minus_1)); + }, + } +} + +fn assert_neg_or_res(expected_value: Option, actual_neg_or_res: Option) { + match expected_value { + Some(expected: u8) => { + let expected: u1 = safe_u8_to_u1(expected); + assert!(jet::eq_1(unwrap(actual_neg_or_res), expected)); + }, + None => { + assert!(is_none::(actual_neg_or_res)); + }, + } +} + +fn main() { + let fn_idx: u8 = witness::FUNCTION_INDEX; + + let output_index: u32 = witness::OUTPUT_INDEX; + let entry_index: u32 = witness::ENTRY_INDEX; + let entry: OpReturnEntry = witness::ENTRY; + + let expected_entry: Option = witness::EXPECTED_ENTRY; + let expected_bool: bool = witness::EXPECTED_BOOL; + + let expected_value: Option = witness::expected_value; + let expected_hash: u256 = witness::EXPECTED_HASH; + + match is_selected(0, fn_idx) { + true => { + let result_entry: Option = get_entry_option(output_index, entry_index); + + assert!(eq_entry_option(result_entry, expected_entry)); + }, + false => (), + }; + + match is_selected(1, fn_idx) { + true => { + let pushed_data: Option<(u2, u256)> = pushed_data_from_entry(entry); + + assert_pushed_data(expected_value, expected_hash, pushed_data); + }, + false => (), + }; + + match is_selected(2, fn_idx) { + true => { + let pushed_data: Option<(u2, u256)> = get_pushed_data(output_index, entry_index); + + assert_pushed_data(expected_value, expected_hash, pushed_data); + }, + false => (), + }; + + match is_selected(3, fn_idx) { + true => { + let op_x_minus_1: Option = op_x_minus_1_from_entry(entry); + + assert_op_x_minus_1(expected_value, op_x_minus_1); + }, + false => (), + }; + + match is_selected(4, fn_idx) { + true => { + let op_x_minus_1: Option = get_op_x_minus_1(output_index, entry_index); + + assert_op_x_minus_1(expected_value, op_x_minus_1); + }, + false => (), + }; + + match is_selected(5, fn_idx) { + true => { + let neg_or_res: Option = negate_or_reserved_from_entry(entry); + + assert_neg_or_res(expected_value, neg_or_res); + }, + false => (), + }; + + match is_selected(6, fn_idx) { + true => { + let neg_or_res: Option = get_negate_or_reserved(output_index, entry_index); + + assert_neg_or_res(expected_value, neg_or_res); + }, + false => (), + }; + + match is_selected(7, fn_idx) { + true => { + let kind: u8 = kind_of_entry(entry); + + assert_eq_8(kind, unwrap(expected_value)); + }, + false => (), + }; + + match is_selected(8, fn_idx) { + true => { + assert_bool(is_output_op_return(output_index), expected_bool); + }, + false => (), + }; + + match is_selected(9, fn_idx) { + true => { + assert_bool(has_entry(output_index, entry_index), expected_bool); + }, + false => (), + }; + + match is_selected(10, fn_idx) { + true => { + assert_output_is_op_return(output_index); + }, + false => (), + }; + + match is_selected(11, fn_idx) { + true => { + assert_has_entry(output_index, entry_index); + }, + false => (), + }; +} diff --git a/tests/stdlib/common/core.rs b/tests/stdlib/common/core.rs index f2ed63c..b596c81 100644 --- a/tests/stdlib/common/core.rs +++ b/tests/stdlib/common/core.rs @@ -3,14 +3,19 @@ #![allow(dead_code)] use secp256k1_zkp::Secp256k1; -use simplex::program::{Program, WitnessTrait}; + +use simplex::program::Program; use simplex::signer::Signer; +use simplex::simplicityhl::WitnessValues; use simplex::simplicityhl::elements::bitcoin::PublicKey; +use simplex::simplicityhl::elements::opcodes::{self, All}; +use simplex::simplicityhl::elements::script::Builder; use simplex::simplicityhl::elements::{AssetId, Script, Txid}; use simplex::transaction::partial_input::IssuanceInput; use simplex::transaction::{ - FinalTransaction, PartialInput, PartialOutput, ProgramInput, RequiredSignature, UTXO, + FinalTransaction, NativeSignature, PartialInput, PartialOutput, ProgramInput, UTXO, }; + use simplicityhl_std::artifacts::tests::mock::derived_mock::MockWitness; use crate::common::utxo_helper::{DEFAULT_SEND_AMOUNT, search_utxo_by_txid}; @@ -91,14 +96,18 @@ pub fn send_issued( let funding = signer .get_utxos_asset(context.get_network().policy_asset())? .into_iter() - .max_by_key(UTXO::amount) + .map(|utxo| Ok((utxo.amount()?, utxo))) + .collect::>>()? + .into_iter() + .max_by_key(|(amount, _)| *amount) + .map(|(_, utxo)| utxo) .ok_or_else(|| anyhow::anyhow!("no policy asset UTXO to attach the issuance to"))?; let mut ft = FinalTransaction::new(); let issued = ft.add_issuance_input( PartialInput::new(funding), IssuanceInput::new_issuance(amount_to_send, 0, rand::random()), - RequiredSignature::NativeEcdsa, + Some(NativeSignature::Ecdsa), ); ft.add_output(PartialOutput::new( to.clone(), @@ -121,7 +130,7 @@ pub fn construct_final_tx( program_for_test: Option, ) -> anyhow::Result where - W: WitnessTrait + 'static, + W: Into + 'static, { let signer = context.get_default_signer(); let network = context.get_network(); @@ -136,8 +145,8 @@ where ft.add_program_input( PartialInput::new(program_utxo.clone()), - ProgramInput::new(Box::new(program.as_ref().clone()), Box::new(witness)), - RequiredSignature::None, + ProgramInput::new(Box::new(program.as_ref().clone()), witness.into()), + None, ); if data.unwrap_or_default().is_empty() { @@ -146,7 +155,7 @@ where &program_utxo, signer.get_blinding_public_key(), policy_asset, - ); + )?; } if txids.len() > 1 { @@ -156,9 +165,12 @@ where for txid in &txids[1..last] { let utxo = search_utxo_by_txid(txid, &unblinded_utxos)?; - ft.add_input(PartialInput::new(utxo.clone()), RequiredSignature::None); + ft.add_input( + PartialInput::new(utxo.clone()), + Some(NativeSignature::Ecdsa), + ); - (ft, _) = add_mirror_output(ft, &utxo, signer.get_blinding_public_key(), policy_asset); + (ft, _) = add_mirror_output(ft, &utxo, signer.get_blinding_public_key(), policy_asset)?; } let last_utxo = match program_for_test { @@ -168,8 +180,8 @@ where ft.add_program_input( PartialInput::new(last_utxo.clone()), - ProgramInput::new(Box::new(program_for_test.clone()), Box::new(MockWitness {})), - RequiredSignature::None, + ProgramInput::new(Box::new(program_for_test.clone()), MockWitness {}), + None, ); last_utxo @@ -179,7 +191,7 @@ where ft.add_input( PartialInput::new(last_utxo.clone()), - RequiredSignature::None, + Some(NativeSignature::Ecdsa), ); last_utxo @@ -191,7 +203,7 @@ where &last_utxo, signer.get_blinding_public_key(), policy_asset, - ); + )?; } if let Some(leftover_asset) = leftover_asset { @@ -199,12 +211,32 @@ where } if let Some(data) = data { - ft.add_output(PartialOutput::new_metadata(data)) + ft.add_output(PartialOutput::new( + create_op_return_script(data), + 0, + AssetId::default(), + )); }; Ok(ft) } +/// Create an op return script from bytes, where the first byte indicates whether +/// the bytes are an immediate push or raw opcodes +fn create_op_return_script(data: &[u8]) -> Script { + let mut builder = Builder::new().push_opcode(opcodes::all::OP_RETURN); + + if data[0] == 0x00 { + builder = builder.push_slice(&data[1..]); + } else { + for i in &data[1..] { + builder = builder.push_opcode(All::from(*i)); + } + } + + builder.into_script() +} + /// Fetch the UTXO for script, unblind if confidential. fn prepare_utxo( context: &simplex::TestContext, @@ -239,33 +271,33 @@ fn unblind(signer: &Signer, mut utxo: UTXO) -> anyhow::Result { Ok(utxo) } -// Add an output mirroring utxo's asset with amount - 1 and the tweaked script. +/// Add an output mirroring utxo's asset with amount - 1 and the tweaked script. fn add_mirror_output( mut ft: FinalTransaction, utxo: &UTXO, - binding_key: PublicKey, + blinding_key: PublicKey, policy_asset: AssetId, -) -> (FinalTransaction, Option) { +) -> anyhow::Result<(FinalTransaction, Option)> { let mut output = PartialOutput::new( // scripts in input and output on the same index should be different for the test tweak_script(&utxo.txout.script_pubkey), // amounts in input and output on the same index should be different for the test - utxo.amount() - 1, - utxo.asset(), + utxo.amount()? - 1, + utxo.asset()?, ); if utxo.txout.value.is_confidential() { - output = output.with_blinding_key(binding_key) + output = output.with_blinding_key(blinding_key) } ft.add_output(output); - let leftover_asset = match utxo.asset() == policy_asset { + let leftover_asset = match utxo.asset()? == policy_asset { true => None, - false => Some(utxo.asset()), + false => Some(utxo.asset()?), }; - (ft, leftover_asset) + Ok((ft, leftover_asset)) } /// Change the lowest bit of the last byte (used for test purposes). @@ -288,7 +320,7 @@ pub fn spend( program_for_test: Option, ) -> anyhow::Result where - W: WitnessTrait + 'static, + W: Into + 'static, { let ft = construct_final_tx(context, program, witness, data, txids, program_for_test)?; @@ -323,7 +355,7 @@ pub fn run( expect: Expect, ) -> anyhow::Result<()> where - W: WitnessTrait + 'static, + W: Into + 'static, { fund(context, &program, DEFAULT_SEND_AMOUNT)?; let result = spend(context, &program, witness, None, Vec::new(), None); @@ -341,7 +373,7 @@ pub fn run_with_op_return( data: &[u8], ) -> anyhow::Result<()> where - W: WitnessTrait + 'static, + W: Into + 'static, { fund(context, &program, DEFAULT_SEND_AMOUNT)?; let result = spend(context, &program, witness, Some(data), Vec::new(), None); @@ -359,7 +391,7 @@ pub fn run_with_inputs_outputs( program_for_test: Option, ) -> anyhow::Result<()> where - W: WitnessTrait + 'static, + W: Into + 'static, { let result = spend(context, &program, witness, None, txids, program_for_test); diff --git a/tests/stdlib/common/uint.rs b/tests/stdlib/common/uint.rs index 1169c33..ab997d1 100644 --- a/tests/stdlib/common/uint.rs +++ b/tests/stdlib/common/uint.rs @@ -8,7 +8,8 @@ use std::ops::{Add, Div, Mul, Sub}; use rand::Rng; use rand::distributions::uniform::SampleUniform; -use simplex::program::{Program, WitnessTrait}; +use simplex::program::Program; +use simplex::simplicityhl::WitnessValues; use crate::common::u256_wrapper::U256Wrapper; @@ -47,7 +48,7 @@ pub trait TestUint: + Div { type Program: AsRef; - type Witness: WitnessTrait + 'static; + type Witness: Into + 'static; const ZERO: Self; const ONE: Self; diff --git a/tests/stdlib/common/utxo_helper.rs b/tests/stdlib/common/utxo_helper.rs index 90cad21..3baa243 100644 --- a/tests/stdlib/common/utxo_helper.rs +++ b/tests/stdlib/common/utxo_helper.rs @@ -1,6 +1,5 @@ use primitive_types::U256; use rand::Rng; -use secp256k1_zkp::hashes::Hash; use simplex::program::Program; use simplex::simplicityhl::elements::AssetId; @@ -184,7 +183,7 @@ pub fn create_utxos_with_new_asset( } } - Ok((new_asset.unwrap().into_inner().0, txids)) + Ok((new_asset.unwrap().to_byte_array(), txids)) } fn get_asset_id_amount_from_conf_utxo(conf_utxo: UTXO) -> anyhow::Result { diff --git a/tests/stdlib/main.rs b/tests/stdlib/main.rs index 04a23cb..a5c2be3 100644 --- a/tests/stdlib/main.rs +++ b/tests/stdlib/main.rs @@ -2,13 +2,14 @@ mod common; mod asserts; mod binary; -mod op_return; mod secp256k1; mod u1; mod u128; mod u16; +mod u2; mod u256; mod u32; +mod u4; mod u64; mod u8; mod utxo; diff --git a/tests/stdlib/op_return.rs b/tests/stdlib/op_return.rs deleted file mode 100644 index 2b83226..0000000 --- a/tests/stdlib/op_return.rs +++ /dev/null @@ -1,131 +0,0 @@ -use rand::Rng; - -use crate::common::core::{Expect, run, run_with_op_return}; - -use simplicityhl_std::artifacts::tests::op_return::OpReturnProgram as OpReturnTestProgram; -use simplicityhl_std::artifacts::tests::op_return::derived_op_return::{ - OpReturnArguments as OpReturnTestArguments, OpReturnWitness as OpReturnTestWitness, -}; - -use FunctionToTest::*; - -enum FunctionToTest { - IsOpReturn, - AssertOutputIsOpReturn, -} - -const DEFAULT_DATA: &[u8; 1] = &[1]; - -fn program() -> OpReturnTestProgram { - OpReturnTestProgram::new(&OpReturnTestArguments {}) -} - -/// One dispatch arm of the contract, plus the witness it reads and whether the -/// spend carries an `OP_RETURN` output. -struct Case { - witness: OpReturnTestWitness, - data: Option<&'static [u8]>, -} - -fn case(function: FunctionToTest) -> Case { - Case { - witness: OpReturnTestWitness { - function_index: function as u8, - index: 0, - expected: false, - }, - data: None, - } -} - -impl Case { - /// `index`: the output the arm inspects. - fn index(mut self, index: u32) -> Self { - self.witness.index = index; - self - } - - /// `expected`: the boolean the arm should report. - fn flag(mut self, flag: bool) -> Self { - self.witness.expected = flag; - self - } - - /// Add an `OP_RETURN` output carrying `data` to the spend. - fn op_return(mut self, data: &'static [u8]) -> Self { - self.data = Some(data); - self - } - - /// Fund, spend, and expect the spend to succeed. - fn run(self, context: &simplex::TestContext) -> anyhow::Result<()> { - self.expecting(context, Expect::Ok) - } - - /// Fund, spend, and expect `expect`. - fn expecting(self, context: &simplex::TestContext, expect: Expect) -> anyhow::Result<()> { - match self.data { - Some(data) => run_with_op_return(context, program(), self.witness, expect, data), - None => run(context, program(), self.witness, expect), - } - } -} - -#[simplex::test] -fn is_output_op_return_true(context: simplex::TestContext) -> anyhow::Result<()> { - let index = 0; - - case(IsOpReturn) - .index(index) - .flag(true) - .op_return(DEFAULT_DATA) - .run(&context) -} - -#[simplex::test] -fn is_output_op_return_empty_index_false(context: simplex::TestContext) -> anyhow::Result<()> { - let index = rand::thread_rng().gen_range(1..=u32::MAX); - - case(IsOpReturn) - .index(index) - .op_return(DEFAULT_DATA) - .run(&context) -} - -#[simplex::test] -fn is_output_op_return_false(context: simplex::TestContext) -> anyhow::Result<()> { - let index = 0; - - case(IsOpReturn).index(index).run(&context) -} - -#[simplex::test] -fn assert_output_is_op_return_pass(context: simplex::TestContext) -> anyhow::Result<()> { - let index = 0; - - case(AssertOutputIsOpReturn) - .index(index) - .op_return(DEFAULT_DATA) - .run(&context) -} - -#[simplex::test] -fn assert_output_is_op_return_empty_index_fail( - context: simplex::TestContext, -) -> anyhow::Result<()> { - let index = rand::thread_rng().gen_range(1..=u32::MAX); - - case(AssertOutputIsOpReturn) - .index(index) - .op_return(DEFAULT_DATA) - .expecting(&context, Expect::AssertFailed) -} - -#[simplex::test] -fn assert_output_is_op_return_fail(context: simplex::TestContext) -> anyhow::Result<()> { - let index = 0; - - case(AssertOutputIsOpReturn) - .index(index) - .expecting(&context, Expect::AssertFailed) -} diff --git a/tests/stdlib/u2/bit.rs b/tests/stdlib/u2/bit.rs new file mode 100644 index 0000000..b642949 --- /dev/null +++ b/tests/stdlib/u2/bit.rs @@ -0,0 +1,107 @@ +use rand::Rng; + +use crate::common::core::{Expect, run}; + +use simplicityhl_std::artifacts::tests::u2::bit::BitProgram as U2TestBitProgram; +use simplicityhl_std::artifacts::tests::u2::bit::derived_bit::{ + BitArguments as U2TestBitArguments, BitWitness as U2TestBitWitness, +}; + +use FunctionToTest::*; + +enum FunctionToTest { + And2, + Or2, + Eq2, +} + +const U2_MAX: u8 = 3; + +fn program() -> U2TestBitProgram { + U2TestBitProgram::new(&U2TestBitArguments {}) +} + +/// One dispatch arm of the contract, plus the witness it reads. +struct Case { + witness: U2TestBitWitness, +} + +fn case(function: FunctionToTest) -> Case { + Case { + witness: U2TestBitWitness { + function_index: function as u8, + first_arg: 0, + second_arg: 0, + expected: None, + expected_bool: false, + }, + } +} + +impl Case { + /// The two operands, `first_arg` and `second_arg`. + fn args(mut self, a: u8, b: u8) -> Self { + self.witness.first_arg = a; + self.witness.second_arg = b; + self + } + + /// The u2 value the arm should return + fn expect(mut self, expected: u8) -> Self { + self.witness.expected = Some(expected); + self + } + + /// `expected_bool`: the boolean the arm should report. + fn expected_bool(mut self, expected_bool: bool) -> Self { + self.witness.expected_bool = expected_bool; + self + } + + /// Fund, spend, and expect the spend to succeed. + fn run(self, context: &simplex::TestContext) -> anyhow::Result<()> { + self.expecting(context, Expect::Ok) + } + + /// Fund, spend, and expect `expect`. + fn expecting(self, context: &simplex::TestContext, expect: Expect) -> anyhow::Result<()> { + run(context, program(), self.witness, expect) + } +} + +#[simplex::test] +fn and_2(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=U2_MAX); + let b = rand::thread_rng().gen_range(0..=U2_MAX); + let result = a & b; + + case(And2).args(a, b).expect(result).run(&context) +} + +#[simplex::test] +fn or_2(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=U2_MAX); + let b = rand::thread_rng().gen_range(0..=U2_MAX); + let result = a | b; + + case(Or2).args(a, b).expect(result).run(&context) +} + +#[simplex::test] +fn eq_2_true(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=U2_MAX); + + case(Eq2).args(a, a).expected_bool(true).run(&context) +} + +#[simplex::test] +fn eq_2_false(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=U2_MAX); + let mut b = rand::thread_rng().gen_range(0..=U2_MAX); + + while a == b { + b = rand::thread_rng().gen_range(0..=U2_MAX); + } + + case(Eq2).args(a, b).expected_bool(false).run(&context) +} diff --git a/tests/stdlib/u2/convert.rs b/tests/stdlib/u2/convert.rs new file mode 100644 index 0000000..b48d307 --- /dev/null +++ b/tests/stdlib/u2/convert.rs @@ -0,0 +1,157 @@ +use primitive_types::U256; +use rand::Rng; + +use crate::common::core::{Expect, run}; + +use simplicityhl_std::artifacts::tests::u2::convert::ConvertProgram as U2ConvertTestProgram; +use simplicityhl_std::artifacts::tests::u2::convert::derived_convert::{ + ConvertArguments as U2ConvertTestArguments, ConvertWitness as U2ConvertTestWitness, +}; + +use FunctionToTest::*; + +enum FunctionToTest { + U2ToU8, + U2ToU16, + U2ToU32, + U2ToU64, + U2ToU128, + U2ToU256, + SplitU2IntoU1, + SafeU2ToU1, +} + +const U2_MAX: u8 = 3; + +fn program() -> U2ConvertTestProgram { + U2ConvertTestProgram::new(&U2ConvertTestArguments {}) +} + +/// One dispatch arm of the contract, plus the witness it reads. +struct Case { + witness: U2ConvertTestWitness, +} + +fn case(function: FunctionToTest) -> Case { + Case { + witness: U2ConvertTestWitness { + function_index: function as u8, + first_arg: 0, + expected: [0; 32], + }, + } +} + +impl Case { + /// The operand, `first_arg`. + fn arg(mut self, a: u8) -> Self { + self.witness.first_arg = a; + self + } + + /// `expected`: the value the arm should produce. + fn expect(mut self, expected: [u8; 32]) -> Self { + self.witness.expected = expected; + self + } + + /// Fund, spend, and expect the spend to succeed. + fn run(self, context: &simplex::TestContext) -> anyhow::Result<()> { + self.expecting(context, Expect::Ok) + } + + /// Fund, spend, and expect `expect`. + fn expecting(self, context: &simplex::TestContext, expect: Expect) -> anyhow::Result<()> { + run(context, program(), self.witness, expect) + } +} + +#[simplex::test] +fn u2_to_u8(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=U2_MAX); + + case(U2ToU8) + .arg(a) + .expect(U256::from(a).to_big_endian()) + .run(&context) +} + +#[simplex::test] +fn u2_to_u16(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=U2_MAX); + + case(U2ToU16) + .arg(a) + .expect(U256::from(a).to_big_endian()) + .run(&context) +} + +#[simplex::test] +fn u2_to_u32(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=U2_MAX); + + case(U2ToU32) + .arg(a) + .expect(U256::from(a).to_big_endian()) + .run(&context) +} + +#[simplex::test] +fn u2_to_u64(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=U2_MAX); + + case(U2ToU64) + .arg(a) + .expect(U256::from(a).to_big_endian()) + .run(&context) +} + +#[simplex::test] +fn u2_to_u128(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=U2_MAX); + + case(U2ToU128) + .arg(a) + .expect(U256::from(a).to_big_endian()) + .run(&context) +} + +#[simplex::test] +fn u2_to_u256(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=U2_MAX); + + case(U2ToU256) + .arg(a) + .expect(U256::from(a).to_big_endian()) + .run(&context) +} + +#[simplex::test] +fn split_u2_into_u1(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=U2_MAX); + + case(SplitU2IntoU1) + .arg(a) + .expect(U256::from(a).to_big_endian()) + .run(&context) +} + +#[simplex::test] +fn safe_u2_to_u1(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=1); + + case(SafeU2ToU1) + .arg(a) + .expect(U256::from(a).to_big_endian()) + .run(&context) +} + +#[simplex::test] +fn safe_u2_to_u1_overflow(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(2..=U2_MAX); + + case(SafeU2ToU1) + .arg(a) + .expect(U256::from(a).to_big_endian()) + .expecting(&context, Expect::AssertFailed) +} diff --git a/tests/stdlib/u2/mod.rs b/tests/stdlib/u2/mod.rs new file mode 100644 index 0000000..61da102 --- /dev/null +++ b/tests/stdlib/u2/mod.rs @@ -0,0 +1,2 @@ +mod bit; +mod convert; diff --git a/tests/stdlib/u4/bit.rs b/tests/stdlib/u4/bit.rs new file mode 100644 index 0000000..772ac1c --- /dev/null +++ b/tests/stdlib/u4/bit.rs @@ -0,0 +1,107 @@ +use rand::Rng; + +use crate::common::core::{Expect, run}; + +use simplicityhl_std::artifacts::tests::u4::bit::BitProgram as U4TestBitProgram; +use simplicityhl_std::artifacts::tests::u4::bit::derived_bit::{ + BitArguments as U4TestBitArguments, BitWitness as U4TestBitWitness, +}; + +use FunctionToTest::*; + +enum FunctionToTest { + And4, + Or4, + Eq4, +} + +const U4_MAX: u8 = 15; + +fn program() -> U4TestBitProgram { + U4TestBitProgram::new(&U4TestBitArguments {}) +} + +/// One dispatch arm of the contract, plus the witness it reads. +struct Case { + witness: U4TestBitWitness, +} + +fn case(function: FunctionToTest) -> Case { + Case { + witness: U4TestBitWitness { + function_index: function as u8, + first_arg: 0, + second_arg: 0, + expected: None, + expected_bool: false, + }, + } +} + +impl Case { + /// The two operands, `first_arg` and `second_arg`. + fn args(mut self, a: u8, b: u8) -> Self { + self.witness.first_arg = a; + self.witness.second_arg = b; + self + } + + /// The u4 value the arm should return + fn expect(mut self, expected: u8) -> Self { + self.witness.expected = Some(expected); + self + } + + /// `expected_bool`: the boolean the arm should report. + fn expected_bool(mut self, expected_bool: bool) -> Self { + self.witness.expected_bool = expected_bool; + self + } + + /// Fund, spend, and expect the spend to succeed. + fn run(self, context: &simplex::TestContext) -> anyhow::Result<()> { + self.expecting(context, Expect::Ok) + } + + /// Fund, spend, and expect `expect`. + fn expecting(self, context: &simplex::TestContext, expect: Expect) -> anyhow::Result<()> { + run(context, program(), self.witness, expect) + } +} + +#[simplex::test] +fn and_4(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=U4_MAX); + let b = rand::thread_rng().gen_range(0..=U4_MAX); + let result = a & b; + + case(And4).args(a, b).expect(result).run(&context) +} + +#[simplex::test] +fn or_4(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=U4_MAX); + let b = rand::thread_rng().gen_range(0..=U4_MAX); + let result = a | b; + + case(Or4).args(a, b).expect(result).run(&context) +} + +#[simplex::test] +fn eq_4_true(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=U4_MAX); + + case(Eq4).args(a, a).expected_bool(true).run(&context) +} + +#[simplex::test] +fn eq_4_false(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=U4_MAX); + let mut b = rand::thread_rng().gen_range(0..=U4_MAX); + + while a == b { + b = rand::thread_rng().gen_range(0..=U4_MAX); + } + + case(Eq4).args(a, b).expected_bool(false).run(&context) +} diff --git a/tests/stdlib/u4/convert.rs b/tests/stdlib/u4/convert.rs new file mode 100644 index 0000000..e929302 --- /dev/null +++ b/tests/stdlib/u4/convert.rs @@ -0,0 +1,157 @@ +use primitive_types::U256; +use rand::Rng; + +use crate::common::core::{Expect, run}; + +use simplicityhl_std::artifacts::tests::u4::convert::ConvertProgram as U4ConvertTestProgram; +use simplicityhl_std::artifacts::tests::u4::convert::derived_convert::{ + ConvertArguments as U4ConvertTestArguments, ConvertWitness as U4ConvertTestWitness, +}; + +use FunctionToTest::*; + +enum FunctionToTest { + U4ToU8, + U4ToU16, + U4ToU32, + U4ToU64, + U4ToU128, + U4ToU256, + SplitU4IntoU1, + SafeU4ToU1, +} + +const U4_MAX: u8 = 15; + +fn program() -> U4ConvertTestProgram { + U4ConvertTestProgram::new(&U4ConvertTestArguments {}) +} + +/// One dispatch arm of the contract, plus the witness it reads. +struct Case { + witness: U4ConvertTestWitness, +} + +fn case(function: FunctionToTest) -> Case { + Case { + witness: U4ConvertTestWitness { + function_index: function as u8, + first_arg: 0, + expected: [0; 32], + }, + } +} + +impl Case { + /// The operand, `first_arg`. + fn arg(mut self, a: u8) -> Self { + self.witness.first_arg = a; + self + } + + /// `expected`: the value the arm should produce. + fn expect(mut self, expected: [u8; 32]) -> Self { + self.witness.expected = expected; + self + } + + /// Fund, spend, and expect the spend to succeed. + fn run(self, context: &simplex::TestContext) -> anyhow::Result<()> { + self.expecting(context, Expect::Ok) + } + + /// Fund, spend, and expect `expect`. + fn expecting(self, context: &simplex::TestContext, expect: Expect) -> anyhow::Result<()> { + run(context, program(), self.witness, expect) + } +} + +#[simplex::test] +fn u4_to_u8(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=U4_MAX); + + case(U4ToU8) + .arg(a) + .expect(U256::from(a).to_big_endian()) + .run(&context) +} + +#[simplex::test] +fn u4_to_u16(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=U4_MAX); + + case(U4ToU16) + .arg(a) + .expect(U256::from(a).to_big_endian()) + .run(&context) +} + +#[simplex::test] +fn u4_to_u32(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=U4_MAX); + + case(U4ToU32) + .arg(a) + .expect(U256::from(a).to_big_endian()) + .run(&context) +} + +#[simplex::test] +fn u4_to_u64(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=U4_MAX); + + case(U4ToU64) + .arg(a) + .expect(U256::from(a).to_big_endian()) + .run(&context) +} + +#[simplex::test] +fn u4_to_u128(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=U4_MAX); + + case(U4ToU128) + .arg(a) + .expect(U256::from(a).to_big_endian()) + .run(&context) +} + +#[simplex::test] +fn u4_to_u256(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=U4_MAX); + + case(U4ToU256) + .arg(a) + .expect(U256::from(a).to_big_endian()) + .run(&context) +} + +#[simplex::test] +fn split_u4_into_u1(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=U4_MAX); + + case(SplitU4IntoU1) + .arg(a) + .expect(U256::from(a).to_big_endian()) + .run(&context) +} + +#[simplex::test] +fn safe_u4_to_u1(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=1); + + case(SafeU4ToU1) + .arg(a) + .expect(U256::from(a).to_big_endian()) + .run(&context) +} + +#[simplex::test] +fn safe_u4_to_u1_overflow(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(2..=U4_MAX); + + case(SafeU4ToU1) + .arg(a) + .expect(U256::from(a).to_big_endian()) + .expecting(&context, Expect::AssertFailed) +} diff --git a/tests/stdlib/u4/mod.rs b/tests/stdlib/u4/mod.rs new file mode 100644 index 0000000..61da102 --- /dev/null +++ b/tests/stdlib/u4/mod.rs @@ -0,0 +1,2 @@ +mod bit; +mod convert; diff --git a/tests/stdlib/utxo/asserts/asset_id.rs b/tests/stdlib/utxo/asserts/asset_id.rs index 6408829..2ed19c4 100644 --- a/tests/stdlib/utxo/asserts/asset_id.rs +++ b/tests/stdlib/utxo/asserts/asset_id.rs @@ -186,7 +186,7 @@ fn assert_false_explicit_asset_id_for_input(context: simplex::TestContext) -> an case(Explicit) .index(index) .index_type(IndexType::Input) - .expect(context.get_network().policy_asset().into_inner().0) + .expect(context.get_network().policy_asset().to_byte_array()) .expecting(&context, Expect::AssertFailed, txids) } @@ -224,7 +224,7 @@ fn assert_false_explicit_asset_id_for_output(context: simplex::TestContext) -> a case(Explicit) .index(index) .index_type(IndexType::Output) - .expect(context.get_network().policy_asset().into_inner().0) + .expect(context.get_network().policy_asset().to_byte_array()) .expecting(&context, Expect::AssertFailed, txids) } @@ -321,7 +321,7 @@ fn assert_false_explicit_input_asset_id(context: simplex::TestContext) -> anyhow case(ExplicitInput) .index(index) - .expect(context.get_network().policy_asset().into_inner().0) + .expect(context.get_network().policy_asset().to_byte_array()) .expecting(&context, Expect::AssertFailed, txids) } @@ -376,7 +376,7 @@ fn assert_false_explicit_output_asset_id(context: simplex::TestContext) -> anyho case(ExplicitOutput) .index(index) - .expect(context.get_network().policy_asset().into_inner().0) + .expect(context.get_network().policy_asset().to_byte_array()) .expecting(&context, Expect::AssertFailed, txids) } @@ -434,7 +434,7 @@ fn assert_false_explicit_current_asset_id(context: simplex::TestContext) -> anyh case(CurrentExplicit) .index(index) - .expect(context.get_network().policy_asset().into_inner().0) + .expect(context.get_network().policy_asset().to_byte_array()) .expecting(&context, Expect::AssertFailed, txids) } diff --git a/tests/stdlib/utxo/asserts/asset_id_with_amount.rs b/tests/stdlib/utxo/asserts/asset_id_with_amount.rs index de17bc4..f9331ee 100644 --- a/tests/stdlib/utxo/asserts/asset_id_with_amount.rs +++ b/tests/stdlib/utxo/asserts/asset_id_with_amount.rs @@ -210,7 +210,7 @@ fn assert_false_explicit_asset_id_with_amount_for_input( .index(index) .index_type(IndexType::Input) .expect( - context.get_network().policy_asset().into_inner().0, + context.get_network().policy_asset().to_byte_array(), real_amount, ) .expecting(&context, Expect::AssertFailed, txids) @@ -281,7 +281,7 @@ fn assert_false_explicit_asset_id_with_amount_for_output( .index(index) .index_type(IndexType::Output) .expect( - context.get_network().policy_asset().into_inner().0, + context.get_network().policy_asset().to_byte_array(), real_amount, ) .expecting(&context, Expect::AssertFailed, txids) @@ -432,7 +432,7 @@ fn assert_false_explicit_input_asset_id_with_amount( case(ExplicitInput) .index(index) .expect( - context.get_network().policy_asset().into_inner().0, + context.get_network().policy_asset().to_byte_array(), real_amount, ) .expecting(&context, Expect::AssertFailed, txids) @@ -533,7 +533,7 @@ fn assert_false_explicit_output_asset_id_with_amount( case(ExplicitOutput) .index(index) .expect( - context.get_network().policy_asset().into_inner().0, + context.get_network().policy_asset().to_byte_array(), real_amount, ) .expecting(&context, Expect::AssertFailed, txids) @@ -631,7 +631,7 @@ fn assert_false_explicit_current_asset_id_with_amount( case(CurrentExplicit) .index(index) .expect( - context.get_network().policy_asset().into_inner().0, + context.get_network().policy_asset().to_byte_array(), real_amount, ) .expecting(&context, Expect::AssertFailed, txids) diff --git a/tests/stdlib/utxo/asserts/mod.rs b/tests/stdlib/utxo/asserts/mod.rs index 6cbabb2..c2cea35 100644 --- a/tests/stdlib/utxo/asserts/mod.rs +++ b/tests/stdlib/utxo/asserts/mod.rs @@ -1,3 +1,4 @@ mod amount; mod asset_id; mod asset_id_with_amount; +mod script_hash; diff --git a/tests/stdlib/utxo/asserts/script_hash.rs b/tests/stdlib/utxo/asserts/script_hash.rs new file mode 100644 index 0000000..d87cbdc --- /dev/null +++ b/tests/stdlib/utxo/asserts/script_hash.rs @@ -0,0 +1,268 @@ +use primitive_types::U256; +use rand::Rng; +use simplex::simplicityhl::elements::Txid; + +use crate::common::core::Expect; +use crate::common::core::run_with_inputs_outputs; +use crate::common::helper::generate_u256; +use crate::common::utxo_helper::create_utxos_and_return_script_hash; + +use simplicityhl_std::artifacts::tests::utxo::asserts::script_hash::ScriptHashProgram as TestScriptHashProgram; +use simplicityhl_std::artifacts::tests::utxo::asserts::script_hash::derived_script_hash::{ + ScriptHashArguments as TestScriptHashArguments, ScriptHashWitness as TestScriptHashWitness, +}; + +use simplicityhl_std::artifacts::tests::mock::MockProgram as TestMockProgram; +use simplicityhl_std::artifacts::tests::mock::derived_mock::MockArguments as TestMockArguments; + +use FunctionToTest::*; + +enum FunctionToTest { + Script, + InputScript, + OutputScript, +} + +#[derive(PartialEq, Eq)] +enum IndexType { + Input, + Output, +} + +fn is_input(o: IndexType) -> bool { + o == IndexType::Input +} + +fn program() -> TestScriptHashProgram { + TestScriptHashProgram::new(&TestScriptHashArguments {}) +} + +fn mock() -> TestMockProgram { + TestMockProgram::new(&TestMockArguments {}) +} + +/// One dispatch arm of the contract, plus the witness it reads. +struct Case { + witness: TestScriptHashWitness, +} + +fn case(function: FunctionToTest) -> Case { + Case { + witness: TestScriptHashWitness { + function_index: function as u8, + index: 0, + is_input_index: false, + expected: [0; 32], + }, + } +} + +impl Case { + /// `index`: the input/output the arm inspects. + fn index(mut self, index: u32) -> Self { + self.witness.index = index; + self + } + + /// `is_input`: whether the arm inspects an input. + fn index_type(mut self, index_type: IndexType) -> Self { + self.witness.is_input_index = is_input(index_type); + self + } + + /// `expected`: value the arm should assert. + fn expect(mut self, expected: [u8; 32]) -> Self { + self.witness.expected = expected; + self + } + + /// Fund, spend, and expect the spend to succeed. + fn run(self, context: &simplex::TestContext, txids: Vec) -> anyhow::Result<()> { + self.expecting(context, Expect::Ok, txids) + } + + /// Fund, spend, and expect `expect`. + fn expecting( + self, + context: &simplex::TestContext, + expect: Expect, + txids: Vec, + ) -> anyhow::Result<()> { + run_with_inputs_outputs( + context, + program(), + self.witness, + expect, + txids, + Some(mock().as_ref().clone()), + ) + } +} + +fn create_utxos_wrapper( + context: &simplex::TestContext, + index: u32, + is_input: bool, +) -> anyhow::Result<([u8; 32], Vec)> { + let last_script = &mock().as_ref().get_script_pubkey(context.get_network()); + let (hash, txids) = + create_utxos_and_return_script_hash(context, index, is_input, last_script, program())?; + + Ok((hash, txids)) +} + +#[simplex::test] +fn assert_script_hash_for_random_input(context: simplex::TestContext) -> anyhow::Result<()> { + let index = rand::thread_rng().gen_range(0..=20) as u32; // not a big value to not to slow down tests + let (expected_hash, txids) = create_utxos_wrapper(&context, index, is_input(IndexType::Input))?; + + case(Script) + .index(index) + .index_type(IndexType::Input) + .expect(expected_hash) + .run(&context, txids) +} + +#[simplex::test] +fn assert_script_hash_for_random_output(context: simplex::TestContext) -> anyhow::Result<()> { + let index = rand::thread_rng().gen_range(0..=20) as u32; // not a big value to not to slow down tests + let (expected_hash, txids) = + create_utxos_wrapper(&context, index, is_input(IndexType::Output))?; + + case(Script) + .index(index) + .index_type(IndexType::Output) + .expect(expected_hash) + .run(&context, txids) +} + +#[simplex::test] +fn assert_false_script_hash_for_input(context: simplex::TestContext) -> anyhow::Result<()> { + let index = 2; + let (_, txids) = create_utxos_wrapper(&context, index, is_input(IndexType::Input))?; + let false_hash = generate_u256(U256::zero(), U256::MAX).to_big_endian(); + + case(Script) + .index(index) + .index_type(IndexType::Input) + .expect(false_hash) + .expecting(&context, Expect::AssertFailed, txids) +} + +#[simplex::test] +fn assert_false_script_hash_for_output(context: simplex::TestContext) -> anyhow::Result<()> { + let index = 2; + let (_, txids) = create_utxos_wrapper(&context, index, is_input(IndexType::Output))?; + let false_hash = generate_u256(U256::zero(), U256::MAX).to_big_endian(); + + case(Script) + .index(index) + .index_type(IndexType::Output) + .expect(false_hash) + .expecting(&context, Expect::AssertFailed, txids) +} + +/// The output script at the same index is tweaked, so the input's hash must not match the output. +#[simplex::test] +fn assert_input_script_hash_against_output_fail( + context: simplex::TestContext, +) -> anyhow::Result<()> { + let index = 1; + let (input_hash, txids) = create_utxos_wrapper(&context, index, is_input(IndexType::Input))?; + + case(Script) + .index(index) + .index_type(IndexType::Output) + .expect(input_hash) + .expecting(&context, Expect::AssertFailed, txids) +} + +#[simplex::test] +fn assert_script_hash_for_missing_input_fail(context: simplex::TestContext) -> anyhow::Result<()> { + let (_, txids) = create_utxos_wrapper(&context, 0, is_input(IndexType::Input))?; + + case(Script) + .index(u32::MAX) + .index_type(IndexType::Input) + .expecting(&context, Expect::PrunedBranch, txids) +} + +#[simplex::test] +fn assert_script_hash_for_missing_output_fail(context: simplex::TestContext) -> anyhow::Result<()> { + let (_, txids) = create_utxos_wrapper(&context, 0, is_input(IndexType::Output))?; + + case(Script) + .index(u32::MAX) + .index_type(IndexType::Output) + .expecting(&context, Expect::PrunedBranch, txids) +} + +#[simplex::test] +fn assert_input_script_hash(context: simplex::TestContext) -> anyhow::Result<()> { + let index = rand::thread_rng().gen_range(0..=3) as u32; + let (expected_hash, txids) = create_utxos_wrapper(&context, index, is_input(IndexType::Input))?; + + case(InputScript) + .index(index) + .expect(expected_hash) + .run(&context, txids) +} + +#[simplex::test] +fn assert_false_input_script_hash(context: simplex::TestContext) -> anyhow::Result<()> { + let index = 0; + let (_, txids) = create_utxos_wrapper(&context, index, is_input(IndexType::Input))?; + let false_hash = generate_u256(U256::zero(), U256::MAX).to_big_endian(); + + case(InputScript).index(index).expect(false_hash).expecting( + &context, + Expect::AssertFailed, + txids, + ) +} + +#[simplex::test] +fn assert_input_script_hash_missing_input_fail( + context: simplex::TestContext, +) -> anyhow::Result<()> { + let (_, txids) = create_utxos_wrapper(&context, 0, is_input(IndexType::Input))?; + + case(InputScript) + .index(u32::MAX) + .expecting(&context, Expect::PrunedBranch, txids) +} + +#[simplex::test] +fn assert_output_script_hash(context: simplex::TestContext) -> anyhow::Result<()> { + let index = rand::thread_rng().gen_range(0..=3) as u32; + let (expected_hash, txids) = + create_utxos_wrapper(&context, index, is_input(IndexType::Output))?; + + case(OutputScript) + .index(index) + .expect(expected_hash) + .run(&context, txids) +} + +#[simplex::test] +fn assert_false_output_script_hash(context: simplex::TestContext) -> anyhow::Result<()> { + let index = 3; + let (_, txids) = create_utxos_wrapper(&context, index, is_input(IndexType::Output))?; + let false_hash = generate_u256(U256::zero(), U256::MAX).to_big_endian(); + + case(OutputScript) + .index(index) + .expect(false_hash) + .expecting(&context, Expect::AssertFailed, txids) +} + +#[simplex::test] +fn assert_output_script_hash_missing_output_fail( + context: simplex::TestContext, +) -> anyhow::Result<()> { + let (_, txids) = create_utxos_wrapper(&context, 0, is_input(IndexType::Output))?; + + case(OutputScript) + .index(u32::MAX) + .expecting(&context, Expect::PrunedBranch, txids) +} diff --git a/tests/stdlib/utxo/mod.rs b/tests/stdlib/utxo/mod.rs index 9c68058..18d33bf 100644 --- a/tests/stdlib/utxo/mod.rs +++ b/tests/stdlib/utxo/mod.rs @@ -1,2 +1,3 @@ mod asserts; mod getters; +mod op_return; diff --git a/tests/stdlib/utxo/op_return.rs b/tests/stdlib/utxo/op_return.rs new file mode 100644 index 0000000..98e4164 --- /dev/null +++ b/tests/stdlib/utxo/op_return.rs @@ -0,0 +1,761 @@ +use rand::Rng; + +use simplex::either::Either; +use simplex::simplicityhl::elements::Script; + +use crate::common::core::{Expect, run, run_with_op_return}; + +use simplicityhl_std::artifacts::tests::utxo::op_return::OpReturnProgram as OpReturnTestProgram; +use simplicityhl_std::artifacts::tests::utxo::op_return::derived_op_return::{ + OpReturnArguments as OpReturnTestArguments, OpReturnWitness as OpReturnTestWitness, +}; + +use FunctionToTest::*; + +enum FunctionToTest { + GetEntryOption, + PushedDataFromEntry, + GetPushedData, + OpXMinus1FromEntry, + GetOpXMinus1, + NegateOrReservedFromEntry, + GetNegateOrReserved, + KindOfEntry, + IsOutputOpReturn, + HasEntry, + AssertOutputIsOpReturn, + AssertHasEntry, +} + +type OpReturnEntry = Either<(u8, [u8; 32]), Either>; + +const DEFAULT_DATA: &[u8; 2] = &[0, 1]; +const OP_PUSHNUM_NEG1: &[u8] = &[0x01, 0x4f]; +const OP_RESERVED: &[u8] = &[0x01, 0x50]; + +fn program() -> OpReturnTestProgram { + OpReturnTestProgram::new(&OpReturnTestArguments {}) +} + +/// One dispatch arm of the contract, plus the witness it reads and whether the +/// spend carries an `OP_RETURN` output. +struct Case { + witness: OpReturnTestWitness, + data: Option<&'static [u8]>, +} + +fn case(function: FunctionToTest) -> Case { + Case { + witness: OpReturnTestWitness { + function_index: function as u8, + output_index: 0, + entry_index: 0, + entry: Either::Right(Either::Right(0)), + expected_entry: None, + expected_value: None, + expected_bool: false, + expected_hash: [0; 32], + }, + data: None, + } +} + +impl Case { + /// `output_index`: the output the arm inspects. + fn output_index(mut self, output_index: u32) -> Self { + self.witness.output_index = output_index; + self + } + + /// `entry_index`: the entry index the arm inspects. + fn entry_index(mut self, entry_index: u32) -> Self { + self.witness.entry_index = entry_index; + self + } + + /// `entry`:the entry passed to the arm. + fn entry(mut self, entry: OpReturnEntry) -> Self { + self.witness.entry = entry; + self + } + + /// `expected_entry`: the entry the arm expects. + fn expected_entry(mut self, expected_entry: Option) -> Self { + self.witness.expected_entry = expected_entry; + self + } + + /// `expected_value`: the value the arm expects. + fn expected_value(mut self, expected_value: Option) -> Self { + self.witness.expected_value = expected_value; + self + } + + /// `expected_bool`: the boolean the arm should report. + fn expected_bool(mut self, expected_bool: bool) -> Self { + self.witness.expected_bool = expected_bool; + self + } + + /// `expected_hash`: the hash of the pushed data the arm inspects. + fn expected_hash(mut self, expected_hash: [u8; 32]) -> Self { + self.witness.expected_hash = expected_hash; + self + } + + /// Add an `OP_RETURN` output carrying `data` to the spend. + /// First byte is a build indicator (0 means single data push, 1 means raw opcode push) + fn op_return(mut self, data: &'static [u8]) -> Self { + self.data = Some(data); + self + } + + /// Fund, spend, and expect the spend to succeed. + fn run(self, context: &simplex::TestContext) -> anyhow::Result<()> { + self.expecting(context, Expect::Ok) + } + + /// Fund, spend, and expect `expect`. + fn expecting(self, context: &simplex::TestContext, expect: Expect) -> anyhow::Result<()> { + match self.data { + Some(data) => run_with_op_return(context, program(), self.witness, expect, data), + None => run(context, program(), self.witness, expect), + } + } +} + +pub fn hash_of_pushed_data(data: &[u8]) -> [u8; 32] { + Script::from(data.to_vec()).wscript_hash().to_byte_array() +} + +#[simplex::test] +fn get_entry_option(context: simplex::TestContext) -> anyhow::Result<()> { + /// 0 = push data (0x12, 0x23), 1 = OP_1, 2 = OP_1NEGATE, 3 = OP_RESERVED. + const MIXED_ENTRIES: &[u8] = &[1, 0x02, 0x12, 0x23, 0x51, 0x4f, 0x50]; + const PUSHED_DATA: &[u8] = &[0x12, 0x23]; + + let cases: [(u32, OpReturnEntry); 4] = [ + (0, Either::Left((0, hash_of_pushed_data(PUSHED_DATA)))), + (1, Either::Right(Either::Right(0))), + (2, Either::Right(Either::Left(0))), + (3, Either::Right(Either::Left(1))), + ]; + + for (entry_index, entry) in cases { + case(GetEntryOption) + .entry_index(entry_index) + .expected_entry(Some(entry)) + .op_return(MIXED_ENTRIES) + .run(&context) + .unwrap_or_else(|_| panic!("entry index = {entry_index}")); + } + + Ok(()) +} + +#[simplex::test] +fn get_entry_option_nonexistent_output(context: simplex::TestContext) -> anyhow::Result<()> { + let output_index = u32::MAX; + let entry_index = 0; + let expected_entry: Option = None; + + case(GetEntryOption) + .output_index(output_index) + .entry_index(entry_index) + .expected_entry(expected_entry) + .op_return(DEFAULT_DATA) + .run(&context) +} + +#[simplex::test] +fn get_entry_option_not_op_return_output(context: simplex::TestContext) -> anyhow::Result<()> { + let output_index = 0; + let entry_index = 0; + let expected_entry: Option = None; + + case(GetEntryOption) + .output_index(output_index) + .entry_index(entry_index) + .expected_entry(expected_entry) + .run(&context) +} + +#[simplex::test] +fn get_entry_option_out_of_range_entry(context: simplex::TestContext) -> anyhow::Result<()> { + let output_index = 0; + let entry_index = u32::MAX; + let expected_entry: Option = None; + + case(GetEntryOption) + .output_index(output_index) + .entry_index(entry_index) + .expected_entry(expected_entry) + .op_return(DEFAULT_DATA) + .run(&context) +} + +#[simplex::test] +fn pushed_data_from_entry(context: simplex::TestContext) -> anyhow::Result<()> { + let indicator = 0_u8; + let hash = hash_of_pushed_data(&DEFAULT_DATA[1..]); + + let entry: OpReturnEntry = Either::Left((indicator, hash)); + + case(PushedDataFromEntry) + .entry(entry) + .expected_value(Some(indicator)) + .expected_hash(hash) + .run(&context) +} + +#[simplex::test] +fn pushed_data_from_entry_op_x_returns_none(context: simplex::TestContext) -> anyhow::Result<()> { + case(PushedDataFromEntry) + .entry(Either::Right(Either::Right(0))) + .expected_value(None) + .run(&context) +} + +#[simplex::test] +fn get_pushdata0(context: simplex::TestContext) -> anyhow::Result<()> { + let output_index = 0; + let entry_index = 0; + + let indicator = 0_u8; + let hash = hash_of_pushed_data(&DEFAULT_DATA[1..]); + + case(GetPushedData) + .output_index(output_index) + .entry_index(entry_index) + .expected_value(Some(indicator)) + .expected_hash(hash) + .op_return(DEFAULT_DATA) + .run(&context) +} + +#[simplex::test] +fn get_pushdata1(context: simplex::TestContext) -> anyhow::Result<()> { + let output_index = 0; + let entry_index = 0; + + const DATA: [u8; 77] = [0; 77]; // a 76-byte push, which needs OP_PUSHDATA1 + + let indicator = 1_u8; + let hash = hash_of_pushed_data(&DATA[1..]); + + case(GetPushedData) + .output_index(output_index) + .entry_index(entry_index) + .expected_value(Some(indicator)) + .expected_hash(hash) + .op_return(&DATA) + .run(&context) +} + +#[simplex::test] +fn get_pushdata2(context: simplex::TestContext) -> anyhow::Result<()> { + let output_index = 0; + let entry_index = 0; + + const DATA: [u8; 7] = [1, 0x4d, 0x03, 0x00, 0xde, 0xad, 0xbe]; // the last 3 bytes are the pushed data + + let indicator = 2_u8; + let hash = hash_of_pushed_data(&DATA[4..]); + + case(GetPushedData) + .output_index(output_index) + .entry_index(entry_index) + .expected_value(Some(indicator)) + .expected_hash(hash) + .op_return(&DATA) + .run(&context) +} + +#[simplex::test] +fn get_pushdata4(context: simplex::TestContext) -> anyhow::Result<()> { + let output_index = 0; + let entry_index = 0; + + const DATA: [u8; 9] = [1, 0x4e, 0x03, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe]; // the last 3 bytes are the pushed data + + let indicator = 3_u8; + let hash = hash_of_pushed_data(&DATA[6..]); + + case(GetPushedData) + .output_index(output_index) + .entry_index(entry_index) + .expected_value(Some(indicator)) + .expected_hash(hash) + .op_return(&DATA) + .run(&context) +} + +#[simplex::test] +fn get_pushed_data_nonexistent_output(context: simplex::TestContext) -> anyhow::Result<()> { + let output_index = u32::MAX; + let entry_index = 0; + + case(GetPushedData) + .output_index(output_index) + .entry_index(entry_index) + .expected_value(None) + .op_return(DEFAULT_DATA) + .run(&context) +} + +#[simplex::test] +fn get_pushed_data_not_op_return_output(context: simplex::TestContext) -> anyhow::Result<()> { + let output_index = 0; + let entry_index = 0; + + case(GetPushedData) + .output_index(output_index) + .entry_index(entry_index) + .expected_value(None) + .run(&context) +} + +#[simplex::test] +fn get_pushed_data_out_of_range_entry(context: simplex::TestContext) -> anyhow::Result<()> { + let output_index = 0; + let entry_index = u32::MAX; + + case(GetPushedData) + .output_index(output_index) + .entry_index(entry_index) + .expected_value(None) + .op_return(DEFAULT_DATA) + .run(&context) +} + +#[simplex::test] +fn get_pushed_data_neg1_returns_none(context: simplex::TestContext) -> anyhow::Result<()> { + let output_index = 0; + let entry_index = 0; + + case(GetPushedData) + .output_index(output_index) + .entry_index(entry_index) + .expected_value(None) + .op_return(OP_PUSHNUM_NEG1) + .run(&context) +} + +#[simplex::test] +fn op_x_minus_1_from_entry(context: simplex::TestContext) -> anyhow::Result<()> { + for i in 0..16 { + let entry: OpReturnEntry = Either::Right(Either::Right(i)); + + case(OpXMinus1FromEntry) + .entry(entry) + .expected_value(Some(i)) + .run(&context) + .unwrap_or_else(|_| panic!("entry index = {i}")); + } + + Ok(()) +} + +#[simplex::test] +fn op_x_minus_1_from_entry_returns_none(context: simplex::TestContext) -> anyhow::Result<()> { + let entry: OpReturnEntry = Either::Right(Either::Left(1)); // OP_RESERVED + + case(OpXMinus1FromEntry) + .entry(entry) + .expected_value(None) + .run(&context) +} + +#[simplex::test] +fn get_op_x_minus_1(context: simplex::TestContext) -> anyhow::Result<()> { + let output_index = 0; + + // the first byte is the build indicator + const OP_1_TO_16: &[u8] = &[ + 0x01, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, + 0x5f, 0x60, + ]; + + for i in 0..16 { + case(GetOpXMinus1) + .output_index(output_index) + .entry_index(i) + .expected_value(Some(i as u8)) + .op_return(OP_1_TO_16) + .run(&context) + .unwrap_or_else(|err| panic!("{err}, entry index = {i}")); + } + + Ok(()) +} + +#[simplex::test] +fn get_op_x_minus_1_nonexistent_output(context: simplex::TestContext) -> anyhow::Result<()> { + let output_index = u32::MAX; + let entry_index = 0; + + case(GetOpXMinus1) + .output_index(output_index) + .entry_index(entry_index) + .expected_value(None) + .op_return(DEFAULT_DATA) + .run(&context) +} + +#[simplex::test] +fn get_op_x_minus_1_not_op_return_output(context: simplex::TestContext) -> anyhow::Result<()> { + let output_index = 0; + let entry_index = 0; + + case(GetOpXMinus1) + .output_index(output_index) + .entry_index(entry_index) + .expected_value(None) + .run(&context) +} + +#[simplex::test] +fn get_op_x_minus_1_out_of_range_entry(context: simplex::TestContext) -> anyhow::Result<()> { + let output_index = 0; + let entry_index = u32::MAX; + const OP_1: &[u8] = &[0x01, 0x51]; + + case(GetOpXMinus1) + .output_index(output_index) + .entry_index(entry_index) + .expected_value(None) + .op_return(OP_1) + .run(&context) +} + +#[simplex::test] +fn get_op_x_minus_1_returns_none(context: simplex::TestContext) -> anyhow::Result<()> { + let output_index = 0; + let entry_index = 0; + + case(GetOpXMinus1) + .output_index(output_index) + .entry_index(entry_index) + .expected_value(None) + .op_return(OP_RESERVED) + .run(&context) +} + +#[simplex::test] +fn negate_or_reserved_from_entry_negate(context: simplex::TestContext) -> anyhow::Result<()> { + let indicator = 0_u8; + + let entry: OpReturnEntry = Either::Right(Either::Left(indicator)); + + case(NegateOrReservedFromEntry) + .entry(entry) + .expected_value(Some(indicator)) + .run(&context) +} + +#[simplex::test] +fn negate_or_reserved_from_entry_reserved(context: simplex::TestContext) -> anyhow::Result<()> { + let indicator = 1_u8; + + let entry: OpReturnEntry = Either::Right(Either::Left(indicator)); + + case(NegateOrReservedFromEntry) + .entry(entry) + .expected_value(Some(indicator)) + .run(&context) +} + +#[simplex::test] +fn negate_or_reserved_from_entry_returns_none(context: simplex::TestContext) -> anyhow::Result<()> { + let entry: OpReturnEntry = Either::Left((0, hash_of_pushed_data(&[1]))); + + case(NegateOrReservedFromEntry) + .entry(entry) + .expected_value(None) + .run(&context) +} + +#[simplex::test] +fn get_negate_or_reserved_negate(context: simplex::TestContext) -> anyhow::Result<()> { + let output_index = 0; + let entry_index = 0; + let indicator = 0_u8; + + case(GetNegateOrReserved) + .output_index(output_index) + .entry_index(entry_index) + .expected_value(Some(indicator)) + .op_return(OP_PUSHNUM_NEG1) + .run(&context) +} + +#[simplex::test] +fn get_negate_or_reserved_reserved(context: simplex::TestContext) -> anyhow::Result<()> { + let output_index = 0; + let entry_index = 0; + let indicator = 1_u8; + + case(GetNegateOrReserved) + .output_index(output_index) + .entry_index(entry_index) + .expected_value(Some(indicator)) + .op_return(OP_RESERVED) + .run(&context) +} + +#[simplex::test] +fn get_negate_or_reserved_nonexistent_output(context: simplex::TestContext) -> anyhow::Result<()> { + let output_index = u32::MAX; + let entry_index = 0; + + case(GetNegateOrReserved) + .output_index(output_index) + .entry_index(entry_index) + .expected_value(None) + .op_return(DEFAULT_DATA) + .run(&context) +} + +#[simplex::test] +fn get_negate_or_reserved_not_op_return_output( + context: simplex::TestContext, +) -> anyhow::Result<()> { + let output_index = 0; + let entry_index = 0; + + case(GetNegateOrReserved) + .output_index(output_index) + .entry_index(entry_index) + .expected_value(None) + .run(&context) +} + +#[simplex::test] +fn get_negate_or_reserved_out_of_range_entry(context: simplex::TestContext) -> anyhow::Result<()> { + let output_index = 0; + let entry_index = u32::MAX; + + case(GetNegateOrReserved) + .output_index(output_index) + .entry_index(entry_index) + .expected_value(None) + .op_return(OP_RESERVED) + .run(&context) +} + +#[simplex::test] +fn get_negate_or_reserved_returns_none(context: simplex::TestContext) -> anyhow::Result<()> { + let output_index = 0; + let entry_index = 0; + + case(GetNegateOrReserved) + .output_index(output_index) + .entry_index(entry_index) + .expected_value(None) + .op_return(DEFAULT_DATA) + .run(&context) +} + +#[simplex::test] +fn kind_of_entry_pushed_data(context: simplex::TestContext) -> anyhow::Result<()> { + let entry: OpReturnEntry = Either::Left((0, hash_of_pushed_data(&[1]))); + + case(KindOfEntry) + .entry(entry) + .expected_value(Some(0)) + .op_return(DEFAULT_DATA) + .run(&context) +} + +#[simplex::test] +fn kind_of_entry_op_x_minus_1(context: simplex::TestContext) -> anyhow::Result<()> { + let op_x_minus_1 = rand::thread_rng().gen_range(0..16); + let entry: OpReturnEntry = Either::Right(Either::Right(op_x_minus_1)); + + case(KindOfEntry) + .entry(entry) + .expected_value(Some(1)) + .op_return(DEFAULT_DATA) + .run(&context) +} + +#[simplex::test] +fn kind_of_entry_negate_or_reserved_negate(context: simplex::TestContext) -> anyhow::Result<()> { + let indicator = 0_u8; + let entry: OpReturnEntry = Either::Right(Either::Left(indicator)); + + case(KindOfEntry) + .entry(entry) + .expected_value(Some(2)) + .op_return(DEFAULT_DATA) + .run(&context) +} + +#[simplex::test] +fn kind_of_entry_negate_or_reserved_reserved(context: simplex::TestContext) -> anyhow::Result<()> { + let indicator = 1_u8; + let entry: OpReturnEntry = Either::Right(Either::Left(indicator)); + + case(KindOfEntry) + .entry(entry) + .expected_value(Some(2)) + .op_return(DEFAULT_DATA) + .run(&context) +} + +#[simplex::test] +fn is_output_op_return_true(context: simplex::TestContext) -> anyhow::Result<()> { + let output_index = 0; + let entry_index = 0; + + case(IsOutputOpReturn) + .output_index(output_index) + .entry_index(entry_index) + .expected_bool(true) + .op_return(DEFAULT_DATA) + .run(&context) +} + +#[simplex::test] +fn is_output_op_returns_nonexistent_output_false( + context: simplex::TestContext, +) -> anyhow::Result<()> { + let output_index = u32::MAX; + + case(IsOutputOpReturn) + .output_index(output_index) + .expected_bool(false) + .op_return(DEFAULT_DATA) + .run(&context) +} + +#[simplex::test] +fn is_output_op_return_false(context: simplex::TestContext) -> anyhow::Result<()> { + let output_index = 0; + + case(IsOutputOpReturn) + .output_index(output_index) + .expected_bool(false) + .run(&context) +} + +#[simplex::test] +fn has_entry_true(context: simplex::TestContext) -> anyhow::Result<()> { + let output_index = 0; + let entry_index = 0; + + case(HasEntry) + .output_index(output_index) + .entry_index(entry_index) + .expected_bool(true) + .op_return(DEFAULT_DATA) + .run(&context) +} + +#[simplex::test] +fn has_entry_nonexistent_output_false(context: simplex::TestContext) -> anyhow::Result<()> { + let output_index = u32::MAX; + + case(HasEntry) + .output_index(output_index) + .expected_bool(false) + .op_return(DEFAULT_DATA) + .run(&context) +} + +#[simplex::test] +fn has_entry_out_of_range_entry_false(context: simplex::TestContext) -> anyhow::Result<()> { + let output_index = 0; + let entry_index = u32::MAX; + + case(HasEntry) + .output_index(output_index) + .entry_index(entry_index) + .expected_bool(false) + .op_return(DEFAULT_DATA) + .run(&context) +} + +#[simplex::test] +fn has_entry_output_not_op_return_false(context: simplex::TestContext) -> anyhow::Result<()> { + let output_index = 0; + + case(HasEntry) + .output_index(output_index) + .expected_bool(false) + .run(&context) +} + +#[simplex::test] +fn assert_output_is_op_return_pass(context: simplex::TestContext) -> anyhow::Result<()> { + let output_index = 0; + + case(AssertOutputIsOpReturn) + .output_index(output_index) + .op_return(DEFAULT_DATA) + .run(&context) +} + +#[simplex::test] +fn assert_output_is_op_returns_nonexistent_output_fails( + context: simplex::TestContext, +) -> anyhow::Result<()> { + let output_index = u32::MAX; + + case(AssertOutputIsOpReturn) + .output_index(output_index) + .op_return(DEFAULT_DATA) + .expecting(&context, Expect::AssertFailed) +} + +#[simplex::test] +fn assert_output_is_op_return_fail(context: simplex::TestContext) -> anyhow::Result<()> { + let output_index = 0; + + case(AssertOutputIsOpReturn) + .output_index(output_index) + .expecting(&context, Expect::AssertFailed) +} + +#[simplex::test] +fn assert_has_entry_pass(context: simplex::TestContext) -> anyhow::Result<()> { + let output_index = 0; + + case(AssertHasEntry) + .output_index(output_index) + .op_return(DEFAULT_DATA) + .run(&context) +} + +#[simplex::test] +fn assert_has_entry_nonexistent_output_fails(context: simplex::TestContext) -> anyhow::Result<()> { + let output_index = u32::MAX; + + case(AssertHasEntry) + .output_index(output_index) + .op_return(DEFAULT_DATA) + .expecting(&context, Expect::AssertFailed) +} + +#[simplex::test] +fn assert_has_entry_output_not_op_return_fails( + context: simplex::TestContext, +) -> anyhow::Result<()> { + let output_index = 0; + + case(AssertHasEntry) + .output_index(output_index) + .expecting(&context, Expect::AssertFailed) +} + +#[simplex::test] +fn assert_has_entry_out_of_range_entry_fails(context: simplex::TestContext) -> anyhow::Result<()> { + let output_index = 0; + let entry_index = u32::MAX; + + case(AssertHasEntry) + .output_index(output_index) + .entry_index(entry_index) + .op_return(DEFAULT_DATA) + .expecting(&context, Expect::AssertFailed) +}