From 565c0bea749f2fe6155ff1eeca28d6ee93b756cd Mon Sep 17 00:00:00 2001 From: KtorZ Date: Thu, 30 Mar 2023 18:55:14 +0200 Subject: [PATCH 01/13] Write JSON deserializers for Reference, Constructors, Items and Data. This is needed in order to deserialize a JSON blueprint and use it to perform validation. Still TODO: - [ ] Write JSON deserializer for 'Schema' Which should now be relatively straightforward. --- Cargo.lock | 619 +++++++++++------- crates/aiken-project/Cargo.toml | 3 + .../src/blueprint/definitions.rs | 46 ++ crates/aiken-project/src/blueprint/schema.rs | 391 ++++++++++- 4 files changed, 811 insertions(+), 248 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index abde28d0..ebcce565 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -133,6 +133,7 @@ dependencies = [ "pallas", "pallas-traverse", "petgraph", + "proptest", "pulldown-cmark", "rayon", "regex", @@ -149,10 +150,50 @@ dependencies = [ ] [[package]] -name = "anyhow" -version = "1.0.69" +name = "anstream" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" +checksum = "342258dd14006105c2b75ab1bd7543a03bdf0cfc94383303ac212a04939dff6f" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-wincon", + "concolor-override", + "concolor-query", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23ea9e81bd02e310c216d080f6223c179012256e5151c41db88d12c88a1684d2" + +[[package]] +name = "anstyle-parse" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7d1bb534e9efed14f3e5f44e7dd1a4f709384023a4165199a4241e18dff0116" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-wincon" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3127af6145b149f3287bb9a0d10ad9c5692dba8c53ad48285e5bec4063834fa" +dependencies = [ + "anstyle", + "windows-sys 0.45.0", +] + +[[package]] +name = "anyhow" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" [[package]] name = "arrayvec" @@ -175,9 +216,9 @@ dependencies = [ [[package]] name = "askama_derive" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e80b5ad1afe82872b7aa3e9de9b206ecb85584aa324f0f60fa4c903ce935936b" +checksum = "c22fbe0413545c098358e56966ff22cdd039e10215ae213cfbd65032b119fc94" dependencies = [ "basic-toml", "mime", @@ -186,7 +227,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn", + "syn 2.0.13", ] [[package]] @@ -237,6 +278,15 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "backtrace-ext" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50" +dependencies = [ + "backtrace", +] + [[package]] name = "base16ct" version = "0.2.0" @@ -299,18 +349,18 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "block-buffer" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "bstr" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ffdb39cb703212f3c11973452c2861b972f757b021158f3516ba10f2fa8b2c1" +checksum = "c3d4260bcc2e8fc9df1eac4919a720effeb63a3f0952f5bf4944adfa18897f09" dependencies = [ "memchr", "serde", @@ -391,42 +441,62 @@ dependencies = [ [[package]] name = "clap" -version = "4.1.8" +version = "4.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d7ae14b20b94cb02149ed21a86c423859cbe18dc7ed69845cace50e52b40a5" +checksum = "046ae530c528f252094e4a77886ee1374437744b2bff1497aa898bbddbbb29b3" dependencies = [ - "bitflags", + "clap_builder", "clap_derive", - "clap_lex", - "is-terminal", "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "223163f58c9a40c3b0a43e1c4b50a9ce09f007ea2cb1ec258a687945b4b7929f" +dependencies = [ + "anstream", + "anstyle", + "bitflags", + "clap_lex", "strsim", - "termcolor", - "terminal_size 0.2.5", + "terminal_size 0.2.6", "unicase", "unicode-width", ] [[package]] name = "clap_derive" -version = "4.1.8" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44bec8e5c9d09e439c4335b1af0abaab56dcf3b94999a936e1bb47b9134288f0" +checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" dependencies = [ "heck", - "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 2.0.13", ] [[package]] name = "clap_lex" -version = "0.3.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "350b9cf31731f9957399229e9b2adc51eeabdfbe9d71d9a0552275fd12710d09" +checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" + +[[package]] +name = "concolor-override" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a855d4a1978dc52fb0536a04d384c2c0c1aa273597f08b77c8c4d3b2eec6037f" + +[[package]] +name = "concolor-query" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf" dependencies = [ - "os_str_bytes", + "windows-sys 0.45.0", ] [[package]] @@ -453,15 +523,15 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181" dependencies = [ "libc", ] @@ -520,9 +590,9 @@ dependencies = [ [[package]] name = "crypto-bigint" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071c0f5945634bc9ba7a452f492377dd6b1993665ddb58f28704119b32f07a9a" +checksum = "7c2538c4e68e52548bacb3e83ac549f903d44f011ac9d5abb5e132e67d0808f7" dependencies = [ "generic-array", "rand_core", @@ -553,14 +623,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "der" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc906908ea6458456e5eaa160a9c08543ec3d1e6f71e2235cedd660cb65f9df0" +checksum = "82b10af9f9f9f2134a42d3f8aa74658660f2e0234b0eb81bd171df8aa32779ed" dependencies = [ "const-oid", "zeroize", @@ -579,6 +649,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer", + "const-oid", "crypto-common", "subtle", ] @@ -605,11 +676,12 @@ dependencies = [ [[package]] name = "ecdsa" -version = "0.16.0" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbcfcadd7eade8d8f960aa721e9731a50081694d3118c80eba744cbf68c7e5db" +checksum = "106401dadc137d05cb0d4ab4d42be089746aefdfe8992df4d0edcf351c16ddca" dependencies = [ "der", + "digest", "elliptic-curve", "rfc6979", "signature", @@ -623,9 +695,9 @@ checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" [[package]] name = "elliptic-curve" -version = "0.13.1" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b984fcbd8df0166b077ec083cbfe076fdffb6e2de92d966794fd060794b620d7" +checksum = "22cdacd4d6ed3f9b98680b679c0e52a823b8a2c7a97358d508fe247f2180c282" dependencies = [ "base16ct", "crypto-bigint", @@ -651,13 +723,13 @@ dependencies = [ [[package]] name = "errno" -version = "0.2.8" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -756,9 +828,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", @@ -771,9 +843,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", @@ -781,15 +853,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-executor" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" dependencies = [ "futures-core", "futures-task", @@ -798,38 +870,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-macro" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.13", ] [[package]] name = "futures-sink" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-channel", "futures-core", @@ -845,9 +917,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -856,9 +928,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if", "libc", @@ -1019,9 +1091,9 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.24" +version = "0.14.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c" +checksum = "cc5e554ff619822309ffd57d8734d77cd5ce6238bc956f037ea06c58238c9899" dependencies = [ "bytes", "futures-channel", @@ -1083,9 +1155,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown", @@ -1108,30 +1180,31 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.6" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfa919a82ea574332e2de6e74b4c36e74d41982b335080fa59d4ef31be20fdf3" +checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" dependencies = [ + "hermit-abi 0.3.1", "libc", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] name = "ipnet" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" +checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" [[package]] name = "is-terminal" -version = "0.4.4" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b6b32576413a8e69b90e952e4a026476040d81017b80445deda5f2d3921857" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" dependencies = [ "hermit-abi 0.3.1", "io-lifetimes", "rustix", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -1195,9 +1268,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.139" +version = "0.2.141" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" [[package]] name = "libm" @@ -1207,9 +1280,9 @@ checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" [[package]] name = "linux-raw-sys" -version = "0.1.4" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" [[package]] name = "lock_api" @@ -1272,16 +1345,17 @@ dependencies = [ [[package]] name = "miette" -version = "5.5.0" +version = "5.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4afd9b301defa984bbdbe112b4763e093ed191750a0d914a78c1106b2d0fe703" +checksum = "7abdc09c381c9336b9f2e9bd6067a9a5290d20e2d2e2296f275456121c33ae89" dependencies = [ - "atty", "backtrace", + "backtrace-ext", + "is-terminal", "miette-derive", "once_cell", "owo-colors", - "supports-color", + "supports-color 2.0.0", "supports-hyperlinks", "supports-unicode", "terminal_size 0.1.17", @@ -1292,20 +1366,20 @@ dependencies = [ [[package]] name = "miette-derive" -version = "5.5.0" +version = "5.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97c2401ab7ac5282ca5c8b518a87635b1a93762b0b90b9990c509888eeccba29" +checksum = "8842972f23939443013dfd3720f46772b743e86f1a81d120d4b6fb090f87de1c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.13", ] [[package]] name = "mime" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" @@ -1319,9 +1393,9 @@ dependencies = [ [[package]] name = "minicbor" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d319d47468f164e5138b1c629bdd82ea3da0784ed1d41a22f8e0bcef76c2ae52" +checksum = "d7005aaf257a59ff4de471a9d5538ec868a21586534fff7f85dd97d4043a6139" dependencies = [ "half", "minicbor-derive", @@ -1335,7 +1409,7 @@ checksum = "1154809406efdb7982841adb6311b3d095b46f78342dd646736122fe6b19e267" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1472,13 +1546,13 @@ dependencies = [ [[package]] name = "openssl-macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.13", ] [[package]] @@ -1508,12 +1582,6 @@ dependencies = [ "num-integer", ] -[[package]] -name = "os_str_bytes" -version = "6.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" - [[package]] name = "output_vt100" version = "0.1.3" @@ -1529,7 +1597,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" dependencies = [ - "supports-color", + "supports-color 1.3.1", ] [[package]] @@ -1663,7 +1731,7 @@ checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", "windows-sys 0.45.0", ] @@ -1748,9 +1816,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkcs8" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d2820d87d2b008616e5c27212dd9e0e694fb4c6b522de06094106813328cb49" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ "der", "spki", @@ -1792,35 +1860,11 @@ dependencies = [ "yansi", ] -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - [[package]] name = "proc-macro2" -version = "1.0.51" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" dependencies = [ "unicode-ident", ] @@ -1880,9 +1924,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quote" -version = "1.0.23" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ "proc-macro2", ] @@ -1957,6 +2001,15 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + [[package]] name = "redox_users" version = "0.4.3" @@ -1964,15 +2017,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ "getrandom", - "redox_syscall", + "redox_syscall 0.2.16", "thiserror", ] [[package]] name = "regex" -version = "1.7.1" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" dependencies = [ "aho-corasick", "memchr", @@ -1981,15 +2034,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.28" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "reqwest" -version = "0.11.14" +version = "0.11.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9" +checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254" dependencies = [ "base64", "bytes", @@ -2034,22 +2087,22 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" +checksum = "d4a36c42d1873f9a77c53bde094f9664d9891bc604a45b4798fd2c389ed12e5b" [[package]] name = "rustix" -version = "0.36.9" +version = "0.37.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc" +checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -2125,9 +2178,9 @@ dependencies = [ [[package]] name = "secp256k1-sys" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "642a62736682fdd8c71da0eb273e453c8ac74e33b9fb310e22ba5b03ec7651ff" +checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" dependencies = [ "cc", ] @@ -2157,29 +2210,29 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.153" +version = "1.0.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a382c72b4ba118526e187430bb4963cd6d55051ebf13d9b25574d379cc98d20" +checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.153" +version = "1.0.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ef476a5790f0f6decbc66726b6e5d63680ed518283e64c7df415989d880954f" +checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.13", ] [[package]] name = "serde_json" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" +checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744" dependencies = [ "indexmap", "itoa", @@ -2189,13 +2242,13 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395627de918015623b32e7669714206363a7fc00382bf477e72c1f7533e8eafc" +checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.13", ] [[package]] @@ -2252,9 +2305,9 @@ dependencies = [ [[package]] name = "signature" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fe458c98333f9c8152221191a77e2a44e8325d0193484af2e9421a53019e57d" +checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" dependencies = [ "digest", "rand_core", @@ -2293,9 +2346,9 @@ dependencies = [ [[package]] name = "spki" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0445c905640145c7ea8c1993555957f65e7c46d0535b91ba501bc9bfc85522f" +checksum = "37a5be806ab6f127c3da44b7378837ebf01dadca8510a0e572460216b228bd0e" dependencies = [ "base64ct", "der", @@ -2345,7 +2398,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn", + "syn 1.0.109", ] [[package]] @@ -2365,21 +2418,31 @@ dependencies = [ ] [[package]] -name = "supports-hyperlinks" -version = "1.2.0" +name = "supports-color" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "590b34f7c5f01ecc9d78dba4b3f445f31df750a67621cf31626f3b7441ce6406" +checksum = "4950e7174bffabe99455511c39707310e7e9b440364a2fcb1cc21521be57b354" dependencies = [ - "atty", + "is-terminal", + "is_ci", +] + +[[package]] +name = "supports-hyperlinks" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b4806e0b03b9906e76b018a5d821ebf198c8e9dc0829ed3328eeeb5094aed60" +dependencies = [ + "is-terminal", ] [[package]] name = "supports-unicode" -version = "1.0.2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8b945e45b417b125a8ec51f1b7df2f8df7920367700d1f98aedd21e5735f8b2" +checksum = "4b6c2cb240ab5dd21ed4906895ee23fe5a48acdbd15a3ce388e7b62a9b66baf7" dependencies = [ - "atty", + "is-terminal", ] [[package]] @@ -2394,25 +2457,27 @@ dependencies = [ ] [[package]] -name = "tempfile" -version = "3.4.0" +name = "syn" +version = "2.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" +checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" dependencies = [ - "cfg-if", - "fastrand", - "redox_syscall", - "rustix", - "windows-sys 0.42.0", + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] -name = "termcolor" -version = "1.2.0" +name = "tempfile" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" dependencies = [ - "winapi-util", + "cfg-if", + "fastrand", + "redox_syscall 0.3.5", + "rustix", + "windows-sys 0.45.0", ] [[package]] @@ -2427,12 +2492,12 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c9afddd2cec1c0909f06b00ef33f94ab2cc0578c4a610aa208ddfec8aa2b43a" +checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" dependencies = [ "rustix", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -2448,22 +2513,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.13", ] [[package]] @@ -2509,14 +2574,13 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.26.0" +version = "1.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64" +checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" dependencies = [ "autocfg", "bytes", "libc", - "memchr", "mio", "num_cpus", "parking_lot", @@ -2529,13 +2593,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.8.2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" +checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.13", ] [[package]] @@ -2564,9 +2628,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7afcae9e3f0fe2c370fd4657108972cbb2fa9db1b9f84849cefd80741b01cb6" +checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21" dependencies = [ "serde", "serde_spanned", @@ -2585,9 +2649,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.19.4" +version = "0.19.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a1eb0622d28f4b9c90adc4ea4b2b46b47663fde9ac5fafcb14a1369d5508825" +checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" dependencies = [ "indexmap", "serde", @@ -2622,7 +2686,7 @@ checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2669,9 +2733,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.10" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" @@ -2762,9 +2826,9 @@ checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9" [[package]] name = "utf8parse" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "vcpkg" @@ -2816,12 +2880,11 @@ dependencies = [ [[package]] name = "walkdir" -version = "2.3.2" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" dependencies = [ "same-file", - "winapi", "winapi-util", ] @@ -2862,7 +2925,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-shared", ] @@ -2896,7 +2959,7 @@ checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2954,13 +3017,13 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] @@ -2969,71 +3032,137 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets", + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", ] [[package]] name = "windows-targets" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] name = "windows_aarch64_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_i686_gnu" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_x86_64_gnu" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" -version = "0.3.5" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee7b2c67f962bf5042bfd8b6a916178df33a26eec343ae064cb8e069f638fa6f" +checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28" dependencies = [ "memchr", ] @@ -3055,9 +3184,9 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "zeroize" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" [[package]] name = "zip" @@ -3100,9 +3229,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.7+zstd.1.5.4" +version = "2.0.8+zstd.1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94509c3ba2fe55294d752b79842c530ccfab760192521df74a081a78d2b3c7f5" +checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" dependencies = [ "cc", "libc", diff --git a/crates/aiken-project/Cargo.toml b/crates/aiken-project/Cargo.toml index 993ebd7c..fbc72bda 100644 --- a/crates/aiken-project/Cargo.toml +++ b/crates/aiken-project/Cargo.toml @@ -37,3 +37,6 @@ toml = "0.7.2" uplc = { path = '../uplc', version = "0.0.29" } walkdir = "2.3.2" zip = "0.6.4" + +[dev-dependencies] +proptest = "1.1.0" diff --git a/crates/aiken-project/src/blueprint/definitions.rs b/crates/aiken-project/src/blueprint/definitions.rs index 91d5e582..20b042c2 100644 --- a/crates/aiken-project/src/blueprint/definitions.rs +++ b/crates/aiken-project/src/blueprint/definitions.rs @@ -1,6 +1,7 @@ use aiken_lang::tipo::{Type, TypeVar}; use serde::{ self, + de::{self, Deserialize, Deserializer, MapAccess, Visitor}, ser::{Serialize, SerializeStruct, Serializer}, }; use std::{ @@ -181,3 +182,48 @@ impl Serialize for Reference { s.end() } } + +impl<'a> Deserialize<'a> for Reference { + fn deserialize>(deserializer: D) -> Result { + #[derive(serde::Deserialize)] + enum Field { + #[serde(rename = "$ref")] + Ref, + } + const FIELDS: &[&str] = &["$ref"]; + + struct ReferenceVisitor; + + impl<'a> Visitor<'a> for ReferenceVisitor { + type Value = Reference; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("Reference") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'a>, + { + let mut inner = None; + + while let Some(key) = map.next_key()? { + match key { + Field::Ref => { + if inner.is_some() { + return Err(de::Error::duplicate_field(FIELDS[0])); + } + inner = Some(map.next_value()?); + } + } + } + + Ok(Reference { + inner: inner.ok_or_else(|| de::Error::missing_field(FIELDS[0]))?, + }) + } + } + + deserializer.deserialize_struct("Reference", FIELDS, ReferenceVisitor) + } +} diff --git a/crates/aiken-project/src/blueprint/schema.rs b/crates/aiken-project/src/blueprint/schema.rs index 6e3ef11f..8b4459a7 100644 --- a/crates/aiken-project/src/blueprint/schema.rs +++ b/crates/aiken-project/src/blueprint/schema.rs @@ -8,10 +8,10 @@ use aiken_lang::{ use owo_colors::{OwoColorize, Stream::Stdout}; use serde::{ self, + de::{self, Deserialize, Deserializer, MapAccess, Visitor}, ser::{Serialize, SerializeStruct, Serializer}, }; -use std::ops::Deref; -use std::{collections::HashMap, sync::Arc}; +use std::{collections::HashMap, fmt, ops::Deref, sync::Arc}; // NOTE: Can be anything BUT 0 pub const REDEEMER_DISCRIMINANT: usize = 1; @@ -44,14 +44,17 @@ pub enum Schema { pub enum Data { Integer, Bytes, + // TODO: Generalize to work with either Reference or Data List(Items), + // TODO: Generalize to work with either Reference or Data Map(Box, Box), AnyOf(Vec>), Opaque, } /// A structure that represents either one or many elements. -#[derive(Debug, PartialEq, Eq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone, serde::Deserialize)] +#[serde(untagged)] pub enum Items { One(Box), Many(Vec), @@ -61,6 +64,7 @@ pub enum Items { #[derive(Debug, PartialEq, Eq, Clone)] pub struct Constructor { pub index: usize, + // TODO: Generalize to work with either Reference or Data pub fields: Vec>, } @@ -554,6 +558,170 @@ impl Serialize for Data { } } } + +impl<'a> Deserialize<'a> for Data { + fn deserialize>(deserializer: D) -> Result { + #[derive(serde::Deserialize)] + #[serde(field_identifier, rename_all = "camelCase")] + enum Field { + DataType, + Items, + Keys, + Values, + AnyOf, + OneOf, + } + const FIELDS: &[&str] = &["dataType", "items", "keys", "values", "anyOf", "oneOf"]; + + struct DataVisitor; + + impl<'a> Visitor<'a> for DataVisitor { + type Value = Data; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("Data") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'a>, + { + let mut data_type: Option = None; + let mut items: Option> = None; + let mut keys = None; + let mut values = None; + let mut any_of = None; + + while let Some(key) = map.next_key()? { + match key { + Field::DataType => { + if data_type.is_some() { + return Err(de::Error::duplicate_field(FIELDS[0])); + } + data_type = Some(map.next_value()?); + } + Field::Items => { + if items.is_some() { + return Err(de::Error::duplicate_field(FIELDS[1])); + } + items = Some(map.next_value()?); + } + Field::Keys => { + if keys.is_some() { + return Err(de::Error::duplicate_field(FIELDS[2])); + } + keys = Some(map.next_value()?); + } + Field::Values => { + if values.is_some() { + return Err(de::Error::duplicate_field(FIELDS[3])); + } + values = Some(map.next_value()?); + } + Field::AnyOf => { + if any_of.is_some() { + return Err(de::Error::duplicate_field(FIELDS[4])); + } + any_of = Some(map.next_value()?); + } + Field::OneOf => { + if any_of.is_some() { + return Err(de::Error::duplicate_field(FIELDS[5])); + } + any_of = Some(map.next_value()?); + } + } + } + + let expect_no_items = || { + if items.is_some() { + return Err(de::Error::custom( + "unexpected fields 'items' for non-list data-type", + )); + } + Ok(()) + }; + + let expect_no_keys = || { + if keys.is_some() { + return Err(de::Error::custom( + "unexpected fields 'keys' for non-map data-type", + )); + } + Ok(()) + }; + + let expect_no_values = || { + if values.is_some() { + return Err(de::Error::custom( + "unexpected fields 'values' for non-map data-type", + )); + } + Ok(()) + }; + + let expect_no_any_of = || { + if any_of.is_some() { + return Err(de::Error::custom( + "unexpected fields 'anyOf' or 'oneOf'; applicators must singletons", + )); + } + Ok(()) + }; + + match data_type { + None => { + expect_no_items()?; + expect_no_keys()?; + expect_no_values()?; + match any_of { + None => Ok(Data::Opaque), + Some(constructors) => Ok(Data::AnyOf(constructors)), + } + } + Some(data_type) if data_type == "integer" => { + expect_no_items()?; + expect_no_keys()?; + expect_no_values()?; + expect_no_any_of()?; + Ok(Data::Integer) + } + Some(data_type) if data_type == "bytes" => { + expect_no_items()?; + expect_no_keys()?; + expect_no_values()?; + expect_no_any_of()?; + Ok(Data::Bytes) + } + Some(data_type) if data_type == "list" => { + expect_no_keys()?; + expect_no_values()?; + expect_no_any_of()?; + match items { + None => Err(de::Error::missing_field(FIELDS[1])), + Some(items) => Ok(Data::List(items)), + } + } + Some(data_type) if data_type == "map" => { + expect_no_items()?; + expect_no_any_of()?; + match (keys, values) { + (Some(keys), Some(values)) => { + Ok(Data::Map(Box::new(keys), Box::new(values))) + } + (None, _) => Err(de::Error::missing_field(FIELDS[2])), + (Some(..), None) => Err(de::Error::missing_field(FIELDS[3])), + } + } + Some(..) => Err(de::Error::custom("unknown data-type")), + } + } + } + + deserializer.deserialize_struct("Data", FIELDS, DataVisitor) + } +} + impl Serialize for Constructor { fn serialize(&self, serializer: S) -> Result { let mut s = serializer.serialize_struct("Constructor", 3)?; @@ -564,6 +732,60 @@ impl Serialize for Constructor { } } +impl<'a> Deserialize<'a> for Constructor { + fn deserialize>(deserializer: D) -> Result { + #[derive(serde::Deserialize)] + #[serde(field_identifier, rename_all = "camelCase")] + enum Field { + Index, + Fields, + } + const FIELDS: &[&str] = &["index", "fields"]; + + struct ConstructorVisitor; + + impl<'a> Visitor<'a> for ConstructorVisitor { + type Value = Constructor; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("Constructor") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'a>, + { + let mut index = None; + let mut fields = None; + + while let Some(key) = map.next_key()? { + match key { + Field::Index => { + if index.is_some() { + return Err(de::Error::duplicate_field(FIELDS[0])); + } + index = Some(map.next_value()?); + } + Field::Fields => { + if fields.is_some() { + return Err(de::Error::duplicate_field(FIELDS[1])); + } + fields = Some(map.next_value()?); + } + } + } + + Ok(Constructor { + index: index.ok_or_else(|| de::Error::missing_field(FIELDS[0]))?, + fields: fields.ok_or_else(|| de::Error::missing_field(FIELDS[1]))?, + }) + } + } + + deserializer.deserialize_struct("Constructor", FIELDS, ConstructorVisitor) + } +} + #[derive(Debug, PartialEq, Clone, thiserror::Error)] #[error("{}", context)] pub struct Error { @@ -681,6 +903,7 @@ Here's the types I followed and that led me to this problem: #[cfg(test)] pub mod test { use super::*; + use proptest::prelude::*; use serde_json::{self, json, Value}; pub fn assert_json(schema: &impl Serialize, expected: Value) { @@ -848,4 +1071,166 @@ pub mod test { }), ) } + + #[test] + fn deserialize_data_opaque() { + assert_eq!(Data::Opaque, serde_json::from_value(json!({})).unwrap()) + } + + #[test] + fn deserialize_data_integer() { + assert_eq!( + Data::Integer, + serde_json::from_value(json!({ + "dataType": "integer", + })) + .unwrap() + ) + } + + #[test] + fn deserialize_data_bytes() { + assert_eq!( + Data::Bytes, + serde_json::from_value(json!({ + "dataType": "bytes", + })) + .unwrap() + ) + } + + #[test] + fn deserialize_data_list_one() { + assert_eq!( + Data::List(Items::One(Box::new(Reference::new("foo")))), + serde_json::from_value(json!({ + "dataType": "list", + "items": { "$ref": "foo" } + })) + .unwrap() + ) + } + + #[test] + fn deserialize_data_list_many() { + assert_eq!( + Data::List(Items::Many(vec![ + Reference::new("foo"), + Reference::new("bar") + ])), + serde_json::from_value(json!({ + "dataType": "list", + "items": [ + { "$ref": "foo" }, + { "$ref": "bar" } + ], + })) + .unwrap() + ) + } + + #[test] + fn deserialize_data_map() { + assert_eq!( + Data::Map( + Box::new(Reference::new("foo")), + Box::new(Reference::new("bar")) + ), + serde_json::from_value(json!({ + "dataType": "map", + "keys": { "$ref": "foo" }, + "values": { "$ref": "bar" } + })) + .unwrap() + ) + } + + #[test] + fn deserialize_any_of() { + assert_eq!( + Data::AnyOf(vec![Constructor { + index: 0, + fields: vec![Reference::new("foo").into(), Reference::new("bar").into()], + } + .into()]), + serde_json::from_value(json!({ + "anyOf": [{ + "index": 0, + "fields": [ + { + "$ref": "foo", + }, + { + "$ref": "bar", + } + ] + }] + })) + .unwrap() + ) + } + + #[test] + fn deserialize_one_of() { + assert_eq!( + Data::AnyOf(vec![Constructor { + index: 0, + fields: vec![Reference::new("foo").into(), Reference::new("bar").into()], + } + .into()]), + serde_json::from_value(json!({ + "oneOf": [{ + "index": 0, + "fields": [ + { + "$ref": "foo", + }, + { + "$ref": "bar", + } + ] + }] + })) + .unwrap() + ) + } + + fn arbitrary_data() -> impl Strategy { + let r = prop_oneof![".*".prop_map(|s| Reference::new(&s))]; + let constructor = + (0..3usize, prop::collection::vec(r.clone(), 0..3)).prop_map(|(index, fields)| { + Constructor { + index, + fields: fields.into_iter().map(|f| f.into()).collect(), + } + .into() + }); + prop_oneof![ + Just(Data::Opaque), + Just(Data::Bytes), + Just(Data::Integer), + (r.clone(), r.clone()).prop_map(|(k, v)| Data::Map(Box::new(k), Box::new(v))), + r.clone().prop_map(|x| Data::List(Items::One(Box::new(x)))), + prop::collection::vec(r, 1..3).prop_map(|xs| Data::List(Items::Many(xs))), + prop::collection::vec(constructor, 1..3).prop_map(Data::AnyOf) + ] + } + + proptest! { + #[test] + fn data_serialization_roundtrip(data in arbitrary_data()) { + let json = serde_json::to_value(data); + let pretty = json + .as_ref() + .map(|v| serde_json::to_string_pretty(v).unwrap()) + .unwrap_or_else(|_| "invalid".to_string()); + assert!( + matches!( + json.and_then(serde_json::from_value::), + Ok{..} + ), + "\ncounterexample: {pretty}\n", + ) + } + } } From f0d2d20a4cca26e9ee7e754feea1422050ace9d5 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Wed, 5 Apr 2023 11:40:01 +0200 Subject: [PATCH 02/13] Impl JSON deserialization for blueprint's 'Schema'. --- crates/aiken-project/src/blueprint/schema.rs | 430 ++++++++++++------- 1 file changed, 279 insertions(+), 151 deletions(-) diff --git a/crates/aiken-project/src/blueprint/schema.rs b/crates/aiken-project/src/blueprint/schema.rs index 8b4459a7..2f0b7958 100644 --- a/crates/aiken-project/src/blueprint/schema.rs +++ b/crates/aiken-project/src/blueprint/schema.rs @@ -34,8 +34,10 @@ pub enum Schema { Integer, Bytes, String, - Pair(Data, Data), - List(Vec), + // TODO: Generalize to work with either Reference or Data + Pair(Reference, Reference), + // TODO: Generalize to work with either Reference or Data + List(Items), Data(Data), } @@ -53,7 +55,7 @@ pub enum Data { } /// A structure that represents either one or many elements. -#[derive(Debug, PartialEq, Eq, Clone, serde::Deserialize)] +#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)] #[serde(untagged)] pub enum Items { One(Box), @@ -514,6 +516,231 @@ impl Serialize for Schema { } } +fn visit_schema<'a, V>(mut map: V) -> Result +where + V: MapAccess<'a>, +{ + #[derive(serde::Deserialize)] + #[serde(field_identifier, rename_all = "camelCase")] + enum Field { + DataType, + Items, + Keys, + Values, + Left, + Right, + AnyOf, + OneOf, + } + + let mut data_type: Option = None; + let mut items: Option> = None; + let mut keys = None; + let mut left = None; + let mut right = None; + let mut values = None; + let mut any_of = None; + + while let Some(key) = map.next_key()? { + match key { + Field::DataType => { + if data_type.is_some() { + return Err(de::Error::duplicate_field("dataType")); + } + data_type = Some(map.next_value()?); + } + Field::Items => { + if items.is_some() { + return Err(de::Error::duplicate_field("items")); + } + items = Some(map.next_value()?); + } + Field::Keys => { + if keys.is_some() { + return Err(de::Error::duplicate_field("keys")); + } + keys = Some(map.next_value()?); + } + Field::Values => { + if values.is_some() { + return Err(de::Error::duplicate_field("values")); + } + values = Some(map.next_value()?); + } + Field::Left => { + if left.is_some() { + return Err(de::Error::duplicate_field("left")); + } + left = Some(map.next_value()?); + } + Field::Right => { + if right.is_some() { + return Err(de::Error::duplicate_field("right")); + } + right = Some(map.next_value()?); + } + Field::AnyOf => { + if any_of.is_some() { + return Err(de::Error::duplicate_field("anyOf/oneOf")); + } + any_of = Some(map.next_value()?); + } + Field::OneOf => { + if any_of.is_some() { + return Err(de::Error::duplicate_field("anyOf/oneOf")); + } + any_of = Some(map.next_value()?); + } + } + } + + let expect_no_items = || { + if items.is_some() { + return Err(de::Error::custom( + "unexpected fields 'items' for non-list data-type", + )); + } + Ok(()) + }; + + let expect_no_keys = || { + if keys.is_some() { + return Err(de::Error::custom( + "unexpected fields 'keys' for non-map data-type", + )); + } + Ok(()) + }; + + let expect_no_values = || { + if values.is_some() { + return Err(de::Error::custom( + "unexpected fields 'values' for non-map data-type", + )); + } + Ok(()) + }; + + let expect_no_any_of = || { + if any_of.is_some() { + return Err(de::Error::custom( + "unexpected fields 'anyOf' or 'oneOf'; applicators must singletons", + )); + } + Ok(()) + }; + + let expect_no_left_or_right = || { + if left.is_some() || right.is_some() { + return Err(de::Error::custom( + "unexpected field(s) 'left' and/or 'right' for a non-pair data-type", + )); + } + Ok(()) + }; + + match data_type { + None => { + expect_no_items()?; + expect_no_keys()?; + expect_no_values()?; + expect_no_left_or_right()?; + match any_of { + None => Ok(Schema::Data(Data::Opaque)), + Some(constructors) => Ok(Schema::Data(Data::AnyOf(constructors))), + } + } + Some(data_type) if data_type == "list" || data_type == "#list" => { + expect_no_keys()?; + expect_no_values()?; + expect_no_any_of()?; + expect_no_left_or_right()?; + match items { + Some(items) if data_type == "list" => Ok(Schema::Data(Data::List(items))), + Some(items) if data_type == "#list" => Ok(Schema::List(items)), + Some(_) => unreachable!("condition checked in pattern guard"), + None => Err(de::Error::missing_field("items")), + } + } + Some(data_type) if data_type == "map" => { + expect_no_items()?; + expect_no_any_of()?; + expect_no_left_or_right()?; + match (keys, values) { + (Some(keys), Some(values)) => { + Ok(Schema::Data(Data::Map(Box::new(keys), Box::new(values)))) + } + (None, _) => Err(de::Error::missing_field("keys")), + (Some(..), None) => Err(de::Error::missing_field("values")), + } + } + Some(data_type) if data_type == "#pair" => { + expect_no_items()?; + expect_no_keys()?; + expect_no_values()?; + expect_no_any_of()?; + match (left, right) { + (Some(left), Some(right)) => Ok(Schema::Pair(left, right)), + (None, _) => Err(de::Error::missing_field("left")), + (Some(..), None) => Err(de::Error::missing_field("right")), + } + } + Some(data_type) => { + expect_no_items()?; + expect_no_keys()?; + expect_no_values()?; + expect_no_any_of()?; + expect_no_left_or_right()?; + if data_type == "bytes" { + Ok(Schema::Data(Data::Bytes)) + } else if data_type == "integer" { + Ok(Schema::Data(Data::Integer)) + } else if data_type == "#unit" { + Ok(Schema::Unit) + } else if data_type == "#integer" { + Ok(Schema::Integer) + } else if data_type == "#bytes" { + Ok(Schema::Bytes) + } else if data_type == "#boolean" { + Ok(Schema::Boolean) + } else if data_type == "#string" { + Ok(Schema::String) + } else { + Err(de::Error::custom("unknown data-type")) + } + } + } +} + +impl<'a> Deserialize<'a> for Schema { + fn deserialize>(deserializer: D) -> Result { + struct SchemaVisitor; + + impl<'a> Visitor<'a> for SchemaVisitor { + type Value = Schema; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("Schema") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'a>, + { + visit_schema(&mut map) + } + } + + deserializer.deserialize_struct( + "Schema", + &[ + "dataType", "items", "keys", "values", "anyOf", "oneOf", "left", "right", + ], + SchemaVisitor, + ) + } +} + impl Serialize for Data { fn serialize(&self, serializer: S) -> Result { match self { @@ -531,13 +758,7 @@ impl Serialize for Data { s.serialize_field("dataType", "bytes")?; s.end() } - Data::List(Items::One(item)) => { - let mut s = serializer.serialize_struct("List", 2)?; - s.serialize_field("dataType", "list")?; - s.serialize_field("items", &item)?; - s.end() - } - Data::List(Items::Many(items)) => { + Data::List(items) => { let mut s = serializer.serialize_struct("List", 2)?; s.serialize_field("dataType", "list")?; s.serialize_field("items", &items)?; @@ -561,18 +782,6 @@ impl Serialize for Data { impl<'a> Deserialize<'a> for Data { fn deserialize>(deserializer: D) -> Result { - #[derive(serde::Deserialize)] - #[serde(field_identifier, rename_all = "camelCase")] - enum Field { - DataType, - Items, - Keys, - Values, - AnyOf, - OneOf, - } - const FIELDS: &[&str] = &["dataType", "items", "keys", "values", "anyOf", "oneOf"]; - struct DataVisitor; impl<'a> Visitor<'a> for DataVisitor { @@ -586,139 +795,19 @@ impl<'a> Deserialize<'a> for Data { where V: MapAccess<'a>, { - let mut data_type: Option = None; - let mut items: Option> = None; - let mut keys = None; - let mut values = None; - let mut any_of = None; - - while let Some(key) = map.next_key()? { - match key { - Field::DataType => { - if data_type.is_some() { - return Err(de::Error::duplicate_field(FIELDS[0])); - } - data_type = Some(map.next_value()?); - } - Field::Items => { - if items.is_some() { - return Err(de::Error::duplicate_field(FIELDS[1])); - } - items = Some(map.next_value()?); - } - Field::Keys => { - if keys.is_some() { - return Err(de::Error::duplicate_field(FIELDS[2])); - } - keys = Some(map.next_value()?); - } - Field::Values => { - if values.is_some() { - return Err(de::Error::duplicate_field(FIELDS[3])); - } - values = Some(map.next_value()?); - } - Field::AnyOf => { - if any_of.is_some() { - return Err(de::Error::duplicate_field(FIELDS[4])); - } - any_of = Some(map.next_value()?); - } - Field::OneOf => { - if any_of.is_some() { - return Err(de::Error::duplicate_field(FIELDS[5])); - } - any_of = Some(map.next_value()?); - } - } - } - - let expect_no_items = || { - if items.is_some() { - return Err(de::Error::custom( - "unexpected fields 'items' for non-list data-type", - )); - } - Ok(()) - }; - - let expect_no_keys = || { - if keys.is_some() { - return Err(de::Error::custom( - "unexpected fields 'keys' for non-map data-type", - )); - } - Ok(()) - }; - - let expect_no_values = || { - if values.is_some() { - return Err(de::Error::custom( - "unexpected fields 'values' for non-map data-type", - )); - } - Ok(()) - }; - - let expect_no_any_of = || { - if any_of.is_some() { - return Err(de::Error::custom( - "unexpected fields 'anyOf' or 'oneOf'; applicators must singletons", - )); - } - Ok(()) - }; - - match data_type { - None => { - expect_no_items()?; - expect_no_keys()?; - expect_no_values()?; - match any_of { - None => Ok(Data::Opaque), - Some(constructors) => Ok(Data::AnyOf(constructors)), - } - } - Some(data_type) if data_type == "integer" => { - expect_no_items()?; - expect_no_keys()?; - expect_no_values()?; - expect_no_any_of()?; - Ok(Data::Integer) - } - Some(data_type) if data_type == "bytes" => { - expect_no_items()?; - expect_no_keys()?; - expect_no_values()?; - expect_no_any_of()?; - Ok(Data::Bytes) - } - Some(data_type) if data_type == "list" => { - expect_no_keys()?; - expect_no_values()?; - expect_no_any_of()?; - match items { - None => Err(de::Error::missing_field(FIELDS[1])), - Some(items) => Ok(Data::List(items)), - } - } - Some(data_type) if data_type == "map" => { - expect_no_items()?; - expect_no_any_of()?; - match (keys, values) { - (Some(keys), Some(values)) => { - Ok(Data::Map(Box::new(keys), Box::new(values))) - } - (None, _) => Err(de::Error::missing_field(FIELDS[2])), - (Some(..), None) => Err(de::Error::missing_field(FIELDS[3])), - } - } - Some(..) => Err(de::Error::custom("unknown data-type")), + let schema = visit_schema(&mut map)?; + match schema { + Schema::Data(data) => Ok(data), + _ => Err(de::Error::custom("not a valid 'data'")), } } } - deserializer.deserialize_struct("Data", FIELDS, DataVisitor) + deserializer.deserialize_struct( + "Data", + &["dataType", "items", "keys", "values", "anyOf", "oneOf"], + DataVisitor, + ) } } @@ -1216,6 +1305,27 @@ pub mod test { ] } + fn arbitrary_schema() -> impl Strategy { + let r = prop_oneof![".*".prop_map(|s| Reference::new(&s))]; + prop_compose! { + fn data_strategy()(data in arbitrary_data()) -> Schema { + Schema::Data(data) + } + } + prop_oneof![ + Just(Schema::Unit), + Just(Schema::Boolean), + Just(Schema::Bytes), + Just(Schema::Integer), + Just(Schema::String), + (r.clone(), r.clone()).prop_map(|(l, r)| Schema::Pair(l, r)), + r.clone() + .prop_map(|x| Schema::List(Items::One(Box::new(x)))), + prop::collection::vec(r, 1..3).prop_map(|xs| Schema::List(Items::Many(xs))), + data_strategy(), + ] + } + proptest! { #[test] fn data_serialization_roundtrip(data in arbitrary_data()) { @@ -1233,4 +1343,22 @@ pub mod test { ) } } + + proptest! { + #[test] + fn schema_serialization_roundtrip(schema in arbitrary_schema()) { + let json = serde_json::to_value(schema); + let pretty = json + .as_ref() + .map(|v| serde_json::to_string_pretty(v).unwrap()) + .unwrap_or_else(|_| "invalid".to_string()); + assert!( + matches!( + json.and_then(serde_json::from_value::), + Ok{..} + ), + "\ncounterexample: {pretty}\n", + ) + } + } } From cdc7ebf9a0707f4f794c8decbb30fcbb1fa00a1d Mon Sep 17 00:00:00 2001 From: KtorZ Date: Wed, 5 Apr 2023 11:51:26 +0200 Subject: [PATCH 03/13] Remove now-unnecessary blueprint generics. These were needed before as a way to _partially deserialize_ blueprints. Indeed, some commands required accessing information of the blueprint, but not necessarily the schema. So out of laziness (or cleverness?), we only deserialized validators as serde::Value and achieved that through the use of generics. Now that validators and schemas have proper deserialisers, we can simply deserialize a blueprint. TODO: Our serialisation/deserialisation is safe with regards to itself; i.e. it roundtrips. However, we only supports a subset of the specified blueprint format. For example, we would fail to deserialize blueprints that have inline data-schemas (we only use references). --- crates/aiken-project/src/blueprint/mod.rs | 26 ++++++++----------- .../aiken-project/src/blueprint/validator.rs | 26 ++++++++----------- crates/aiken-project/src/lib.rs | 19 ++++---------- crates/aiken/src/cmd/blueprint/convert.rs | 2 +- 4 files changed, 28 insertions(+), 45 deletions(-) diff --git a/crates/aiken-project/src/blueprint/mod.rs b/crates/aiken-project/src/blueprint/mod.rs index ae5334ae..f9792d4b 100644 --- a/crates/aiken-project/src/blueprint/mod.rs +++ b/crates/aiken-project/src/blueprint/mod.rs @@ -5,18 +5,18 @@ pub mod validator; use crate::{config::Config, module::CheckedModules}; use aiken_lang::gen_uplc::CodeGenerator; -use definitions::{Definitions, Reference}; +use definitions::Definitions; use error::Error; use schema::{Annotated, Schema}; use std::fmt::Debug; use validator::Validator; #[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)] -pub struct Blueprint { +pub struct Blueprint { pub preamble: Preamble, - pub validators: Vec>, + pub validators: Vec, #[serde(skip_serializing_if = "Definitions::is_empty", default)] - pub definitions: Definitions, + pub definitions: Definitions>, } #[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)] @@ -48,7 +48,7 @@ pub enum LookupResult<'a, T> { Many, } -impl Blueprint> { +impl Blueprint { pub fn new( config: &Config, modules: &CheckedModules, @@ -82,12 +82,8 @@ impl Blueprint> { } } -impl Blueprint -where - R: Clone + Default, - S: Clone + Default, -{ - pub fn lookup(&self, title: Option<&String>) -> Option>> { +impl Blueprint { + pub fn lookup(&self, title: Option<&String>) -> Option> { let mut validator = None; for v in self.validators.iter() { @@ -112,7 +108,7 @@ where action: F, ) -> Result where - F: Fn(Validator) -> Result, + F: Fn(Validator) -> Result, { match self.lookup(title) { Some(LookupResult::One(validator)) => action(validator.to_owned()), @@ -152,7 +148,7 @@ mod test { #[test] fn serialize_no_description() { - let blueprint: Blueprint> = Blueprint { + let blueprint = Blueprint { preamble: Preamble { title: "Foo".to_string(), description: None, @@ -179,7 +175,7 @@ mod test { #[test] fn serialize_with_description() { - let blueprint: Blueprint> = Blueprint { + let blueprint = Blueprint { preamble: Preamble { title: "Foo".to_string(), description: Some("Lorem ipsum".to_string()), @@ -227,7 +223,7 @@ mod test { ) .unwrap(); - let blueprint: Blueprint> = Blueprint { + let blueprint = Blueprint { preamble: Preamble { title: "Foo".to_string(), description: None, diff --git a/crates/aiken-project/src/blueprint/validator.rs b/crates/aiken-project/src/blueprint/validator.rs index b23b5c8e..d500706e 100644 --- a/crates/aiken-project/src/blueprint/validator.rs +++ b/crates/aiken-project/src/blueprint/validator.rs @@ -13,44 +13,44 @@ use serde; use uplc::ast::{DeBruijn, Program, Term}; #[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)] -pub struct Validator { +pub struct Validator { pub title: String, #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub datum: Option>, + pub datum: Option, - pub redeemer: Argument, + pub redeemer: Argument, #[serde(skip_serializing_if = "Vec::is_empty")] #[serde(default)] - pub parameters: Vec>, + pub parameters: Vec, #[serde(flatten)] pub program: Program, #[serde(skip_serializing_if = "Definitions::is_empty")] #[serde(default)] - pub definitions: Definitions, + pub definitions: Definitions>, } #[derive(Debug, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)] -pub struct Argument { +pub struct Argument { #[serde(skip_serializing_if = "Option::is_none")] pub title: Option, - pub schema: T, + pub schema: Reference, } -impl Validator> { +impl Validator { pub fn from_checked_module( modules: &CheckedModules, generator: &mut CodeGenerator, module: &CheckedModule, def: &TypedValidator, - ) -> Vec>, Error>> { + ) -> Vec> { let program = generator.generate(def).try_into().unwrap(); let is_multi_validator = def.other_fun.is_some(); @@ -85,7 +85,7 @@ impl Validator> { params: &[TypedArg], func: &TypedFunction, is_multi_validator: bool, - ) -> Result>, Error> { + ) -> Result { let mut args = func.arguments.iter().rev(); let (_, redeemer, datum) = (args.next(), args.next().unwrap(), args.next()); @@ -160,11 +160,7 @@ impl Validator> { } } -impl Validator -where - S: Clone, - R: Clone, -{ +impl Validator { pub fn apply(self, arg: &Term) -> Result { match self.parameters.split_first() { None => Err(Error::NoParametersToApply), diff --git a/crates/aiken-project/src/lib.rs b/crates/aiken-project/src/lib.rs index 914e3b7c..6c455270 100644 --- a/crates/aiken-project/src/lib.rs +++ b/crates/aiken-project/src/lib.rs @@ -12,11 +12,7 @@ pub mod pretty; pub mod script; pub mod telemetry; -use crate::blueprint::{ - definitions::Reference, - schema::{Annotated, Schema}, - Blueprint, -}; +use crate::blueprint::Blueprint; use aiken_lang::{ ast::{Definition, Function, ModuleKind, Tracing, TypedDataType, TypedFunction}, builtins, @@ -218,10 +214,7 @@ where self.compile(options) } - pub fn dump_uplc( - &self, - blueprint: &Blueprint>, - ) -> Result<(), Error> { + pub fn dump_uplc(&self, blueprint: &Blueprint) -> Result<(), Error> { let dir = self.root.join("artifacts"); self.event_listener @@ -362,8 +355,7 @@ where // Read blueprint let blueprint = File::open(self.blueprint_path()) .map_err(|_| blueprint::error::Error::InvalidOrMissingFile)?; - let blueprint: Blueprint = - serde_json::from_reader(BufReader::new(blueprint))?; + let blueprint: Blueprint = serde_json::from_reader(BufReader::new(blueprint))?; // Calculate the address let when_too_many = @@ -386,12 +378,11 @@ where &self, title: Option<&String>, param: &Term, - ) -> Result, Error> { + ) -> Result { // Read blueprint let blueprint = File::open(self.blueprint_path()) .map_err(|_| blueprint::error::Error::InvalidOrMissingFile)?; - let mut blueprint: Blueprint = - serde_json::from_reader(BufReader::new(blueprint))?; + let mut blueprint: Blueprint = serde_json::from_reader(BufReader::new(blueprint))?; // Apply parameters let when_too_many = diff --git a/crates/aiken/src/cmd/blueprint/convert.rs b/crates/aiken/src/cmd/blueprint/convert.rs index 2adeb3c5..d72a3183 100644 --- a/crates/aiken/src/cmd/blueprint/convert.rs +++ b/crates/aiken/src/cmd/blueprint/convert.rs @@ -65,7 +65,7 @@ pub fn exec( .map_err(|_| BlueprintError::InvalidOrMissingFile) .into_diagnostic()?; - let blueprint: Blueprint = + let blueprint: Blueprint = serde_json::from_reader(BufReader::new(blueprint)).into_diagnostic()?; // Perform the conversion From f311e048b71d5ea21da8a88a8439b61e18fda7a1 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Wed, 5 Apr 2023 11:55:05 +0200 Subject: [PATCH 04/13] Improve error message on 'ParameterizedValidator'. --- crates/aiken-project/src/blueprint/error.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/aiken-project/src/blueprint/error.rs b/crates/aiken-project/src/blueprint/error.rs index d440b18c..af173f26 100644 --- a/crates/aiken-project/src/blueprint/error.rs +++ b/crates/aiken-project/src/blueprint/error.rs @@ -37,8 +37,11 @@ pub enum Error { )] #[diagnostic(code("aiken::blueprint::address::parameterized"))] #[diagnostic(help( - "I can only compute addresses of validators that are fully applied. For example, a {keyword_spend} validator must have exactly 3 arguments: a datum, a redeemer and a context. If it has more, they need to be provided beforehand and applied directly in the validator. Applying parameters change the validator's compiled code, and thus the address.\n\nThis is why I need you to apply parameters first.", - keyword_spend = "spend".if_supports_color(Stdout, |s| s.purple())))] + "I can only compute addresses of validators that are fully applied. For example, a {keyword_spend} validator must have exactly {spend_arity} arguments: a datum, a redeemer and a context. If it has more, they need to be provided beforehand and applied directly to the validator.\n\nApplying parameters change the validator's compiled code, and thus the address. This is why I need you to apply parameters first using the {blueprint_apply_command} command.", + keyword_spend = "spend".if_supports_color(Stdout, |s| s.yellow()), + spend_arity = "3".if_supports_color(Stdout, |s| s.yellow()), + blueprint_apply_command = "blueprint apply".if_supports_color(Stdout, |s| s.purple()), + ))] ParameterizedValidator { n: usize }, } From 9033b44044e9fb3b4cbe1006a4f8d51996414030 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Wed, 5 Apr 2023 15:32:39 +0200 Subject: [PATCH 05/13] Implement quick builder on PlutusData. In the same spirit of the existing Term builder; I also added a `data` method to lift a `PlutusData` into a `Term` and generalized a bit the builder to only require a `Term` when necessary and remain generic otherwise. The `PlutusData` builder could potentially be upstreamed to pallas diretly. --- crates/uplc/src/ast.rs | 93 ++++++++++++++++++++++------- crates/uplc/src/builder.rs | 117 ++++++++++++++++++++----------------- 2 files changed, 135 insertions(+), 75 deletions(-) diff --git a/crates/uplc/src/ast.rs b/crates/uplc/src/ast.rs index 884527a6..61d5e52c 100644 --- a/crates/uplc/src/ast.rs +++ b/crates/uplc/src/ast.rs @@ -1,23 +1,3 @@ -use std::{ - fmt::{self, Display}, - hash::{self, Hash}, - rc::Rc, -}; - -use num_bigint::BigInt; -use serde::{ - self, - de::{self, Deserialize, Deserializer, MapAccess, Visitor}, - ser::{Serialize, SerializeStruct, Serializer}, -}; - -use pallas_addresses::{Network, ShelleyAddress, ShelleyDelegationPart, ShelleyPaymentPart}; -use pallas_primitives::{ - alonzo::PlutusData, - babbage::{self as cardano, Language}, -}; -use pallas_traverse::ComputeHash; - use crate::{ builtins::DefaultFunction, debruijn::{self, Converter}, @@ -28,6 +8,24 @@ use crate::{ Machine, }, }; +use num_bigint::BigInt; +use num_traits::ToPrimitive; +use pallas_addresses::{Network, ShelleyAddress, ShelleyDelegationPart, ShelleyPaymentPart}; +use pallas_primitives::{ + alonzo::{self as pallas, Constr, PlutusData}, + babbage::{self as cardano, Language}, +}; +use pallas_traverse::ComputeHash; +use serde::{ + self, + de::{self, Deserialize, Deserializer, MapAccess, Visitor}, + ser::{Serialize, SerializeStruct, Serializer}, +}; +use std::{ + fmt::{self, Display}, + hash::{self, Hash}, + rc::Rc, +}; /// This represents a program in Untyped Plutus Core. /// A program contains a version tuple and a term. @@ -239,6 +237,61 @@ pub enum Constant { Data(PlutusData), } +pub struct Data {} + +// TODO: See about moving these builders upstream to Pallas? +impl Data { + pub fn integer(i: BigInt) -> PlutusData { + match i.to_i64() { + Some(i) => PlutusData::BigInt(pallas::BigInt::Int(i.into())), + None => { + let (sign, bytes) = i.to_bytes_be(); + match sign { + num_bigint::Sign::Minus => { + PlutusData::BigInt(pallas::BigInt::BigNInt(bytes.into())) + } + _ => PlutusData::BigInt(pallas::BigInt::BigUInt(bytes.into())), + } + } + } + } + + pub fn bytestring(bytes: Vec) -> PlutusData { + PlutusData::BoundedBytes(bytes.into()) + } + + pub fn map(kvs: Vec<(PlutusData, PlutusData)>) -> PlutusData { + PlutusData::Map(kvs.into()) + } + + pub fn list(xs: Vec) -> PlutusData { + PlutusData::Array(xs) + } + + pub fn constr(ix: u64, fields: Vec) -> PlutusData { + // NOTE: see https://github.com/input-output-hk/plutus/blob/9538fc9829426b2ecb0628d352e2d7af96ec8204/plutus-core/plutus-core/src/PlutusCore/Data.hs#L139-L155 + if ix < 7 { + PlutusData::Constr(Constr { + tag: 121 + ix, + any_constructor: None, + fields, + }) + } else if ix < 128 { + PlutusData::Constr(Constr { + tag: 1280 + ix - 7, + any_constructor: None, + fields, + }) + } else { + PlutusData::Constr(Constr { + tag: 102, + any_constructor: Some(ix), + fields, + }) + } + } +} + #[derive(Debug, Clone, PartialEq)] pub enum Type { Bool, diff --git a/crates/uplc/src/builder.rs b/crates/uplc/src/builder.rs index d30d8c4c..91d2a763 100644 --- a/crates/uplc/src/builder.rs +++ b/crates/uplc/src/builder.rs @@ -2,13 +2,14 @@ use crate::{ ast::{Constant, Name, Term, Type}, builtins::DefaultFunction, }; +use pallas_primitives::alonzo::PlutusData; pub const CONSTR_FIELDS_EXPOSER: &str = "__constr_fields_exposer"; pub const CONSTR_INDEX_EXPOSER: &str = "__constr_index_exposer"; pub const CONSTR_GET_FIELD: &str = "__constr_get_field"; pub const EXPECT_ON_LIST: &str = "__expect_on_list"; -impl Term { +impl Term { pub fn apply(self, arg: Self) -> Self { Term::Apply { function: self.into(), @@ -16,13 +17,6 @@ impl Term { } } - pub fn lambda(self, parameter_name: impl ToString) -> Self { - Term::Lambda { - parameter_name: Name::text(parameter_name).into(), - body: self.into(), - } - } - pub fn force(self) -> Self { Term::Force(self.into()) } @@ -31,10 +25,6 @@ impl Term { Term::Delay(self.into()) } - pub fn var(name: impl ToString) -> Self { - Term::Var(Name::text(name).into()) - } - pub fn integer(i: num_bigint::BigInt) -> Self { Term::Constant(Constant::Integer(i).into()) } @@ -55,6 +45,10 @@ impl Term { Term::Constant(Constant::Unit.into()) } + pub fn data(d: PlutusData) -> Self { + Term::Constant(Constant::Data(d).into()) + } + pub fn empty_list() -> Self { Term::Constant(Constant::ProtoList(Type::Data, vec![]).into()) } @@ -204,7 +198,7 @@ impl Term { .force() } - pub fn trace(self, msg_term: Term) -> Self { + pub fn trace(self, msg_term: Self) -> Self { Term::Builtin(DefaultFunction::Trace) .force() .apply(msg_term) @@ -212,41 +206,34 @@ impl Term { .force() } - pub fn assert_on_list(self) -> Term { - self.lambda(EXPECT_ON_LIST.to_string()) - .apply( - Term::var(EXPECT_ON_LIST.to_string()).apply(Term::var(EXPECT_ON_LIST.to_string())), - ) - .lambda(EXPECT_ON_LIST.to_string()) - .apply( - Term::var("__list_to_check".to_string()) - .delayed_choose_list( - Term::unit(), - Term::var("__check_with".to_string()) - .apply( - Term::head_list().apply(Term::var("__list_to_check".to_string())), - ) - .choose_unit( - Term::var(EXPECT_ON_LIST.to_string()) - .apply(Term::var(EXPECT_ON_LIST.to_string())) - .apply( - Term::tail_list() - .apply(Term::var("__list_to_check".to_string())), - ) - .apply(Term::var("__check_with".to_string())), - ), - ) - .lambda("__check_with".to_string()) - .lambda("__list_to_check".to_string()) - .lambda(EXPECT_ON_LIST), - ) - } - - pub fn final_wrapper(self: Term) -> Term { + pub fn final_wrapper(self) -> Self { self.delayed_if_else(Term::unit(), Term::Error) } - pub fn constr_fields_exposer(self: Term) -> Term { + pub fn repeat_tail_list(self, repeat: usize) -> Self { + let mut term = self; + + for _ in 0..repeat { + term = Term::tail_list().apply(term); + } + + term + } +} + +impl Term { + pub fn lambda(self, parameter_name: impl ToString) -> Self { + Term::Lambda { + parameter_name: Name::text(parameter_name).into(), + body: self.into(), + } + } + + pub fn var(name: impl ToString) -> Self { + Term::Var(Name::text(name).into()) + } + + pub fn constr_fields_exposer(self) -> Self { self.lambda(CONSTR_FIELDS_EXPOSER.to_string()).apply( Term::snd_pair() .apply(Term::unconstr_data().apply(Term::var("__constr_var".to_string()))) @@ -254,7 +241,7 @@ impl Term { ) } - pub fn constr_index_exposer(self: Term) -> Term { + pub fn constr_index_exposer(self) -> Self { self.lambda(CONSTR_INDEX_EXPOSER.to_string()).apply( Term::fst_pair() .apply(Term::unconstr_data().apply(Term::var("__constr_var".to_string()))) @@ -262,7 +249,7 @@ impl Term { ) } - pub fn constr_get_field(self: Term) -> Term { + pub fn constr_get_field(self) -> Self { self.lambda(CONSTR_GET_FIELD.to_string()) .apply( Term::var(CONSTR_GET_FIELD.to_string()) @@ -298,13 +285,33 @@ impl Term { ) } - pub fn repeat_tail_list(self: Term, repeat: usize) -> Term { - let mut term = self; - - for _ in 0..repeat { - term = Term::tail_list().apply(term); - } - - term + pub fn assert_on_list(self) -> Self { + self.lambda(EXPECT_ON_LIST.to_string()) + .apply( + Term::var(EXPECT_ON_LIST.to_string()).apply(Term::var(EXPECT_ON_LIST.to_string())), + ) + .lambda(EXPECT_ON_LIST.to_string()) + .apply( + Term::var("__list_to_check".to_string()) + .delayed_choose_list( + Term::unit(), + Term::var("__check_with".to_string()) + .apply( + Term::head_list().apply(Term::var("__list_to_check".to_string())), + ) + .choose_unit( + Term::var(EXPECT_ON_LIST.to_string()) + .apply(Term::var(EXPECT_ON_LIST.to_string())) + .apply( + Term::tail_list() + .apply(Term::var("__list_to_check".to_string())), + ) + .apply(Term::var("__check_with".to_string())), + ), + ) + .lambda("__check_with".to_string()) + .lambda("__list_to_check".to_string()) + .lambda(EXPECT_ON_LIST), + ) } } From d620f6367c96c90bccf0ef7029d9b2bd79b90dc9 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Thu, 6 Apr 2023 11:57:23 +0200 Subject: [PATCH 06/13] Bootstrap schema validation for simple constants. --- crates/aiken-lang/src/tipo.rs | 9 +- .../src/blueprint/definitions.rs | 4 +- crates/aiken-project/src/blueprint/error.rs | 29 ++++- crates/aiken-project/src/blueprint/schema.rs | 45 ++++++++ .../aiken-project/src/blueprint/validator.rs | 101 +++++++++++++++++- 5 files changed, 175 insertions(+), 13 deletions(-) diff --git a/crates/aiken-lang/src/tipo.rs b/crates/aiken-lang/src/tipo.rs index fabb31c5..a3b494ce 100644 --- a/crates/aiken-lang/src/tipo.rs +++ b/crates/aiken-lang/src/tipo.rs @@ -1,13 +1,10 @@ -use std::{cell::RefCell, collections::HashMap, ops::Deref, sync::Arc}; - -use uplc::{ast::Type as UplcType, builtins::DefaultFunction}; - +use self::{environment::Environment, pretty::Printer}; use crate::{ ast::{Constant, DefinitionLocation, ModuleKind, Span}, tipo::fields::FieldMap, }; - -use self::{environment::Environment, pretty::Printer}; +use std::{cell::RefCell, collections::HashMap, ops::Deref, sync::Arc}; +use uplc::{ast::Type as UplcType, builtins::DefaultFunction}; mod environment; pub mod error; diff --git a/crates/aiken-project/src/blueprint/definitions.rs b/crates/aiken-project/src/blueprint/definitions.rs index 20b042c2..047e3f68 100644 --- a/crates/aiken-project/src/blueprint/definitions.rs +++ b/crates/aiken-project/src/blueprint/definitions.rs @@ -95,14 +95,14 @@ impl Reference { } /// Turn a reference into a key suitable for lookup. - fn as_key(&self) -> &str { + pub(crate) fn as_key(&self) -> &str { self.inner.as_str() } /// Turn a reference into a valid JSON pointer. Note that the JSON pointer specification /// indicates that '/' must be escaped as '~1' in pointer addresses (as they are otherwise /// treated as path delimiter in pointers paths). - fn as_json_pointer(&self) -> String { + pub(crate) fn as_json_pointer(&self) -> String { format!("#/definitions/{}", self.as_key().replace('/', "~1")) } } diff --git a/crates/aiken-project/src/blueprint/error.rs b/crates/aiken-project/src/blueprint/error.rs index af173f26..25e4f331 100644 --- a/crates/aiken-project/src/blueprint/error.rs +++ b/crates/aiken-project/src/blueprint/error.rs @@ -1,4 +1,7 @@ -use super::schema; +use super::{ + definitions::Reference, + schema::{self, Schema}, +}; use aiken_lang::ast::Span; use miette::{Diagnostic, NamedSource}; use owo_colors::{OwoColorize, Stream::Stdout}; @@ -43,6 +46,30 @@ pub enum Error { blueprint_apply_command = "blueprint apply".if_supports_color(Stdout, |s| s.purple()), ))] ParameterizedValidator { n: usize }, + + #[error("I failed to infer what should be the schema of a given parameter to apply.")] + #[diagnostic(code("aiken:blueprint::apply::malformed::argument"))] + #[diagnostic(help( + "I couldn't figure out the schema corresponding to a term you've given. Here's a possible hint about why I failed: {hint}" + ))] + UnableToInferArgumentSchema { hint: String }, + + #[error("I couldn't find a definition corresponding to a reference.")] + #[diagnostic(code("aiken::blueprint::apply::unknown::reference"))] + #[diagnostic(help( + "While resolving a schema definition, I stumble upon an unknown reference:\n\n {reference}\n\nThis is unfortunate, but signals that either the reference is invalid or that the correspond schema definition is missing.", + reference = reference.as_json_pointer() + ))] + UnresolvedSchemaReference { reference: Reference }, + + #[error("I caught a parameter application that seems off.")] + #[diagnostic(code("aiken::blueprint::apply::mismatch"))] + #[diagnostic(help( + "When applying parameters to a validator, I control that the shape of the parameter you give me matches what is specified in the blueprint. Unfortunately, schemas didn't match in this case.\n\nI am expecting the following:\n\n{}But I've inferred the following schema from your input:\n\n{}", + serde_json::to_string_pretty(&expected).unwrap().if_supports_color(Stdout, |s| s.green()), + serde_json::to_string_pretty(&inferred).unwrap().if_supports_color(Stdout, |s| s.red()), + ))] + SchemaMismatch { expected: Schema, inferred: Schema }, } unsafe impl Send for Error {} diff --git a/crates/aiken-project/src/blueprint/schema.rs b/crates/aiken-project/src/blueprint/schema.rs index 2f0b7958..66c84e5c 100644 --- a/crates/aiken-project/src/blueprint/schema.rs +++ b/crates/aiken-project/src/blueprint/schema.rs @@ -12,6 +12,7 @@ use serde::{ ser::{Serialize, SerializeStruct, Serializer}, }; use std::{collections::HashMap, fmt, ops::Deref, sync::Arc}; +use uplc::ast::Term; // NOTE: Can be anything BUT 0 pub const REDEEMER_DISCRIMINANT: usize = 1; @@ -80,6 +81,50 @@ impl From for Annotated { } } +impl<'a, T> TryFrom<&'a Term> for Schema { + type Error = &'a str; + + fn try_from(term: &'a Term) -> Result { + use uplc::{ast::Constant, Constr, PlutusData}; + + match term { + Term::Constant(constant) => match constant.deref() { + Constant::Integer(..) => Ok(Schema::Integer), + Constant::Bool(..) => Ok(Schema::Boolean), + Constant::ByteString(..) => Ok(Schema::Bytes), + Constant::String(..) => Ok(Schema::String), + Constant::Unit => Ok(Schema::Unit), + Constant::ProtoList{..} => todo!("can't convert from ProtoList to Schema; note that you probably want to use a Data's list instead anyway."), + Constant::ProtoPair{..} => todo!("can't convert from ProtoPair to Schema; note that you probably want to use a Data's list instead anyway."), + Constant::Data(data) => Ok(Schema::Data(match data { + PlutusData::BigInt(..) => { + Data::Integer + } + PlutusData::BoundedBytes(..) => { + Data::Bytes + } + PlutusData::Map(keyValuePair) => { + todo!() + } + PlutusData::Array(elems) => { + todo!() + } + PlutusData::Constr(Constr{ tag, fields, any_constructor }) => { + todo!() + } + })) + }, + Term::Delay(..) + | Term::Lambda { .. } + | Term::Var(..) + | Term::Apply { .. } + | Term::Force(..) + | Term::Error + | Term::Builtin(..) => Err("not a UPLC constant"), + } + } +} + impl Annotated { pub fn as_wrapped_redeemer( definitions: &mut Definitions>, diff --git a/crates/aiken-project/src/blueprint/validator.rs b/crates/aiken-project/src/blueprint/validator.rs index d500706e..9f241930 100644 --- a/crates/aiken-project/src/blueprint/validator.rs +++ b/crates/aiken-project/src/blueprint/validator.rs @@ -6,11 +6,13 @@ use super::{ use crate::module::{CheckedModule, CheckedModules}; use aiken_lang::{ ast::{TypedArg, TypedFunction, TypedValidator}, + builtins, gen_uplc::CodeGenerator, }; use miette::NamedSource; use serde; -use uplc::ast::{DeBruijn, Program, Term}; +use std::{borrow::Borrow, collections::HashMap}; +use uplc::ast::{Constant, DeBruijn, Program, Term}; #[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)] pub struct Validator { @@ -44,6 +46,48 @@ pub struct Argument { pub schema: Reference, } +impl From for Argument { + fn from(schema: Reference) -> Argument { + Argument { + title: None, + schema, + } + } +} + +impl Argument { + pub fn validate( + &self, + definitions: &Definitions>, + term: &Term, + ) -> Result<(), Error> { + let expected_schema = &definitions + .lookup(&self.schema) + .map(Ok) + .unwrap_or_else(|| { + Err(Error::UnresolvedSchemaReference { + reference: self.schema.clone(), + }) + })? + .annotated; + + let inferred_schema: Schema = + term.try_into() + .map_err(|hint: &str| Error::UnableToInferArgumentSchema { + hint: hint.to_owned(), + })?; + + if expected_schema != &inferred_schema { + Err(Error::SchemaMismatch { + expected: expected_schema.to_owned(), + inferred: inferred_schema, + }) + } else { + Ok(()) + } + } +} + impl Validator { pub fn from_checked_module( modules: &CheckedModules, @@ -164,8 +208,8 @@ impl Validator { pub fn apply(self, arg: &Term) -> Result { match self.parameters.split_first() { None => Err(Error::NoParametersToApply), - Some((_, tail)) => { - // TODO: Ideally, we should control that the applied term matches its schema. + Some((head, tail)) => { + head.validate(&self.definitions, arg)?; Ok(Self { program: self.program.apply_term(arg), parameters: tail.to_vec(), @@ -178,7 +222,14 @@ impl Validator { #[cfg(test)] mod test { - use super::*; + use super::{ + super::{ + definitions::Definitions, + error::Error, + schema::{Annotated, Data, Schema}, + }, + *, + }; use crate::{module::ParsedModule, PackageName}; use aiken_lang::{ self, @@ -193,6 +244,7 @@ mod test { use indexmap::IndexMap; use serde_json::{self, json}; use std::{collections::HashMap, path::PathBuf}; + use uplc::ast as uplc; // TODO: Possible refactor this out of the module and have it used by `Project`. The idea would // be to make this struct below the actual project, and wrap it in another metadata struct @@ -314,6 +366,24 @@ mod test { assert_json_eq!(serde_json::to_value(validator).unwrap(), expected); } + fn fixture_definitions() -> Definitions> { + let mut definitions = Definitions::new(); + + definitions + .register::<_, Error>(&builtins::int(), &HashMap::new(), |_| { + Ok(Schema::Data(Data::Integer).into()) + }) + .unwrap(); + + definitions + .register::<_, Error>(&builtins::byte_array(), &HashMap::new(), |_| { + Ok(Schema::Data(Data::Bytes).into()) + }) + .unwrap(); + + definitions + } + #[test] fn mint_basic() { assert_validator( @@ -1092,4 +1162,27 @@ mod test { }), ) } + + #[test] + fn validate_arguments_integer() { + let term = Term::data(uplc::Data::integer(42.into())); + let definitions = fixture_definitions(); + let arg = Argument { + title: None, + schema: Reference::new("Int"), + }; + + assert!(matches!(arg.validate(&definitions, &term), Ok { .. })) + } + #[test] + fn validate_arguments_bytestring() { + let term = Term::data(uplc::Data::bytestring(vec![102, 111, 111])); + let definitions = fixture_definitions(); + let arg = Argument { + title: None, + schema: Reference::new("ByteArray"), + }; + + assert!(matches!(arg.validate(&definitions, &term), Ok { .. })) + } } From ee220881b67fba33b725901e7334220a20c61859 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Thu, 6 Apr 2023 17:55:11 +0200 Subject: [PATCH 07/13] Generalize schema definition to work from inline schema or reference. --- crates/aiken-project/src/blueprint/mod.rs | 7 +- crates/aiken-project/src/blueprint/schema.rs | 193 ++++++++++-------- .../aiken-project/src/blueprint/validator.rs | 2 +- 3 files changed, 119 insertions(+), 83 deletions(-) diff --git a/crates/aiken-project/src/blueprint/mod.rs b/crates/aiken-project/src/blueprint/mod.rs index f9792d4b..11ee91a1 100644 --- a/crates/aiken-project/src/blueprint/mod.rs +++ b/crates/aiken-project/src/blueprint/mod.rs @@ -142,7 +142,7 @@ impl From<&Config> for Preamble { mod test { use super::*; use aiken_lang::builtins; - use schema::{Data, Items, Schema}; + use schema::{Data, Declaration, Items, Schema}; use serde_json::{self, json}; use std::collections::HashMap; @@ -218,7 +218,10 @@ mod test { &HashMap::new(), |_| Ok(Schema::Data(Data::Bytes).into()), )?; - Ok(Schema::Data(Data::List(Items::One(Box::new(ref_bytes)))).into()) + Ok( + Schema::Data(Data::List(Items::One(Declaration::Referenced(ref_bytes)))) + .into(), + ) }, ) .unwrap(); diff --git a/crates/aiken-project/src/blueprint/schema.rs b/crates/aiken-project/src/blueprint/schema.rs index 66c84e5c..6ba0700b 100644 --- a/crates/aiken-project/src/blueprint/schema.rs +++ b/crates/aiken-project/src/blueprint/schema.rs @@ -11,6 +11,7 @@ use serde::{ de::{self, Deserialize, Deserializer, MapAccess, Visitor}, ser::{Serialize, SerializeStruct, Serializer}, }; +use serde_json as json; use std::{collections::HashMap, fmt, ops::Deref, sync::Arc}; use uplc::ast::Term; @@ -27,6 +28,13 @@ pub struct Annotated { pub annotated: T, } +#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)] +#[serde(untagged)] +pub enum Declaration { + Referenced(Reference), + Inline(Box), +} + /// A schema for low-level UPLC primitives. #[derive(Debug, PartialEq, Eq, Clone)] pub enum Schema { @@ -35,10 +43,8 @@ pub enum Schema { Integer, Bytes, String, - // TODO: Generalize to work with either Reference or Data - Pair(Reference, Reference), - // TODO: Generalize to work with either Reference or Data - List(Items), + Pair(Declaration, Declaration), + List(Items), Data(Data), } @@ -47,10 +53,8 @@ pub enum Schema { pub enum Data { Integer, Bytes, - // TODO: Generalize to work with either Reference or Data - List(Items), - // TODO: Generalize to work with either Reference or Data - Map(Box, Box), + List(Items), + Map(Declaration, Declaration), AnyOf(Vec>), Opaque, } @@ -59,8 +63,8 @@ pub enum Data { #[derive(Debug, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)] #[serde(untagged)] pub enum Items { - One(Box), - Many(Vec), + One(Declaration), + Many(Vec>), } /// Captures a single UPLC constructor with its @@ -68,7 +72,7 @@ pub enum Items { pub struct Constructor { pub index: usize, // TODO: Generalize to work with either Reference or Data - pub fields: Vec>, + pub fields: Vec>>, } impl From for Annotated { @@ -103,10 +107,10 @@ impl<'a, T> TryFrom<&'a Term> for Schema { PlutusData::BoundedBytes(..) => { Data::Bytes } - PlutusData::Map(keyValuePair) => { + PlutusData::Array(elems) => { todo!() } - PlutusData::Array(elems) => { + PlutusData::Map(keyValuePair) => { todo!() } PlutusData::Constr(Constr{ tag, fields, any_constructor }) => { @@ -141,7 +145,7 @@ impl Annotated { description: Some("A redeemer wrapped in an extra constructor to make multi-validator detection possible on-chain.".to_string()), annotated: Schema::Data(Data::AnyOf(vec![Constructor { index: REDEEMER_DISCRIMINANT, - fields: vec![schema.into()], + fields: vec![Declaration::Referenced(schema).into()], } .into()])), }) @@ -270,7 +274,7 @@ impl Annotated { description: Some("An optional value.".to_string()), annotated: Constructor { index: 0, - fields: vec![generic.into()], + fields: vec![Declaration::Referenced(generic).into()], }, }, Annotated { @@ -311,22 +315,15 @@ impl Annotated { } if xs.len() == 2 => { definitions.remove(&generic); Data::Map( - Box::new( - xs.first() - .expect("length (== 2) checked in pattern clause") - .to_owned(), - ), - Box::new( - xs.last() - .expect("length (== 2) checked in pattern clause") - .to_owned(), - ), + xs.first() + .expect("length (== 2) checked in pattern clause") + .to_owned(), + xs.last() + .expect("length (== 2) checked in pattern clause") + .to_owned(), ) } - _ => { - // let inner = schema.clone().into_data(type_info)?.annotated; - Data::List(Items::One(Box::new(generic))) - } + _ => Data::List(Items::One(Declaration::Referenced(generic))), }; Ok(Schema::Data(data).into()) @@ -371,6 +368,7 @@ impl Annotated { .iter() .map(|elem| { Annotated::do_from_type(elem, modules, type_parameters, definitions) + .map(Declaration::Referenced) }) .collect::, _>>() .map_err(|e| e.backtrack(type_info))?; @@ -449,7 +447,7 @@ impl Data { fields.push(Annotated { title: field.label.clone(), description: field.doc.clone().map(|s| s.trim().to_string()), - annotated: reference, + annotated: Declaration::Referenced(reference), }); } @@ -579,7 +577,7 @@ where } let mut data_type: Option = None; - let mut items: Option> = None; + let mut items: Option = None; // defer items deserialization to later let mut keys = None; let mut left = None; let mut right = None; @@ -639,6 +637,18 @@ where } } + let expect_data_items = || match &items { + Some(items) => serde_json::from_value::>(items.clone()) + .map_err(|e| de::Error::custom(e.to_string())), + None => Err(de::Error::missing_field("items")), + }; + + let expect_schema_items = || match &items { + Some(items) => serde_json::from_value::>(items.clone()) + .map_err(|e| de::Error::custom(e.to_string())), + None => Err(de::Error::missing_field("items")), + }; + let expect_no_items = || { if items.is_some() { return Err(de::Error::custom( @@ -695,26 +705,28 @@ where Some(constructors) => Ok(Schema::Data(Data::AnyOf(constructors))), } } - Some(data_type) if data_type == "list" || data_type == "#list" => { + Some(data_type) if data_type == "list" => { expect_no_keys()?; expect_no_values()?; expect_no_any_of()?; expect_no_left_or_right()?; - match items { - Some(items) if data_type == "list" => Ok(Schema::Data(Data::List(items))), - Some(items) if data_type == "#list" => Ok(Schema::List(items)), - Some(_) => unreachable!("condition checked in pattern guard"), - None => Err(de::Error::missing_field("items")), - } + let items = expect_data_items()?; + Ok(Schema::Data(Data::List(items))) + } + Some(data_type) if data_type == "#list" => { + expect_no_keys()?; + expect_no_values()?; + expect_no_any_of()?; + expect_no_left_or_right()?; + let items = expect_schema_items()?; + Ok(Schema::List(items)) } Some(data_type) if data_type == "map" => { expect_no_items()?; expect_no_any_of()?; expect_no_left_or_right()?; match (keys, values) { - (Some(keys), Some(values)) => { - Ok(Schema::Data(Data::Map(Box::new(keys), Box::new(values)))) - } + (Some(keys), Some(values)) => Ok(Schema::Data(Data::Map(keys, values))), (None, _) => Err(de::Error::missing_field("keys")), (Some(..), None) => Err(de::Error::missing_field("values")), } @@ -1069,7 +1081,7 @@ pub mod test { #[test] fn serialize_data_list_1() { let ref_integer = Reference::new("Int"); - let schema = Schema::Data(Data::List(Items::One(Box::new(ref_integer)))); + let schema = Schema::Data(Data::List(Items::One(Declaration::Referenced(ref_integer)))); assert_json( &schema, json!({ @@ -1084,7 +1096,9 @@ pub mod test { #[test] fn serialize_data_list_2() { let ref_list_integer = Reference::new("List$Int"); - let schema = Schema::Data(Data::List(Items::One(Box::new(ref_list_integer)))); + let schema = Schema::Data(Data::List(Items::One(Declaration::Referenced( + ref_list_integer, + )))); assert_json( &schema, json!({ @@ -1098,9 +1112,9 @@ pub mod test { #[test] fn serialize_data_map_1() { - let ref_integer = Reference::new("Int"); - let ref_bytes = Reference::new("ByteArray"); - let schema = Schema::Data(Data::Map(Box::new(ref_integer), Box::new(ref_bytes))); + let ref_integer = Declaration::Referenced(Reference::new("Int")); + let ref_bytes = Declaration::Referenced(Reference::new("ByteArray")); + let schema = Schema::Data(Data::Map(ref_integer, ref_bytes)); assert_json( &schema, json!({ @@ -1139,12 +1153,12 @@ pub mod test { let schema = Schema::Data(Data::AnyOf(vec![ Constructor { index: 0, - fields: vec![Reference::new("Int").into()], + fields: vec![Declaration::Referenced(Reference::new("Int")).into()], } .into(), Constructor { index: 1, - fields: vec![Reference::new("Bytes").into()], + fields: vec![Declaration::Referenced(Reference::new("Bytes")).into()], } .into(), ])); @@ -1236,7 +1250,7 @@ pub mod test { #[test] fn deserialize_data_list_one() { assert_eq!( - Data::List(Items::One(Box::new(Reference::new("foo")))), + Data::List(Items::One(Declaration::Referenced(Reference::new("foo")))), serde_json::from_value(json!({ "dataType": "list", "items": { "$ref": "foo" } @@ -1249,8 +1263,8 @@ pub mod test { fn deserialize_data_list_many() { assert_eq!( Data::List(Items::Many(vec![ - Reference::new("foo"), - Reference::new("bar") + Declaration::Referenced(Reference::new("foo")), + Declaration::Referenced(Reference::new("bar")) ])), serde_json::from_value(json!({ "dataType": "list", @@ -1267,8 +1281,8 @@ pub mod test { fn deserialize_data_map() { assert_eq!( Data::Map( - Box::new(Reference::new("foo")), - Box::new(Reference::new("bar")) + Declaration::Referenced(Reference::new("foo")), + Declaration::Referenced(Reference::new("bar")) ), serde_json::from_value(json!({ "dataType": "map", @@ -1284,7 +1298,10 @@ pub mod test { assert_eq!( Data::AnyOf(vec![Constructor { index: 0, - fields: vec![Reference::new("foo").into(), Reference::new("bar").into()], + fields: vec![ + Declaration::Referenced(Reference::new("foo")).into(), + Declaration::Referenced(Reference::new("bar")).into() + ], } .into()]), serde_json::from_value(json!({ @@ -1309,7 +1326,10 @@ pub mod test { assert_eq!( Data::AnyOf(vec![Constructor { index: 0, - fields: vec![Reference::new("foo").into(), Reference::new("bar").into()], + fields: vec![ + Declaration::Referenced(Reference::new("foo")).into(), + Declaration::Referenced(Reference::new("bar")).into() + ], } .into()]), serde_json::from_value(json!({ @@ -1330,45 +1350,58 @@ pub mod test { } fn arbitrary_data() -> impl Strategy { - let r = prop_oneof![".*".prop_map(|s| Reference::new(&s))]; - let constructor = - (0..3usize, prop::collection::vec(r.clone(), 0..3)).prop_map(|(index, fields)| { - Constructor { - index, - fields: fields.into_iter().map(|f| f.into()).collect(), - } - .into() - }); - prop_oneof![ - Just(Data::Opaque), - Just(Data::Bytes), - Just(Data::Integer), - (r.clone(), r.clone()).prop_map(|(k, v)| Data::Map(Box::new(k), Box::new(v))), - r.clone().prop_map(|x| Data::List(Items::One(Box::new(x)))), - prop::collection::vec(r, 1..3).prop_map(|xs| Data::List(Items::Many(xs))), - prop::collection::vec(constructor, 1..3).prop_map(Data::AnyOf) - ] + let leaf = prop_oneof![Just(Data::Opaque), Just(Data::Bytes), Just(Data::Integer)]; + + leaf.prop_recursive(3, 8, 3, |inner| { + let r = prop_oneof![ + ".*".prop_map(|s| Declaration::Referenced(Reference::new(&s))), + inner.prop_map(|s| Declaration::Inline(Box::new(s))) + ]; + let constructor = + (0..3usize, prop::collection::vec(r.clone(), 0..3)).prop_map(|(index, fields)| { + Constructor { + index, + fields: fields.into_iter().map(|f| f.into()).collect(), + } + .into() + }); + + prop_oneof![ + (r.clone(), r.clone()).prop_map(|(k, v)| Data::Map(k, v)), + r.clone().prop_map(|x| Data::List(Items::One(x))), + prop::collection::vec(r, 1..3).prop_map(|xs| Data::List(Items::Many(xs))), + prop::collection::vec(constructor, 1..3).prop_map(Data::AnyOf) + ] + }) } fn arbitrary_schema() -> impl Strategy { - let r = prop_oneof![".*".prop_map(|s| Reference::new(&s))]; prop_compose! { fn data_strategy()(data in arbitrary_data()) -> Schema { Schema::Data(data) } } - prop_oneof![ + + let leaf = prop_oneof![ Just(Schema::Unit), Just(Schema::Boolean), Just(Schema::Bytes), Just(Schema::Integer), Just(Schema::String), - (r.clone(), r.clone()).prop_map(|(l, r)| Schema::Pair(l, r)), - r.clone() - .prop_map(|x| Schema::List(Items::One(Box::new(x)))), - prop::collection::vec(r, 1..3).prop_map(|xs| Schema::List(Items::Many(xs))), data_strategy(), - ] + ]; + + leaf.prop_recursive(3, 8, 3, |inner| { + let r = prop_oneof![ + ".*".prop_map(|s| Declaration::Referenced(Reference::new(&s))), + inner.prop_map(|s| Declaration::Inline(Box::new(s))) + ]; + prop_oneof![ + (r.clone(), r.clone()).prop_map(|(l, r)| Schema::Pair(l, r)), + r.clone().prop_map(|x| Schema::List(Items::One(x))), + prop::collection::vec(r, 1..3).prop_map(|xs| Schema::List(Items::Many(xs))), + ] + }) } proptest! { diff --git a/crates/aiken-project/src/blueprint/validator.rs b/crates/aiken-project/src/blueprint/validator.rs index 9f241930..89310cac 100644 --- a/crates/aiken-project/src/blueprint/validator.rs +++ b/crates/aiken-project/src/blueprint/validator.rs @@ -226,7 +226,7 @@ mod test { super::{ definitions::Definitions, error::Error, - schema::{Annotated, Data, Schema}, + schema::{Annotated, Data, Declaration, Items, Schema}, }, *, }; From d58ef1a0791681ac9625d311ab226b364788d9ae Mon Sep 17 00:00:00 2001 From: KtorZ Date: Fri, 7 Apr 2023 11:53:19 +0200 Subject: [PATCH 08/13] Implement blueprint schema & data validations. --- .../src/blueprint/definitions.rs | 6 + crates/aiken-project/src/blueprint/error.rs | 23 +- crates/aiken-project/src/blueprint/mod.rs | 1 + .../aiken-project/src/blueprint/parameter.rs | 478 ++++++++++++++++++ crates/aiken-project/src/blueprint/schema.rs | 83 ++- .../aiken-project/src/blueprint/validator.rs | 369 +++++++++++--- 6 files changed, 842 insertions(+), 118 deletions(-) create mode 100644 crates/aiken-project/src/blueprint/parameter.rs diff --git a/crates/aiken-project/src/blueprint/definitions.rs b/crates/aiken-project/src/blueprint/definitions.rs index 047e3f68..b72b3a3c 100644 --- a/crates/aiken-project/src/blueprint/definitions.rs +++ b/crates/aiken-project/src/blueprint/definitions.rs @@ -53,6 +53,12 @@ impl Definitions { self.inner.remove(reference.as_key()); } + /// Insert a new definition + pub fn insert(&mut self, reference: &Reference, schema: T) { + self.inner + .insert(reference.as_key().to_string(), Some(schema)); + } + /// Register a new definition only if it doesn't exist. This uses a strategy of /// mark-and-insert such that recursive definitions are only built once. pub fn register( diff --git a/crates/aiken-project/src/blueprint/error.rs b/crates/aiken-project/src/blueprint/error.rs index 25e4f331..27c7d5f4 100644 --- a/crates/aiken-project/src/blueprint/error.rs +++ b/crates/aiken-project/src/blueprint/error.rs @@ -6,6 +6,7 @@ use aiken_lang::ast::Span; use miette::{Diagnostic, NamedSource}; use owo_colors::{OwoColorize, Stream::Stdout}; use std::fmt::Debug; +use uplc::ast::{DeBruijn, Term}; #[derive(Debug, thiserror::Error, Diagnostic)] pub enum Error { @@ -65,11 +66,25 @@ pub enum Error { #[error("I caught a parameter application that seems off.")] #[diagnostic(code("aiken::blueprint::apply::mismatch"))] #[diagnostic(help( - "When applying parameters to a validator, I control that the shape of the parameter you give me matches what is specified in the blueprint. Unfortunately, schemas didn't match in this case.\n\nI am expecting the following:\n\n{}But I've inferred the following schema from your input:\n\n{}", - serde_json::to_string_pretty(&expected).unwrap().if_supports_color(Stdout, |s| s.green()), - serde_json::to_string_pretty(&inferred).unwrap().if_supports_color(Stdout, |s| s.red()), + "When applying parameters to a validator, I control that the shape of the parameter you give me matches what is specified in the blueprint. Unfortunately, schemas didn't match in this case.\n\nI am expecting something of the shape:\n\n{}Which I couldn't match against the following term:\n\n{}\n\nNote that this may only represent part of a bigger whole.", + serde_json::to_string_pretty(&schema).unwrap().if_supports_color(Stdout, |s| s.green()), + term.to_pretty().if_supports_color(Stdout, |s| s.red()), ))] - SchemaMismatch { expected: Schema, inferred: Schema }, + SchemaMismatch { + schema: Schema, + term: Term, + }, + + #[error( + "I discovered a discrepancy of elements between a given tuple and its declared schema." + )] + #[diagnostic(code("aiken::blueprint::apply::tuple::mismatch"))] + #[diagnostic(help( + "When validating a list-like schema with multiple 'items' schemas, I try to match each element of the instance with each item schema (by their position). Hence, I expect to be as many items in the declared schema ({expected}) than there are items in the instance ({found}).", + expected = expected.if_supports_color(Stdout, |s| s.green()), + found = found.if_supports_color(Stdout, |s| s.red()), + ))] + TupleItemsMismatch { expected: usize, found: usize }, } unsafe impl Send for Error {} diff --git a/crates/aiken-project/src/blueprint/mod.rs b/crates/aiken-project/src/blueprint/mod.rs index 11ee91a1..168953c8 100644 --- a/crates/aiken-project/src/blueprint/mod.rs +++ b/crates/aiken-project/src/blueprint/mod.rs @@ -1,5 +1,6 @@ pub mod definitions; pub mod error; +pub mod parameter; pub mod schema; pub mod validator; diff --git a/crates/aiken-project/src/blueprint/parameter.rs b/crates/aiken-project/src/blueprint/parameter.rs new file mode 100644 index 00000000..fe682077 --- /dev/null +++ b/crates/aiken-project/src/blueprint/parameter.rs @@ -0,0 +1,478 @@ +use super::{ + definitions::{Definitions, Reference}, + error::Error, + schema::{Annotated, Constructor, Data, Declaration, Items, Schema}, +}; +use std::{iter, ops::Deref, rc::Rc}; +use uplc::{ + ast::{Constant, Data as UplcData, DeBruijn, Term}, + PlutusData, +}; + +#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)] +pub struct Parameter { + #[serde(skip_serializing_if = "Option::is_none")] + pub title: Option, + + pub schema: Reference, +} + +type Instance = Term; + +impl From for Parameter { + fn from(schema: Reference) -> Parameter { + Parameter { + title: None, + schema, + } + } +} + +impl Parameter { + pub fn validate( + &self, + definitions: &Definitions>, + term: &Instance, + ) -> Result<(), Error> { + let schema = &definitions + .lookup(&self.schema) + .map(Ok) + .unwrap_or_else(|| { + Err(Error::UnresolvedSchemaReference { + reference: self.schema.clone(), + }) + })? + .annotated; + + validate_schema(schema, definitions, term) + } +} + +fn validate_schema( + schema: &Schema, + definitions: &Definitions>, + term: &Instance, +) -> Result<(), Error> { + match schema { + Schema::Data(data) => validate_data(data, definitions, term), + + Schema::Unit => expect_unit(term), + + Schema::Integer => expect_integer(term), + + Schema::Bytes => expect_bytes(term), + + Schema::String => expect_string(term), + + Schema::Boolean => expect_boolean(term), + + Schema::Pair(left, right) => { + let (term_left, term_right) = expect_pair(term)?; + + let left = + left.schema(definitions) + .ok_or_else(|| Error::UnresolvedSchemaReference { + reference: left.reference().unwrap().clone(), + })?; + validate_schema(left, definitions, &term_left)?; + + let right = + right + .schema(definitions) + .ok_or_else(|| Error::UnresolvedSchemaReference { + reference: right.reference().unwrap().clone(), + })?; + validate_schema(right, definitions, &term_right)?; + + Ok(()) + } + + Schema::List(Items::One(item)) => { + let terms = expect_list(term)?; + + let item = + item.schema(definitions) + .ok_or_else(|| Error::UnresolvedSchemaReference { + reference: item.reference().unwrap().clone(), + })?; + + for ref term in terms { + validate_schema(item, definitions, term)?; + } + + Ok(()) + } + + Schema::List(Items::Many(items)) => { + let terms = expect_list(term)?; + + let items = items + .iter() + .map(|item| { + item.schema(definitions) + .ok_or_else(|| Error::UnresolvedSchemaReference { + reference: item.reference().unwrap().clone(), + }) + }) + .collect::, _>>()?; + + if terms.len() != items.len() { + return Err(Error::TupleItemsMismatch { + expected: items.len(), + found: terms.len(), + }); + } + + for (item, ref term) in iter::zip(items, terms) { + validate_schema(item, definitions, term)?; + } + + Ok(()) + } + } +} + +fn validate_data( + data: &Data, + definitions: &Definitions>, + term: &Instance, +) -> Result<(), Error> { + match data { + Data::Opaque => expect_data(term), + + Data::Integer => expect_data_integer(term), + + Data::Bytes => expect_data_bytes(term), + + Data::List(Items::One(item)) => { + let terms = expect_data_list(term)?; + + let item = + item.schema(definitions) + .ok_or_else(|| Error::UnresolvedSchemaReference { + reference: item.reference().unwrap().clone(), + })?; + + for ref term in terms { + validate_data(item, definitions, term)?; + } + + Ok(()) + } + + Data::List(Items::Many(items)) => { + let terms = expect_data_list(term)?; + + let items = items + .iter() + .map(|item| { + item.schema(definitions) + .ok_or_else(|| Error::UnresolvedSchemaReference { + reference: item.reference().unwrap().clone(), + }) + }) + .collect::, _>>()?; + + if terms.len() != items.len() { + return Err(Error::TupleItemsMismatch { + expected: items.len(), + found: terms.len(), + }); + } + + for (item, ref term) in iter::zip(items, terms) { + validate_data(item, definitions, term)?; + } + + Ok(()) + } + + Data::Map(keys, values) => { + let terms = expect_data_map(term)?; + + let keys = + keys.schema(definitions) + .ok_or_else(|| Error::UnresolvedSchemaReference { + reference: keys.reference().unwrap().clone(), + })?; + + let values = + values + .schema(definitions) + .ok_or_else(|| Error::UnresolvedSchemaReference { + reference: values.reference().unwrap().clone(), + })?; + + for (ref k, ref v) in terms { + validate_data(keys, definitions, k)?; + validate_data(values, definitions, v)?; + } + + Ok(()) + } + + Data::AnyOf(constructors) => { + let constructors: Vec<(usize, Vec<&Data>)> = constructors + .iter() + .map(|constructor| { + constructor + .annotated + .fields + .iter() + .map(|field| { + field.annotated.schema(definitions).ok_or_else(|| { + Error::UnresolvedSchemaReference { + reference: field.annotated.reference().unwrap().clone(), + } + }) + }) + .collect::>() + .map(|fields| (constructor.annotated.index, fields)) + }) + .collect::>()?; + + for (index, fields_schema) in constructors.iter() { + if let Ok(fields) = expect_data_constr(term, *index) { + if fields_schema.len() != fields.len() { + panic!("fields length different"); + } + + for (instance, schema) in iter::zip(fields, fields_schema) { + validate_data(schema, definitions, &instance)?; + } + + return Ok(()); + } + } + + Err(Error::SchemaMismatch { + schema: Schema::Data(Data::AnyOf( + constructors + .iter() + .map(|(index, fields)| { + Constructor { + index: *index, + fields: fields + .iter() + .map(|_| Declaration::Inline(Box::new(Data::Opaque)).into()) + .collect(), + } + .into() + }) + .collect(), + )), + term: term.clone(), + }) + } + } +} + +fn expect_data(term: &Instance) -> Result<(), Error> { + if let Term::Constant(constant) = term { + if matches!(constant.deref(), Constant::Data(..)) { + return Ok(()); + } + } + + Err(Error::SchemaMismatch { + schema: Schema::Data(Data::Opaque), + term: term.clone(), + }) +} + +fn expect_data_integer(term: &Instance) -> Result<(), Error> { + if let Term::Constant(constant) = term { + if let Constant::Data(data) = constant.deref() { + if matches!(data, PlutusData::BigInt(..)) { + return Ok(()); + } + } + } + + Err(Error::SchemaMismatch { + schema: Schema::Data(Data::Integer), + term: term.clone(), + }) +} + +fn expect_data_bytes(term: &Instance) -> Result<(), Error> { + if let Term::Constant(constant) = term { + if let Constant::Data(data) = constant.deref() { + if matches!(data, PlutusData::BoundedBytes(..)) { + return Ok(()); + } + } + } + + Err(Error::SchemaMismatch { + schema: Schema::Data(Data::Bytes), + term: term.clone(), + }) +} + +fn expect_data_list(term: &Instance) -> Result, Error> { + if let Term::Constant(constant) = term { + if let Constant::Data(PlutusData::Array(elems)) = constant.deref() { + return Ok(elems + .iter() + .map(|elem| Term::Constant(Rc::new(Constant::Data(elem.to_owned())))) + .collect()); + } + } + + let inner_schema = Items::One(Declaration::Inline(Box::new(Data::Opaque))); + + Err(Error::SchemaMismatch { + schema: Schema::Data(Data::List(inner_schema)), + term: term.clone(), + }) +} + +fn expect_data_map(term: &Instance) -> Result, Error> { + if let Term::Constant(constant) = term { + if let Constant::Data(PlutusData::Map(pairs)) = constant.deref() { + return Ok(pairs + .iter() + .map(|(k, v)| { + ( + Term::Constant(Rc::new(Constant::Data(k.to_owned()))), + Term::Constant(Rc::new(Constant::Data(v.to_owned()))), + ) + }) + .collect()); + } + } + + let key_schema = Declaration::Inline(Box::new(Data::Opaque)); + let value_schema = Declaration::Inline(Box::new(Data::Opaque)); + + Err(Error::SchemaMismatch { + schema: Schema::Data(Data::Map(key_schema, value_schema)), + term: term.clone(), + }) +} + +fn expect_data_constr(term: &Instance, index: usize) -> Result, Error> { + if let Term::Constant(constant) = term { + if let Constant::Data(PlutusData::Constr(constr)) = constant.deref() { + if let PlutusData::Constr(expected) = UplcData::constr(index as u64, vec![]) { + if expected.tag == constr.tag && expected.any_constructor == constr.any_constructor + { + return Ok(constr + .fields + .iter() + .map(|field| Term::Constant(Rc::new(Constant::Data(field.to_owned())))) + .collect()); + } + } + } + } + + Err(Error::SchemaMismatch { + schema: Schema::Data(Data::AnyOf(vec![Constructor { + index, + fields: vec![], + } + .into()])), + term: term.clone(), + }) +} + +fn expect_unit(term: &Instance) -> Result<(), Error> { + if let Term::Constant(constant) = term { + if matches!(constant.deref(), Constant::Unit) { + return Ok(()); + } + } + + Err(Error::SchemaMismatch { + schema: Schema::Unit, + term: term.clone(), + }) +} + +fn expect_integer(term: &Instance) -> Result<(), Error> { + if let Term::Constant(constant) = term { + if matches!(constant.deref(), Constant::Integer(..)) { + return Ok(()); + } + } + + Err(Error::SchemaMismatch { + schema: Schema::Integer, + term: term.clone(), + }) +} + +fn expect_bytes(term: &Instance) -> Result<(), Error> { + if let Term::Constant(constant) = term { + if matches!(constant.deref(), Constant::ByteString(..)) { + return Ok(()); + } + } + + Err(Error::SchemaMismatch { + schema: Schema::Bytes, + term: term.clone(), + }) +} + +fn expect_string(term: &Instance) -> Result<(), Error> { + if let Term::Constant(constant) = term { + if matches!(constant.deref(), Constant::String(..)) { + return Ok(()); + } + } + + Err(Error::SchemaMismatch { + schema: Schema::String, + term: term.clone(), + }) +} + +fn expect_boolean(term: &Instance) -> Result<(), Error> { + if let Term::Constant(constant) = term { + if matches!(constant.deref(), Constant::Bool(..)) { + return Ok(()); + } + } + + Err(Error::SchemaMismatch { + schema: Schema::Boolean, + term: term.clone(), + }) +} + +fn expect_pair(term: &Instance) -> Result<(Instance, Instance), Error> { + if let Term::Constant(constant) = term { + if let Constant::ProtoPair(_, _, left, right) = constant.deref() { + return Ok((Term::Constant(left.clone()), Term::Constant(right.clone()))); + } + } + + let left_schema = Declaration::Inline(Box::new(Schema::Data(Data::Opaque))); + let right_schema = Declaration::Inline(Box::new(Schema::Data(Data::Opaque))); + + Err(Error::SchemaMismatch { + schema: Schema::Pair(left_schema, right_schema), + term: term.clone(), + }) +} + +fn expect_list(term: &Instance) -> Result, Error> { + if let Term::Constant(constant) = term { + if let Constant::ProtoList(_, elems) = constant.deref() { + return Ok(elems + .iter() + .map(|elem| Term::Constant(Rc::new(elem.to_owned()))) + .collect()); + } + } + + let inner_schema = Items::One(Declaration::Inline(Box::new(Schema::Data(Data::Opaque)))); + + Err(Error::SchemaMismatch { + schema: Schema::List(inner_schema), + term: term.clone(), + }) +} diff --git a/crates/aiken-project/src/blueprint/schema.rs b/crates/aiken-project/src/blueprint/schema.rs index 6ba0700b..c35202a3 100644 --- a/crates/aiken-project/src/blueprint/schema.rs +++ b/crates/aiken-project/src/blueprint/schema.rs @@ -13,7 +13,6 @@ use serde::{ }; use serde_json as json; use std::{collections::HashMap, fmt, ops::Deref, sync::Arc}; -use uplc::ast::Term; // NOTE: Can be anything BUT 0 pub const REDEEMER_DISCRIMINANT: usize = 1; @@ -35,6 +34,43 @@ pub enum Declaration { Inline(Box), } +impl<'a, T> Declaration { + pub fn reference(&'a self) -> Option<&'a Reference> { + match self { + Declaration::Referenced(reference) => Some(reference), + Declaration::Inline(..) => None, + } + } + + fn try_schema( + &'a self, + definitions: &'a Definitions>, + cast: fn(&'a Schema) -> Option<&'a T>, + ) -> Option<&'a T> { + match self { + Declaration::Inline(inner) => Some(inner.deref()), + Declaration::Referenced(reference) => definitions + .lookup(reference) + .and_then(|s| cast(&s.annotated)), + } + } +} + +impl<'a> Declaration { + pub fn schema(&'a self, definitions: &'a Definitions>) -> Option<&'a Data> { + self.try_schema(definitions, |s| match s { + Schema::Data(data) => Some(data), + _ => None, + }) + } +} + +impl<'a> Declaration { + pub fn schema(&'a self, definitions: &'a Definitions>) -> Option<&'a Schema> { + self.try_schema(definitions, Some) + } +} + /// A schema for low-level UPLC primitives. #[derive(Debug, PartialEq, Eq, Clone)] pub enum Schema { @@ -71,7 +107,6 @@ pub enum Items { #[derive(Debug, PartialEq, Eq, Clone)] pub struct Constructor { pub index: usize, - // TODO: Generalize to work with either Reference or Data pub fields: Vec>>, } @@ -85,50 +120,6 @@ impl From for Annotated { } } -impl<'a, T> TryFrom<&'a Term> for Schema { - type Error = &'a str; - - fn try_from(term: &'a Term) -> Result { - use uplc::{ast::Constant, Constr, PlutusData}; - - match term { - Term::Constant(constant) => match constant.deref() { - Constant::Integer(..) => Ok(Schema::Integer), - Constant::Bool(..) => Ok(Schema::Boolean), - Constant::ByteString(..) => Ok(Schema::Bytes), - Constant::String(..) => Ok(Schema::String), - Constant::Unit => Ok(Schema::Unit), - Constant::ProtoList{..} => todo!("can't convert from ProtoList to Schema; note that you probably want to use a Data's list instead anyway."), - Constant::ProtoPair{..} => todo!("can't convert from ProtoPair to Schema; note that you probably want to use a Data's list instead anyway."), - Constant::Data(data) => Ok(Schema::Data(match data { - PlutusData::BigInt(..) => { - Data::Integer - } - PlutusData::BoundedBytes(..) => { - Data::Bytes - } - PlutusData::Array(elems) => { - todo!() - } - PlutusData::Map(keyValuePair) => { - todo!() - } - PlutusData::Constr(Constr{ tag, fields, any_constructor }) => { - todo!() - } - })) - }, - Term::Delay(..) - | Term::Lambda { .. } - | Term::Var(..) - | Term::Apply { .. } - | Term::Force(..) - | Term::Error - | Term::Builtin(..) => Err("not a UPLC constant"), - } - } -} - impl Annotated { pub fn as_wrapped_redeemer( definitions: &mut Definitions>, diff --git a/crates/aiken-project/src/blueprint/validator.rs b/crates/aiken-project/src/blueprint/validator.rs index 89310cac..7d8d87d5 100644 --- a/crates/aiken-project/src/blueprint/validator.rs +++ b/crates/aiken-project/src/blueprint/validator.rs @@ -1,18 +1,17 @@ use super::{ - definitions::{Definitions, Reference}, + definitions::Definitions, error::Error, + parameter::Parameter, schema::{Annotated, Schema}, }; use crate::module::{CheckedModule, CheckedModules}; use aiken_lang::{ ast::{TypedArg, TypedFunction, TypedValidator}, - builtins, gen_uplc::CodeGenerator, }; use miette::NamedSource; use serde; -use std::{borrow::Borrow, collections::HashMap}; -use uplc::ast::{Constant, DeBruijn, Program, Term}; +use uplc::ast::{DeBruijn, Program, Term}; #[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)] pub struct Validator { @@ -22,13 +21,13 @@ pub struct Validator { pub description: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub datum: Option, + pub datum: Option, - pub redeemer: Argument, + pub redeemer: Parameter, #[serde(skip_serializing_if = "Vec::is_empty")] #[serde(default)] - pub parameters: Vec, + pub parameters: Vec, #[serde(flatten)] pub program: Program, @@ -38,56 +37,6 @@ pub struct Validator { pub definitions: Definitions>, } -#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)] -pub struct Argument { - #[serde(skip_serializing_if = "Option::is_none")] - pub title: Option, - - pub schema: Reference, -} - -impl From for Argument { - fn from(schema: Reference) -> Argument { - Argument { - title: None, - schema, - } - } -} - -impl Argument { - pub fn validate( - &self, - definitions: &Definitions>, - term: &Term, - ) -> Result<(), Error> { - let expected_schema = &definitions - .lookup(&self.schema) - .map(Ok) - .unwrap_or_else(|| { - Err(Error::UnresolvedSchemaReference { - reference: self.schema.clone(), - }) - })? - .annotated; - - let inferred_schema: Schema = - term.try_into() - .map_err(|hint: &str| Error::UnableToInferArgumentSchema { - hint: hint.to_owned(), - })?; - - if expected_schema != &inferred_schema { - Err(Error::SchemaMismatch { - expected: expected_schema.to_owned(), - inferred: inferred_schema, - }) - } else { - Ok(()) - } - } -} - impl Validator { pub fn from_checked_module( modules: &CheckedModules, @@ -146,7 +95,7 @@ impl Validator { .iter() .map(|param| { Annotated::from_type(modules.into(), ¶m.tipo, &mut definitions) - .map(|schema| Argument { + .map(|schema| Parameter { title: Some(param.arg_name.get_label()), schema, }) @@ -174,7 +123,7 @@ impl Validator { ) }) .transpose()? - .map(|schema| Argument { + .map(|schema| Parameter { title: datum.map(|datum| datum.arg_name.get_label()), schema, }), @@ -187,7 +136,7 @@ impl Validator { module.code.clone(), ), }) - .map(|schema| Argument { + .map(|schema| Parameter { title: Some(redeemer.arg_name.get_label()), schema: match datum { Some(..) if is_multi_validator => Annotated::as_wrapped_redeemer( @@ -224,9 +173,9 @@ impl Validator { mod test { use super::{ super::{ - definitions::Definitions, + definitions::{Definitions, Reference}, error::Error, - schema::{Annotated, Data, Declaration, Items, Schema}, + schema::{Annotated, Constructor, Data, Declaration, Items, Schema}, }, *, }; @@ -369,18 +318,63 @@ mod test { fn fixture_definitions() -> Definitions> { let mut definitions = Definitions::new(); + // #/definitions/Int + // + // { + // "dataType": "integer" + // } definitions .register::<_, Error>(&builtins::int(), &HashMap::new(), |_| { Ok(Schema::Data(Data::Integer).into()) }) .unwrap(); + // #/definitions/ByteArray + // + // { + // "dataType": "bytes" + // } definitions .register::<_, Error>(&builtins::byte_array(), &HashMap::new(), |_| { Ok(Schema::Data(Data::Bytes).into()) }) .unwrap(); + // #/definitions/Bool + // + // { + // "anyOf": [ + // { + // "dataType": "constructor", + // "index": 0, + // "fields": [] + // }, + // { + // "dataType": "constructor", + // "index": 1, + // "fields": [] + // }, + // ] + // } + definitions.insert( + &Reference::new("Bool"), + Schema::Data(Data::AnyOf(vec![ + // False + Constructor { + index: 0, + fields: vec![], + } + .into(), + // True + Constructor { + index: 1, + fields: vec![], + } + .into(), + ])) + .into(), + ); + definitions } @@ -1165,24 +1159,263 @@ mod test { #[test] fn validate_arguments_integer() { - let term = Term::data(uplc::Data::integer(42.into())); let definitions = fixture_definitions(); - let arg = Argument { + + let term = Term::data(uplc::Data::integer(42.into())); + + let param = Parameter { title: None, schema: Reference::new("Int"), }; - assert!(matches!(arg.validate(&definitions, &term), Ok { .. })) + assert!(matches!(param.validate(&definitions, &term), Ok { .. })) } + #[test] fn validate_arguments_bytestring() { - let term = Term::data(uplc::Data::bytestring(vec![102, 111, 111])); let definitions = fixture_definitions(); - let arg = Argument { + + let term = Term::data(uplc::Data::bytestring(vec![102, 111, 111])); + + let param = Parameter { title: None, schema: Reference::new("ByteArray"), }; - assert!(matches!(arg.validate(&definitions, &term), Ok { .. })) + assert!(matches!(param.validate(&definitions, &term), Ok { .. })) + } + + #[test] + fn validate_arguments_list_inline() { + let schema = Reference::new("List$Int"); + + // #/definitions/List$Int + // + // { + // "dataType": "list", + // "items": { "dataType": "integer" } + // } + let mut definitions = fixture_definitions(); + definitions.insert( + &schema, + Schema::Data(Data::List(Items::One(Declaration::Inline(Box::new( + Data::Integer, + ))))) + .into(), + ); + + let term = Term::data(uplc::Data::list(vec![ + uplc::Data::integer(42.into()), + uplc::Data::integer(14.into()), + ])); + + let param: Parameter = schema.into(); + + assert!(matches!(param.validate(&definitions, &term), Ok { .. })) + } + + #[test] + fn validate_arguments_list_ref() { + let schema = Reference::new("List$ByteArray"); + + // #/definitions/List$ByteArray + // + // { + // "dataType": "list", + // "items": { "$ref": "#/definitions/ByteArray" } + // } + let mut definitions = fixture_definitions(); + definitions.insert( + &schema, + Schema::Data(Data::List(Items::One(Declaration::Referenced( + Reference::new("ByteArray"), + )))) + .into(), + ); + + let term = Term::data(uplc::Data::list(vec![uplc::Data::bytestring(vec![ + 102, 111, 111, + ])])); + + let param: Parameter = schema.into(); + + assert!(matches!(param.validate(&definitions, &term), Ok { .. })) + } + + #[test] + fn validate_arguments_tuple() { + let schema = Reference::new("Tuple$Int_ByteArray"); + + // #/definitions/Tuple$Int_ByteArray + // + // { + // "dataType": "list", + // "items": [ + // { "$ref": "#/definitions/Int" } + // { "$ref": "#/definitions/ByteArray" } + // ] + // } + let mut definitions = fixture_definitions(); + definitions.insert( + &schema, + Schema::Data(Data::List(Items::Many(vec![ + Declaration::Referenced(Reference::new("Int")), + Declaration::Referenced(Reference::new("ByteArray")), + ]))) + .into(), + ); + + let term = Term::data(uplc::Data::list(vec![ + uplc::Data::integer(42.into()), + uplc::Data::bytestring(vec![102, 111, 111]), + ])); + + let param: Parameter = schema.into(); + + assert!(matches!(param.validate(&definitions, &term), Ok { .. })) + } + + #[test] + fn validate_arguments_dict() { + let schema = Reference::new("Dict$ByteArray_Int"); + + // #/definitions/Dict$Int_ByteArray + // + // { + // "dataType": "map", + // "keys": { "dataType": "bytes" }, + // "values": { "dataType": "integer" } + // } + let mut definitions = fixture_definitions(); + definitions.insert( + &Reference::new("Dict$ByteArray_Int"), + Schema::Data(Data::Map( + Declaration::Inline(Box::new(Data::Bytes)), + Declaration::Inline(Box::new(Data::Integer)), + )) + .into(), + ); + + let term = Term::data(uplc::Data::map(vec![( + uplc::Data::bytestring(vec![102, 111, 111]), + uplc::Data::integer(42.into()), + )])); + + let param: Parameter = schema.into(); + + assert!(matches!(param.validate(&definitions, &term), Ok { .. })) + } + + #[test] + fn validate_arguments_constr_nullary() { + let schema = Reference::new("Bool"); + + let definitions = fixture_definitions(); + + let term = Term::data(uplc::Data::constr(1, vec![])); + + let param: Parameter = schema.into(); + + assert!(matches!(param.validate(&definitions, &term), Ok { .. })) + } + + #[test] + fn validate_arguments_constr_n_ary() { + let schema = Reference::new("Foo"); + + // #/definitions/Foo + // + // { + // "anyOf": [ + // { + // "dataType": "constructor", + // "index": 0, + // "fields": [{ + // "$ref": "#/definitions/Bool + // }] + // }, + // ] + // } + let mut definitions = fixture_definitions(); + definitions.insert( + &schema, + Schema::Data(Data::AnyOf(vec![Constructor { + index: 0, + fields: vec![Declaration::Referenced(Reference::new("Bool")).into()], + } + .into()])) + .into(), + ); + + let term = Term::data(uplc::Data::constr(0, vec![uplc::Data::constr(0, vec![])])); + + let param: Parameter = schema.into(); + + assert!(matches!(param.validate(&definitions, &term), Ok { .. })) + } + + #[test] + fn validate_arguments_constr_recursive() { + let schema = Reference::new("LinkedList$Int"); + + // #/definitions/LinkedList$Int + // + // { + // "anyOf": [ + // { + // "dataType": "constructor", + // "index": 0, + // "fields": [] + // }, + // { + // "dataType": "constructor", + // "index": 1, + // "fields": [{ + // "$ref": "#/definitions/Int + // "$ref": "#/definitions/LinkedList$Int + // }] + // }, + // ] + // } + let mut definitions = fixture_definitions(); + definitions.insert( + &schema, + Schema::Data(Data::AnyOf(vec![ + // Empty + Constructor { + index: 0, + fields: vec![], + } + .into(), + // Node + Constructor { + index: 1, + fields: vec![ + Declaration::Referenced(Reference::new("Int")).into(), + Declaration::Referenced(Reference::new("LinkedList$Int")).into(), + ], + } + .into(), + ])) + .into(), + ); + + let term = Term::data(uplc::Data::constr( + 1, + vec![ + uplc::Data::integer(14.into()), + uplc::Data::constr( + 1, + vec![ + uplc::Data::integer(42.into()), + uplc::Data::constr(0, vec![]), + ], + ), + ], + )); + + let param: Parameter = schema.into(); + + assert!(matches!(param.validate(&definitions, &term), Ok { .. })) } } From 7d6612b108434b7c56bbd3aa7ee146504f65daf3 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Fri, 7 Apr 2023 12:03:09 +0200 Subject: [PATCH 09/13] Rework parameter validation to work from 'Constant' instead of 'Term' This simplifies the code and makes it more efficient as we no longer need to wrap and unwrap constant terms constantly. --- crates/aiken-project/src/blueprint/error.rs | 13 +- .../aiken-project/src/blueprint/parameter.rs | 157 +++++++----------- 2 files changed, 67 insertions(+), 103 deletions(-) diff --git a/crates/aiken-project/src/blueprint/error.rs b/crates/aiken-project/src/blueprint/error.rs index 27c7d5f4..cb53916b 100644 --- a/crates/aiken-project/src/blueprint/error.rs +++ b/crates/aiken-project/src/blueprint/error.rs @@ -6,7 +6,7 @@ use aiken_lang::ast::Span; use miette::{Diagnostic, NamedSource}; use owo_colors::{OwoColorize, Stream::Stdout}; use std::fmt::Debug; -use uplc::ast::{DeBruijn, Term}; +use uplc::ast::Constant; #[derive(Debug, thiserror::Error, Diagnostic)] pub enum Error { @@ -48,12 +48,12 @@ pub enum Error { ))] ParameterizedValidator { n: usize }, - #[error("I failed to infer what should be the schema of a given parameter to apply.")] + #[error("I stumble upon something else than a constant when I expected one.")] #[diagnostic(code("aiken:blueprint::apply::malformed::argument"))] #[diagnostic(help( - "I couldn't figure out the schema corresponding to a term you've given. Here's a possible hint about why I failed: {hint}" + "Parameters applied to blueprints must be constant; they cannot be lambdas or delayed terms." ))] - UnableToInferArgumentSchema { hint: String }, + NonConstantParameter, #[error("I couldn't find a definition corresponding to a reference.")] #[diagnostic(code("aiken::blueprint::apply::unknown::reference"))] @@ -70,10 +70,7 @@ pub enum Error { serde_json::to_string_pretty(&schema).unwrap().if_supports_color(Stdout, |s| s.green()), term.to_pretty().if_supports_color(Stdout, |s| s.red()), ))] - SchemaMismatch { - schema: Schema, - term: Term, - }, + SchemaMismatch { schema: Schema, term: Constant }, #[error( "I discovered a discrepancy of elements between a given tuple and its declared schema." diff --git a/crates/aiken-project/src/blueprint/parameter.rs b/crates/aiken-project/src/blueprint/parameter.rs index fe682077..4e127490 100644 --- a/crates/aiken-project/src/blueprint/parameter.rs +++ b/crates/aiken-project/src/blueprint/parameter.rs @@ -3,7 +3,7 @@ use super::{ error::Error, schema::{Annotated, Constructor, Data, Declaration, Items, Schema}, }; -use std::{iter, ops::Deref, rc::Rc}; +use std::{iter, ops::Deref}; use uplc::{ ast::{Constant, Data as UplcData, DeBruijn, Term}, PlutusData, @@ -17,8 +17,6 @@ pub struct Parameter { pub schema: Reference, } -type Instance = Term; - impl From for Parameter { fn from(schema: Reference) -> Parameter { Parameter { @@ -32,7 +30,7 @@ impl Parameter { pub fn validate( &self, definitions: &Definitions>, - term: &Instance, + term: &Term, ) -> Result<(), Error> { let schema = &definitions .lookup(&self.schema) @@ -44,14 +42,18 @@ impl Parameter { })? .annotated; - validate_schema(schema, definitions, term) + if let Term::Constant(constant) = term { + validate_schema(schema, definitions, constant) + } else { + Err(Error::NonConstantParameter) + } } } fn validate_schema( schema: &Schema, definitions: &Definitions>, - term: &Instance, + term: &Constant, ) -> Result<(), Error> { match schema { Schema::Data(data) => validate_data(data, definitions, term), @@ -135,7 +137,7 @@ fn validate_schema( fn validate_data( data: &Data, definitions: &Definitions>, - term: &Instance, + term: &Constant, ) -> Result<(), Error> { match data { Data::Opaque => expect_data(term), @@ -267,11 +269,9 @@ fn validate_data( } } -fn expect_data(term: &Instance) -> Result<(), Error> { - if let Term::Constant(constant) = term { - if matches!(constant.deref(), Constant::Data(..)) { - return Ok(()); - } +fn expect_data(term: &Constant) -> Result<(), Error> { + if matches!(term, Constant::Data(..)) { + return Ok(()); } Err(Error::SchemaMismatch { @@ -280,12 +280,10 @@ fn expect_data(term: &Instance) -> Result<(), Error> { }) } -fn expect_data_integer(term: &Instance) -> Result<(), Error> { - if let Term::Constant(constant) = term { - if let Constant::Data(data) = constant.deref() { - if matches!(data, PlutusData::BigInt(..)) { - return Ok(()); - } +fn expect_data_integer(term: &Constant) -> Result<(), Error> { + if let Constant::Data(data) = term { + if matches!(data, PlutusData::BigInt(..)) { + return Ok(()); } } @@ -295,12 +293,10 @@ fn expect_data_integer(term: &Instance) -> Result<(), Error> { }) } -fn expect_data_bytes(term: &Instance) -> Result<(), Error> { - if let Term::Constant(constant) = term { - if let Constant::Data(data) = constant.deref() { - if matches!(data, PlutusData::BoundedBytes(..)) { - return Ok(()); - } +fn expect_data_bytes(term: &Constant) -> Result<(), Error> { + if let Constant::Data(data) = term { + if matches!(data, PlutusData::BoundedBytes(..)) { + return Ok(()); } } @@ -310,14 +306,12 @@ fn expect_data_bytes(term: &Instance) -> Result<(), Error> { }) } -fn expect_data_list(term: &Instance) -> Result, Error> { - if let Term::Constant(constant) = term { - if let Constant::Data(PlutusData::Array(elems)) = constant.deref() { - return Ok(elems - .iter() - .map(|elem| Term::Constant(Rc::new(Constant::Data(elem.to_owned())))) - .collect()); - } +fn expect_data_list(term: &Constant) -> Result, Error> { + if let Constant::Data(PlutusData::Array(elems)) = term { + return Ok(elems + .iter() + .map(|elem| Constant::Data(elem.to_owned())) + .collect()); } let inner_schema = Items::One(Declaration::Inline(Box::new(Data::Opaque))); @@ -328,19 +322,12 @@ fn expect_data_list(term: &Instance) -> Result, Error> { }) } -fn expect_data_map(term: &Instance) -> Result, Error> { - if let Term::Constant(constant) = term { - if let Constant::Data(PlutusData::Map(pairs)) = constant.deref() { - return Ok(pairs - .iter() - .map(|(k, v)| { - ( - Term::Constant(Rc::new(Constant::Data(k.to_owned()))), - Term::Constant(Rc::new(Constant::Data(v.to_owned()))), - ) - }) - .collect()); - } +fn expect_data_map(term: &Constant) -> Result, Error> { + if let Constant::Data(PlutusData::Map(pairs)) = term { + return Ok(pairs + .iter() + .map(|(k, v)| (Constant::Data(k.to_owned()), Constant::Data(v.to_owned()))) + .collect()); } let key_schema = Declaration::Inline(Box::new(Data::Opaque)); @@ -352,18 +339,15 @@ fn expect_data_map(term: &Instance) -> Result, Error> }) } -fn expect_data_constr(term: &Instance, index: usize) -> Result, Error> { - if let Term::Constant(constant) = term { - if let Constant::Data(PlutusData::Constr(constr)) = constant.deref() { - if let PlutusData::Constr(expected) = UplcData::constr(index as u64, vec![]) { - if expected.tag == constr.tag && expected.any_constructor == constr.any_constructor - { - return Ok(constr - .fields - .iter() - .map(|field| Term::Constant(Rc::new(Constant::Data(field.to_owned())))) - .collect()); - } +fn expect_data_constr(term: &Constant, index: usize) -> Result, Error> { + if let Constant::Data(PlutusData::Constr(constr)) = term { + if let PlutusData::Constr(expected) = UplcData::constr(index as u64, vec![]) { + if expected.tag == constr.tag && expected.any_constructor == constr.any_constructor { + return Ok(constr + .fields + .iter() + .map(|field| Constant::Data(field.to_owned())) + .collect()); } } } @@ -378,11 +362,9 @@ fn expect_data_constr(term: &Instance, index: usize) -> Result, Er }) } -fn expect_unit(term: &Instance) -> Result<(), Error> { - if let Term::Constant(constant) = term { - if matches!(constant.deref(), Constant::Unit) { - return Ok(()); - } +fn expect_unit(term: &Constant) -> Result<(), Error> { + if matches!(term, Constant::Unit) { + return Ok(()); } Err(Error::SchemaMismatch { @@ -391,11 +373,9 @@ fn expect_unit(term: &Instance) -> Result<(), Error> { }) } -fn expect_integer(term: &Instance) -> Result<(), Error> { - if let Term::Constant(constant) = term { - if matches!(constant.deref(), Constant::Integer(..)) { - return Ok(()); - } +fn expect_integer(term: &Constant) -> Result<(), Error> { + if matches!(term, Constant::Integer(..)) { + return Ok(()); } Err(Error::SchemaMismatch { @@ -404,11 +384,9 @@ fn expect_integer(term: &Instance) -> Result<(), Error> { }) } -fn expect_bytes(term: &Instance) -> Result<(), Error> { - if let Term::Constant(constant) = term { - if matches!(constant.deref(), Constant::ByteString(..)) { - return Ok(()); - } +fn expect_bytes(term: &Constant) -> Result<(), Error> { + if matches!(term, Constant::ByteString(..)) { + return Ok(()); } Err(Error::SchemaMismatch { @@ -417,11 +395,9 @@ fn expect_bytes(term: &Instance) -> Result<(), Error> { }) } -fn expect_string(term: &Instance) -> Result<(), Error> { - if let Term::Constant(constant) = term { - if matches!(constant.deref(), Constant::String(..)) { - return Ok(()); - } +fn expect_string(term: &Constant) -> Result<(), Error> { + if matches!(term, Constant::String(..)) { + return Ok(()); } Err(Error::SchemaMismatch { @@ -430,11 +406,9 @@ fn expect_string(term: &Instance) -> Result<(), Error> { }) } -fn expect_boolean(term: &Instance) -> Result<(), Error> { - if let Term::Constant(constant) = term { - if matches!(constant.deref(), Constant::Bool(..)) { - return Ok(()); - } +fn expect_boolean(term: &Constant) -> Result<(), Error> { + if matches!(term, Constant::Bool(..)) { + return Ok(()); } Err(Error::SchemaMismatch { @@ -443,11 +417,9 @@ fn expect_boolean(term: &Instance) -> Result<(), Error> { }) } -fn expect_pair(term: &Instance) -> Result<(Instance, Instance), Error> { - if let Term::Constant(constant) = term { - if let Constant::ProtoPair(_, _, left, right) = constant.deref() { - return Ok((Term::Constant(left.clone()), Term::Constant(right.clone()))); - } +fn expect_pair(term: &Constant) -> Result<(Constant, Constant), Error> { + if let Constant::ProtoPair(_, _, left, right) = term { + return Ok((left.deref().clone(), right.deref().clone())); } let left_schema = Declaration::Inline(Box::new(Schema::Data(Data::Opaque))); @@ -459,14 +431,9 @@ fn expect_pair(term: &Instance) -> Result<(Instance, Instance), Error> { }) } -fn expect_list(term: &Instance) -> Result, Error> { - if let Term::Constant(constant) = term { - if let Constant::ProtoList(_, elems) = constant.deref() { - return Ok(elems - .iter() - .map(|elem| Term::Constant(Rc::new(elem.to_owned()))) - .collect()); - } +fn expect_list(term: &Constant) -> Result, Error> { + if let Constant::ProtoList(_, elems) = term { + return Ok(elems.to_owned()); } let inner_schema = Items::One(Declaration::Inline(Box::new(Schema::Data(Data::Opaque)))); From 176cb45524068c2781792b3c2e708bb567c98ce9 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Fri, 7 Apr 2023 16:20:51 +0200 Subject: [PATCH 10/13] Factor out error (schema mismatch) creation during blueprint validation. --- .../aiken-project/src/blueprint/parameter.rs | 115 ++++++++---------- 1 file changed, 49 insertions(+), 66 deletions(-) diff --git a/crates/aiken-project/src/blueprint/parameter.rs b/crates/aiken-project/src/blueprint/parameter.rs index 4e127490..ef0633e6 100644 --- a/crates/aiken-project/src/blueprint/parameter.rs +++ b/crates/aiken-project/src/blueprint/parameter.rs @@ -50,6 +50,13 @@ impl Parameter { } } +fn mismatch(term: &Constant, schema: Schema) -> Error { + Error::SchemaMismatch { + schema, + term: term.clone(), + } +} + fn validate_schema( schema: &Schema, definitions: &Definitions>, @@ -247,8 +254,9 @@ fn validate_data( } } - Err(Error::SchemaMismatch { - schema: Schema::Data(Data::AnyOf( + Err(mismatch( + term, + Schema::Data(Data::AnyOf( constructors .iter() .map(|(index, fields)| { @@ -263,8 +271,7 @@ fn validate_data( }) .collect(), )), - term: term.clone(), - }) + )) } } } @@ -274,10 +281,7 @@ fn expect_data(term: &Constant) -> Result<(), Error> { return Ok(()); } - Err(Error::SchemaMismatch { - schema: Schema::Data(Data::Opaque), - term: term.clone(), - }) + Err(mismatch(term, Schema::Data(Data::Opaque))) } fn expect_data_integer(term: &Constant) -> Result<(), Error> { @@ -287,10 +291,7 @@ fn expect_data_integer(term: &Constant) -> Result<(), Error> { } } - Err(Error::SchemaMismatch { - schema: Schema::Data(Data::Integer), - term: term.clone(), - }) + Err(mismatch(term, Schema::Data(Data::Integer))) } fn expect_data_bytes(term: &Constant) -> Result<(), Error> { @@ -300,10 +301,7 @@ fn expect_data_bytes(term: &Constant) -> Result<(), Error> { } } - Err(Error::SchemaMismatch { - schema: Schema::Data(Data::Bytes), - term: term.clone(), - }) + Err(mismatch(term, Schema::Data(Data::Bytes))) } fn expect_data_list(term: &Constant) -> Result, Error> { @@ -314,12 +312,12 @@ fn expect_data_list(term: &Constant) -> Result, Error> { .collect()); } - let inner_schema = Items::One(Declaration::Inline(Box::new(Data::Opaque))); - - Err(Error::SchemaMismatch { - schema: Schema::Data(Data::List(inner_schema)), - term: term.clone(), - }) + Err(mismatch( + term, + Schema::Data(Data::List(Items::One(Declaration::Inline(Box::new( + Data::Opaque, + ))))), + )) } fn expect_data_map(term: &Constant) -> Result, Error> { @@ -330,13 +328,13 @@ fn expect_data_map(term: &Constant) -> Result, Error> .collect()); } - let key_schema = Declaration::Inline(Box::new(Data::Opaque)); - let value_schema = Declaration::Inline(Box::new(Data::Opaque)); - - Err(Error::SchemaMismatch { - schema: Schema::Data(Data::Map(key_schema, value_schema)), - term: term.clone(), - }) + Err(mismatch( + term, + Schema::Data(Data::Map( + Declaration::Inline(Box::new(Data::Opaque)), + Declaration::Inline(Box::new(Data::Opaque)), + )), + )) } fn expect_data_constr(term: &Constant, index: usize) -> Result, Error> { @@ -352,14 +350,14 @@ fn expect_data_constr(term: &Constant, index: usize) -> Result, Er } } - Err(Error::SchemaMismatch { - schema: Schema::Data(Data::AnyOf(vec![Constructor { + Err(mismatch( + term, + Schema::Data(Data::AnyOf(vec![Constructor { index, fields: vec![], } .into()])), - term: term.clone(), - }) + )) } fn expect_unit(term: &Constant) -> Result<(), Error> { @@ -367,10 +365,7 @@ fn expect_unit(term: &Constant) -> Result<(), Error> { return Ok(()); } - Err(Error::SchemaMismatch { - schema: Schema::Unit, - term: term.clone(), - }) + Err(mismatch(term, Schema::Unit)) } fn expect_integer(term: &Constant) -> Result<(), Error> { @@ -378,10 +373,7 @@ fn expect_integer(term: &Constant) -> Result<(), Error> { return Ok(()); } - Err(Error::SchemaMismatch { - schema: Schema::Integer, - term: term.clone(), - }) + Err(mismatch(term, Schema::Integer)) } fn expect_bytes(term: &Constant) -> Result<(), Error> { @@ -389,10 +381,7 @@ fn expect_bytes(term: &Constant) -> Result<(), Error> { return Ok(()); } - Err(Error::SchemaMismatch { - schema: Schema::Bytes, - term: term.clone(), - }) + Err(mismatch(term, Schema::Bytes)) } fn expect_string(term: &Constant) -> Result<(), Error> { @@ -400,10 +389,7 @@ fn expect_string(term: &Constant) -> Result<(), Error> { return Ok(()); } - Err(Error::SchemaMismatch { - schema: Schema::String, - term: term.clone(), - }) + Err(mismatch(term, Schema::String)) } fn expect_boolean(term: &Constant) -> Result<(), Error> { @@ -411,10 +397,7 @@ fn expect_boolean(term: &Constant) -> Result<(), Error> { return Ok(()); } - Err(Error::SchemaMismatch { - schema: Schema::Boolean, - term: term.clone(), - }) + Err(mismatch(term, Schema::Boolean)) } fn expect_pair(term: &Constant) -> Result<(Constant, Constant), Error> { @@ -422,13 +405,13 @@ fn expect_pair(term: &Constant) -> Result<(Constant, Constant), Error> { return Ok((left.deref().clone(), right.deref().clone())); } - let left_schema = Declaration::Inline(Box::new(Schema::Data(Data::Opaque))); - let right_schema = Declaration::Inline(Box::new(Schema::Data(Data::Opaque))); - - Err(Error::SchemaMismatch { - schema: Schema::Pair(left_schema, right_schema), - term: term.clone(), - }) + Err(mismatch( + term, + Schema::Pair( + Declaration::Inline(Box::new(Schema::Data(Data::Opaque))), + Declaration::Inline(Box::new(Schema::Data(Data::Opaque))), + ), + )) } fn expect_list(term: &Constant) -> Result, Error> { @@ -436,10 +419,10 @@ fn expect_list(term: &Constant) -> Result, Error> { return Ok(elems.to_owned()); } - let inner_schema = Items::One(Declaration::Inline(Box::new(Schema::Data(Data::Opaque)))); - - Err(Error::SchemaMismatch { - schema: Schema::List(inner_schema), - term: term.clone(), - }) + Err(mismatch( + term, + Schema::List(Items::One(Declaration::Inline(Box::new(Schema::Data( + Data::Opaque, + ))))), + )) } From c18deecdc8d077338d5f2e53a8bef8a082c1e896 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Fri, 7 Apr 2023 16:21:33 +0200 Subject: [PATCH 11/13] Show slightly better schema mismatch errors Display terms as CBOR diagnostic when they are Plutus data. --- Cargo.lock | 1 + crates/aiken-project/Cargo.toml | 1 + crates/aiken-project/src/blueprint/error.rs | 16 +++++++++++++--- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ebcce565..462a5c33 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -129,6 +129,7 @@ dependencies = [ "indexmap", "itertools", "miette", + "minicbor", "owo-colors", "pallas", "pallas-traverse", diff --git a/crates/aiken-project/Cargo.toml b/crates/aiken-project/Cargo.toml index fbc72bda..f972900a 100644 --- a/crates/aiken-project/Cargo.toml +++ b/crates/aiken-project/Cargo.toml @@ -20,6 +20,7 @@ ignore = "0.4.20" indexmap = "1.9.2" itertools = "0.10.5" miette = { version = "5.5.0", features = ["fancy"] } +minicbor = "0.19.1" owo-colors = { version = "3.5.0", features = ["supports-colors"] } pallas = "0.18.0" pallas-traverse = "0.18.0" diff --git a/crates/aiken-project/src/blueprint/error.rs b/crates/aiken-project/src/blueprint/error.rs index cb53916b..894ec7ed 100644 --- a/crates/aiken-project/src/blueprint/error.rs +++ b/crates/aiken-project/src/blueprint/error.rs @@ -4,6 +4,7 @@ use super::{ }; use aiken_lang::ast::Span; use miette::{Diagnostic, NamedSource}; +use minicbor as cbor; use owo_colors::{OwoColorize, Stream::Stdout}; use std::fmt::Debug; use uplc::ast::Constant; @@ -66,9 +67,18 @@ pub enum Error { #[error("I caught a parameter application that seems off.")] #[diagnostic(code("aiken::blueprint::apply::mismatch"))] #[diagnostic(help( - "When applying parameters to a validator, I control that the shape of the parameter you give me matches what is specified in the blueprint. Unfortunately, schemas didn't match in this case.\n\nI am expecting something of the shape:\n\n{}Which I couldn't match against the following term:\n\n{}\n\nNote that this may only represent part of a bigger whole.", - serde_json::to_string_pretty(&schema).unwrap().if_supports_color(Stdout, |s| s.green()), - term.to_pretty().if_supports_color(Stdout, |s| s.red()), + "When applying parameters to a validator, I control that the shape of the parameter you give me matches what is specified in the blueprint. Unfortunately, schemas didn't match in this case.\n\nI am expecting something of the shape:\n\n{expected}Which I couldn't match against the following term:\n\n{term}\n\nNote that this may only represent part of a bigger whole.", + expected = serde_json::to_string_pretty(&schema).unwrap().if_supports_color(Stdout, |s| s.green()), + term = { + let mut buf = vec![]; + match term { + Constant::Data(data) => { + cbor::encode(data, &mut buf).unwrap(); + cbor::display(&buf).to_string() + }, + _ => term.to_pretty() + } + }.if_supports_color(Stdout, |s| s.red()), ))] SchemaMismatch { schema: Schema, term: Constant }, From bf222a3ea221490b772efef835cc60e18639161d Mon Sep 17 00:00:00 2001 From: KtorZ Date: Fri, 7 Apr 2023 17:38:37 +0200 Subject: [PATCH 12/13] Fix reference JSON deserialization. --- .../aiken-project/src/blueprint/definitions.rs | 13 ++++++++++--- crates/aiken-project/src/blueprint/schema.rs | 18 +++++++++--------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/crates/aiken-project/src/blueprint/definitions.rs b/crates/aiken-project/src/blueprint/definitions.rs index b72b3a3c..98b7211f 100644 --- a/crates/aiken-project/src/blueprint/definitions.rs +++ b/crates/aiken-project/src/blueprint/definitions.rs @@ -224,9 +224,16 @@ impl<'a> Deserialize<'a> for Reference { } } - Ok(Reference { - inner: inner.ok_or_else(|| de::Error::missing_field(FIELDS[0]))?, - }) + let inner: String = inner.ok_or_else(|| de::Error::missing_field(FIELDS[0]))?; + + match inner.strip_prefix("#/definitions/") { + Some(suffix) => Ok(Reference { + inner: suffix.to_string(), + }), + None => Err(de::Error::custom( + "Invalid reference; only local JSON pointer to #/definitions are allowed.", + )), + } } } diff --git a/crates/aiken-project/src/blueprint/schema.rs b/crates/aiken-project/src/blueprint/schema.rs index c35202a3..41f5f2d0 100644 --- a/crates/aiken-project/src/blueprint/schema.rs +++ b/crates/aiken-project/src/blueprint/schema.rs @@ -1244,7 +1244,7 @@ pub mod test { Data::List(Items::One(Declaration::Referenced(Reference::new("foo")))), serde_json::from_value(json!({ "dataType": "list", - "items": { "$ref": "foo" } + "items": { "$ref": "#/definitions/foo" } })) .unwrap() ) @@ -1260,8 +1260,8 @@ pub mod test { serde_json::from_value(json!({ "dataType": "list", "items": [ - { "$ref": "foo" }, - { "$ref": "bar" } + { "$ref": "#/definitions/foo" }, + { "$ref": "#/definitions/bar" } ], })) .unwrap() @@ -1277,8 +1277,8 @@ pub mod test { ), serde_json::from_value(json!({ "dataType": "map", - "keys": { "$ref": "foo" }, - "values": { "$ref": "bar" } + "keys": { "$ref": "#/definitions/foo" }, + "values": { "$ref": "#/definitions/bar" } })) .unwrap() ) @@ -1300,10 +1300,10 @@ pub mod test { "index": 0, "fields": [ { - "$ref": "foo", + "$ref": "#/definitions/foo", }, { - "$ref": "bar", + "$ref": "#/definitions/bar", } ] }] @@ -1328,10 +1328,10 @@ pub mod test { "index": 0, "fields": [ { - "$ref": "foo", + "$ref": "#/definitions/foo", }, { - "$ref": "bar", + "$ref": "#/definitions/bar", } ] }] From 4799af3242038c36318af88f5f66e3dc0dd430ca Mon Sep 17 00:00:00 2001 From: KtorZ Date: Fri, 7 Apr 2023 17:40:40 +0200 Subject: [PATCH 13/13] Rework 'blueprint apply' command and wrap up wiring up validation. The apply command now works only from a serialized CBOR data (instead of a UPLC syntax). So it is no longer possible to specify arbitrary cbor terms through the CLI. I believe it to be an acceptable limitation for now; especially given that Aiken will never generate blueprints with non-data terms at the interface boundary. --- crates/aiken-project/src/blueprint/error.rs | 11 ++- .../aiken-project/src/blueprint/validator.rs | 8 +- crates/aiken-project/src/lib.rs | 4 +- crates/aiken/src/cmd/blueprint/apply.rs | 98 +++++++++++++++---- 4 files changed, 95 insertions(+), 26 deletions(-) diff --git a/crates/aiken-project/src/blueprint/error.rs b/crates/aiken-project/src/blueprint/error.rs index 894ec7ed..9aac7739 100644 --- a/crates/aiken-project/src/blueprint/error.rs +++ b/crates/aiken-project/src/blueprint/error.rs @@ -59,15 +59,15 @@ pub enum Error { #[error("I couldn't find a definition corresponding to a reference.")] #[diagnostic(code("aiken::blueprint::apply::unknown::reference"))] #[diagnostic(help( - "While resolving a schema definition, I stumble upon an unknown reference:\n\n {reference}\n\nThis is unfortunate, but signals that either the reference is invalid or that the correspond schema definition is missing.", - reference = reference.as_json_pointer() + "While resolving a schema definition, I stumbled upon an unknown reference:\n\n→ {reference}\n\nThis is unfortunate, but signals that either the reference is invalid or that the corresponding schema definition is missing. Double-check the blueprint for that reference or definition.", + reference = reference.as_json_pointer().if_supports_color(Stdout, |s| s.red()) ))] UnresolvedSchemaReference { reference: Reference }, #[error("I caught a parameter application that seems off.")] #[diagnostic(code("aiken::blueprint::apply::mismatch"))] #[diagnostic(help( - "When applying parameters to a validator, I control that the shape of the parameter you give me matches what is specified in the blueprint. Unfortunately, schemas didn't match in this case.\n\nI am expecting something of the shape:\n\n{expected}Which I couldn't match against the following term:\n\n{term}\n\nNote that this may only represent part of a bigger whole.", + "When applying parameters to a validator, I control that the shape of the parameter you give me matches what is specified in the blueprint. Unfortunately, it didn't match in this case.\n\nI am looking at the following value:\n\n{term}\n\nbut failed to match it against the specified schema:\n\n{expected}\n\n\nNOTE: this may only represent part of a bigger whole as I am validating the parameter incrementally.", expected = serde_json::to_string_pretty(&schema).unwrap().if_supports_color(Stdout, |s| s.green()), term = { let mut buf = vec![]; @@ -92,6 +92,11 @@ pub enum Error { found = found.if_supports_color(Stdout, |s| s.red()), ))] TupleItemsMismatch { expected: usize, found: usize }, + + #[error("I failed to convert some input into a valid parameter")] + #[diagnostic(code("aiken::blueprint::parse::parameter"))] + #[diagnostic(help("{hint}"))] + MalformedParameter { hint: String }, } unsafe impl Send for Error {} diff --git a/crates/aiken-project/src/blueprint/validator.rs b/crates/aiken-project/src/blueprint/validator.rs index 7d8d87d5..1b46ff91 100644 --- a/crates/aiken-project/src/blueprint/validator.rs +++ b/crates/aiken-project/src/blueprint/validator.rs @@ -154,11 +154,15 @@ impl Validator { } impl Validator { - pub fn apply(self, arg: &Term) -> Result { + pub fn apply( + self, + definitions: &Definitions>, + arg: &Term, + ) -> Result { match self.parameters.split_first() { None => Err(Error::NoParametersToApply), Some((head, tail)) => { - head.validate(&self.definitions, arg)?; + head.validate(definitions, arg)?; Ok(Self { program: self.program.apply_term(arg), parameters: tail.to_vec(), diff --git a/crates/aiken-project/src/lib.rs b/crates/aiken-project/src/lib.rs index 6c455270..28677b20 100644 --- a/crates/aiken-project/src/lib.rs +++ b/crates/aiken-project/src/lib.rs @@ -391,7 +391,9 @@ where let applied_validator = blueprint.with_validator(title, when_too_many, when_missing, |validator| { - validator.apply(param).map_err(|e| e.into()) + validator + .apply(&blueprint.definitions, param) + .map_err(|e| e.into()) })?; // Overwrite validator diff --git a/crates/aiken/src/cmd/blueprint/apply.rs b/crates/aiken/src/cmd/blueprint/apply.rs index d9d3ff2c..d98200f9 100644 --- a/crates/aiken/src/cmd/blueprint/apply.rs +++ b/crates/aiken/src/cmd/blueprint/apply.rs @@ -1,18 +1,22 @@ use crate::with_project; -use aiken_project::error::Error; -use miette::IntoDiagnostic; -use std::{fs, path::PathBuf}; -use uplc::{ - ast::{DeBruijn, Term}, - parser, -}; +use aiken_project::{blueprint, error::Error}; +use owo_colors::{OwoColorize, Stream::Stderr}; +use std::{fs, path::PathBuf, process, rc::Rc}; +use uplc::ast::{Constant, DeBruijn, Term}; /// Apply a parameter to a parameterized validator. #[derive(clap::Args)] pub struct Args { + /// The parameter, as a Plutus Data (CBOR, hex-encoded) + parameter: String, + /// Path to project directory: Option, + /// Output file. Optional, print on stdout when omitted. + #[clap(short, long)] + out: Option, + /// Name of the validator's module within the project. Optional if there's only one validator. #[clap(short, long)] module: Option, @@ -20,23 +24,58 @@ pub struct Args { /// Name of the validator within the module. Optional if there's only one validator. #[clap(short, long)] validator: Option, - - /// The parameter, using high-level UPLC-syntax - parameter: String, } pub fn exec( Args { + parameter, directory, + out, module, validator, - parameter, }: Args, ) -> miette::Result<()> { - let term: Term = parser::term(¶meter) - .into_diagnostic()? - .try_into() - .into_diagnostic()?; + eprintln!( + "{} inputs", + " Parsing" + .if_supports_color(Stderr, |s| s.purple()) + .if_supports_color(Stderr, |s| s.bold()), + ); + + let bytes = hex::decode(parameter) + .map_err::(|e| { + blueprint::error::Error::MalformedParameter { + hint: format!("Invalid hex-encoded string: {e}"), + } + .into() + }) + .unwrap_or_else(|e| { + println!(); + e.report(); + process::exit(1) + }); + + let data = uplc::plutus_data(&bytes) + .map_err::(|e| { + blueprint::error::Error::MalformedParameter { + hint: format!("Invalid Plutus data; malformed CBOR encoding: {e}"), + } + .into() + }) + .unwrap_or_else(|e| { + println!(); + e.report(); + process::exit(1) + }); + + let term: Term = Term::Constant(Rc::new(Constant::Data(data))); + + eprintln!( + "{} blueprint", + " Analyzing" + .if_supports_color(Stderr, |s| s.purple()) + .if_supports_color(Stderr, |s| s.bold()), + ); with_project(directory, |p| { let title = module.as_ref().map(|m| { @@ -51,16 +90,35 @@ pub fn exec( let title = title.as_ref().or(validator.as_ref()); + eprintln!( + "{} parameter", + " Applying" + .if_supports_color(Stderr, |s| s.purple()) + .if_supports_color(Stderr, |s| s.bold()), + ); + let blueprint = p.apply_parameter(title, &term)?; let json = serde_json::to_string_pretty(&blueprint).unwrap(); - fs::write(p.blueprint_path(), json).map_err(|error| { - Error::FileIo { + match out { + None => { + println!("\n{}\n", json); + Ok(()) + } + Some(ref path) => fs::write(path, json).map_err(|error| Error::FileIo { error, path: p.blueprint_path(), - } - .into() - }) + }), + }?; + + eprintln!( + "{}", + " Done" + .if_supports_color(Stderr, |s| s.purple()) + .if_supports_color(Stderr, |s| s.bold()), + ); + + Ok(()) }) }