diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 09c98de..3599c26 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -11,11 +11,43 @@ env: RUSTFLAGS: -D warnings jobs: - build: - name: build + snap: + name: Generate and test a Snap + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install Rust run: rustup show - - name: Build - run: cargo build + - name: Install wasm32-wasi + run: rustup target add wasm32-wasi + - name: Create the sov-runtime library + run: | + cargo new --lib sov-runtime + echo -e '[package]\nname = "sov-runtime"\nversion = "0.1.0"\nedition = "2021"\n[dependencies]\nborsh = "0.10.3"\nserde_json = "1.0"\nsov-modules-api = { git = "https://github.com/Sovereign-Labs/sovereign-sdk.git", rev = "df169be", features = ["serde"] }\ndemo-stf = { git = "https://github.com/Sovereign-Labs/sovereign-sdk.git", rev = "df169be", features = ["serde"] }\nsov-mock-da = { git = "https://github.com/Sovereign-Labs/sovereign-sdk.git", rev = "df169be" }\n' > ./sov-runtime/Cargo.toml + echo -e 'pub use sov_modules_api::default_context::ZkDefaultContext as Context;\npub use sov_mock_da::MockDaSpec as DaSpec;\npub use demo_stf::runtime::RuntimeCall;\n' > ./sov-runtime/src/lib.rs + wget -O ./sov-runtime/constants.json 'https://raw.githubusercontent.com/Sovereign-Labs/sovereign-sdk/d42e289f26b9824b5ed54dbfbda94007dee305b2/constants.json' + - name: Sanity check the sov-runtime library + run: cargo check --manifest-path ./sov-runtime/Cargo.toml + - name: Generate the Snap project + run: cargo run -- sov-snap-generator init --defaults --path ./sov-runtime --target ./sov-runtime-snap + - name: Install WASM tools + run: | + PACKAGE_URL="https://github.com/WebAssembly/binaryen/releases/download/version_116/binaryen-version_116-x86_64-linux.tar.gz" + INSTALL_PATH="/usr/local/bin" + curl -L $PACKAGE_URL | tar xz -C $INSTALL_PATH + shell: bash + - name: Install WABT + run: | + PACKAGE_URL="https://github.com/WebAssembly/wabt/releases/download/1.0.34/wabt-1.0.34-ubuntu.tar.gz" + INSTALL_PATH="/usr/local/bin" + curl -L $PACKAGE_URL | tar xz -C $INSTALL_PATH + shell: bash + - name: Build the Snap project + run: | + PATH="/usr/local/bin/binaryen-version_116/bin:/usr/local/bin/wabt-1.0.34/bin:$PATH" + cargo run -- sov-snap-generator build --defaults --target ./sov-runtime-snap + - name: Run Snap tests + run: | + cd sov-runtime-snap + yarn install-chrome + yarn test diff --git a/Cargo.lock b/Cargo.lock index 1fd2da8..bcca64f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,7 +37,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -47,7 +47,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -56,6 +56,12 @@ version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + [[package]] name = "bitflags" version = "1.3.2" @@ -76,9 +82,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.4.8" +version = "4.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64" +checksum = "41fffed7514f420abec6d183b1d3acfd9099c79c3a10a06ade4f8203f1411272" dependencies = [ "clap_builder", "clap_derive", @@ -86,9 +92,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.8" +version = "4.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc" +checksum = "63361bae7eef3771745f02d8d892bec2fee5f6e34af316ba556e7f97a7069ff1" dependencies = [ "anstream", "anstyle", @@ -105,7 +111,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.39", ] [[package]] @@ -131,6 +137,58 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "console" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.45.0", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "duct" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ae3fc31835f74c2a7ceda3aeede378b0ae2e74c8f1c36559fcc9ae2a4e7d3e" +dependencies = [ + "libc", + "once_cell", + "os_pipe", + "shared_child", +] + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "endian-type" version = "0.1.2" @@ -145,12 +203,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -171,14 +229,25 @@ checksum = "ef033ed5e9bad94e55838ca0ca906db0e043f517adda0c8b79c7a8c66c93c1b5" dependencies = [ "cfg-if", "rustix", - "windows-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "getrandom" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +dependencies = [ + "cfg-if", + "libc", + "wasi", ] [[package]] name = "hashbrown" -version = "0.14.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "heck" @@ -186,15 +255,6 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -[[package]] -name = "home" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" -dependencies = [ - "windows-sys", -] - [[package]] name = "indexmap" version = "2.1.0" @@ -205,12 +265,29 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.1", + "libc", + "redox_syscall", +] + [[package]] name = "linux-raw-sys" version = "0.4.11" @@ -240,20 +317,37 @@ dependencies = [ [[package]] name = "nix" -version = "0.26.4" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" dependencies = [ + "autocfg", "bitflags 1.3.2", "cfg-if", "libc", ] +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "os_pipe" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae859aa07428ca9a929b936690f8b12dc5f11dd8c6992a18ca93919f28bc177" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ "unicode-ident", ] @@ -277,36 +371,55 @@ dependencies = [ "nibble_vec", ] +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_users" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + [[package]] name = "rustix" -version = "0.38.24" +version = "0.38.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ad981d6c340a49cdc40a1028d9c6084ec7e9fa33fcb839cab656a267071e234" +checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" dependencies = [ "bitflags 2.4.1", "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] name = "rustyline" -version = "12.0.0" +version = "10.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "994eca4bca05c87e86e15d90fc7a91d1be64b4482b38cb2d27474568fe7c9db9" +checksum = "c1e83c32c3f3c33b08496e0d1df9ea8c64d39adb8eb36a1ebb1440c690697aef" dependencies = [ - "bitflags 2.4.1", + "bitflags 1.3.2", "cfg-if", "clipboard-win", + "dirs-next", "fd-lock", - "home", "libc", "log", "memchr", "nix", "radix_trie", - "rustyline-derive", "scopeguard", "unicode-segmentation", "unicode-width", @@ -316,13 +429,13 @@ dependencies = [ [[package]] name = "rustyline-derive" -version = "0.9.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a32af5427251d2e4be14fc151eabe18abb4a7aad5efee7044da9f096c906a43" +checksum = "107c3d5d7f370ac09efa62a78375f94d94b8a33c61d8c278b96683fb4dbf2d8d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -333,22 +446,22 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.192" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.192" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.39", ] [[package]] @@ -360,6 +473,16 @@ dependencies = [ "serde", ] +[[package]] +name = "shared_child" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0d94659ad3c2137fef23ae75b03d5241d633f8acded53d672decfa0e6e0caef" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "smallvec" version = "1.11.2" @@ -372,7 +495,10 @@ version = "0.1.0" dependencies = [ "anyhow", "clap", + "console", + "duct", "rustyline", + "rustyline-derive", "toml", ] @@ -388,6 +514,17 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.39" @@ -399,6 +536,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "toml" version = "0.8.8" @@ -457,6 +614,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "winapi" version = "0.3.9" @@ -479,13 +642,46 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "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", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "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]] @@ -494,57 +690,156 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 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.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "winnow" version = "0.5.19" diff --git a/Cargo.toml b/Cargo.toml index 793a396..bce43e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sov-snap-generator" -version = "0.1.0" +version = "0.1.2" edition = "2021" license = "MIT OR Apache-2.0" authors = ["Sovereign Labs "] @@ -12,5 +12,12 @@ readme = "README.md" [dependencies] anyhow = "1.0" clap = { version = "4.4", features = ["derive"] } -rustyline = { version = "12.0", features = ["derive"] } +console = "0.15" +duct = "0.13" +rustyline = { version = "10.0" } +rustyline-derive = "0.7" toml = "0.8" + +[[bin]] +name = "cargo-sov-snap-generator" +path = "./src/main.rs" diff --git a/README.md b/README.md index 91338e6..4b2b6bb 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ This utility creates [Metamask Snaps](https://metamask.io/snaps/) for Sovereign ## Installation ```bash -cargo install --git https://github.com/Sovereign-Labs/sov-snap-generator --tag "v0.1.1" +cargo install --git https://github.com/Sovereign-Labs/sov-snap-generator --tag "v0.1.2" ``` Also, check if the `wasm32-wasi` target is installed: @@ -38,7 +38,7 @@ rustup target add wasm32-wasi This example will re-export the `RuntimeCall` from `demo-stf`. -First, we create the project: +First, we create the sample project: ```bash cargo new --lib sov-runtime @@ -66,13 +66,7 @@ demo-stf = { git = "https://github.com/Sovereign-Labs/sovereign-sdk.git", rev = sov-mock-da = { git = "https://github.com/Sovereign-Labs/sovereign-sdk.git", rev = "df169be" } ``` -Then, fetch the default `constants.json` required for module compilation. - -```bash -wget https://raw.githubusercontent.com/Sovereign-Labs/sovereign-sdk/d42e289f26b9824b5ed54dbfbda94007dee305b2/constants.json -``` - -Finally, update the `src/lib.rs` with the following: +Then, update the `src/lib.rs` with the following: ```rust /// The `Context` will be used to define the asymmetric key pair. @@ -85,6 +79,12 @@ pub use sov_mock_da::MockDaSpec as DaSpec; pub use demo_stf::runtime::RuntimeCall; ``` +Finally, fetch the default `constants.json` required for module compilation. + +```bash +wget https://raw.githubusercontent.com/Sovereign-Labs/sovereign-sdk/d42e289f26b9824b5ed54dbfbda94007dee305b2/constants.json +``` + The utility defaults to searching for `Context`, `DaSpec`, and `RuntimeCall` definitions at the project root. However, these can be replaced with other paths. For a sanity check, run the following: @@ -101,41 +101,27 @@ Some prompts can be specified through CLI arguments. For more information, run: sov-snap-generator --help ``` -To skip all prompts and checks, run: +We use the options `--defaults` and `--force` to skip prompt confirmations and checks. + +First, we generate the target project. This will create the Snap project under `../sov-runtime-snap` (i.e. `--target` argument). ```bash -sov-snap-generator --defaults --force +cargo sov-snap-generator init --defaults --force --target ../sov-runtime-snap ``` -To run the interactive mode, at the project root, execute: +We can perform a sanity check on the generated WASM project. This project is editable by the user to adhere to the WASM file specification. Nevertheless, functions designated with the directive `#[no_mangle]` will be consumed by the Snap and will typically remain unchanged. ```bash -sov-snap-generator +cargo check --manifest-path ../sov-runtime-snap/external/sov-wasm/Cargo.toml ``` -The first prompt will inquire about the project `path`. It defaults to the current directory, so you can simply press `Enter`. +To compile the Snap, run: ```bash -Insert the path to your `Cargo.toml` -> /home/sovereign/sov-runtime +cargo sov-snap-generator build --target ../sov-runtime-snap ``` -The next prompt asks for the manifest definition to use the module as a dependency. It defaults to the parent directory of the resolved project manifest file. - -The generated WASM file requires certain dependencies. The subsequent prompts will default to the dependencies specified in the project manifest file, if present. It will then ask for the following items in order: - -- Base project (usually a path pointing to the parent directory of the project manifest file). -- [borsh](https://crates.io/crates/borsh) -- [serde_json](https://crates.io/crates/serde_json) -- [sov-modules-api](https://github.com/Sovereign-Labs/sovereign-sdk/tree/d42e289f26b9824b5ed54dbfbda94007dee305b2/module-system/sov-modules-api) - -The next step is to define the target directory in which the generated Snap will be placed. It defaults to a new directory adjacent to the current project, suffixed by `-snap`. - -The WASM file depends on specific definitions, typically customized by the module implementation. By default, it searches the root of the project for exports of `Context`, `DaSpec`, and `RuntimeCall`. The subsequent prompts will ask for these paths. If your implementation diverges from this standard, replace these items with their fully qualified paths. - -The template snap will be downloaded from a GitHub release. The next prompts inquire about the repository origin and its branch/tag. - -After the installation is executed, you should see a message indicating the project generated on ``. +Finally, you can run the local development environment. #### Run a local development environment @@ -148,8 +134,50 @@ cd ../sov-runtime-snap yarn start ``` -Your Snap will be accessible by default at http://localhost:8000. Click Connect/Reconnect to load the Snap into your Metamask Flask, enabling you to sign transactions. +Your Snap is accessible by default at `http://localhost:8000`. To load the Snap into your Metamask Flask and enable signing of transactions, click on Connect/Reconnect. + +This development environment allows you to submit a signed transaction to a [sov-sequencer](https://github.com/Sovereign-Labs/sovereign-sdk/tree/d42e289f26b9824b5ed54dbfbda94007dee305b2/full-node/sov-sequencer). However, most modern browsers query external services for a [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) policy. Normally, a Sequencer will be served behind a layer that handles authentication. + +To bypass this issue, you can either disable CORS in your browser or set up a proxy to handle CORS requests, forwarding the payload to the sequencer. Here is a minimalistic Python script that will run a CORS proxy on port 9000, redirecting all requests to `127.0.0.1:12345`: + +```python +from flask import Flask, request, jsonify +import requests + +app = Flask(__name__) -This development environment allows you to submit a signed transaction to a [sov-sequencer](https://github.com/Sovereign-Labs/sovereign-sdk/tree/d42e289f26b9824b5ed54dbfbda94007dee305b2/full-node/sov-sequencer). However, most modern browsers query external services for a [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) policy. +# Define the target URL (the URL you want to proxy to) +TARGET_URL = "http://127.0.0.1:12345" -You can either disable CORS in your browser or set up a proxy to handle CORS requests, forwarding the payload to the sequencer. +# Enable CORS for all routes +@app.after_request +def add_cors_headers(response): + response.headers["Access-Control-Allow-Origin"] = "*" + response.headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS" + response.headers["Access-Control-Allow-Headers"] = "Content-Type" + return response + +@app.route('/', defaults={'path': ''}, methods=['GET', 'POST', 'OPTIONS']) +@app.route('/', methods=['GET', 'POST', 'OPTIONS']) +def proxy(path): + target_url = f"{TARGET_URL}/{path}" + headers = {key: value for (key, value) in request.headers if key != 'Host'} + + if request.method == 'OPTIONS': + # Handle preflight requests + return jsonify({'status': 'ok'}) + + if request.method == 'POST': + # Forward POST request + response = requests.post(target_url, data=request.get_data(), headers=headers) + else: + # Forward GET request + response = requests.get(target_url, headers=headers) + + # Forward the received headers and content to the client + headers = [(key, value) for (key, value) in response.headers.items()] + return response.content, response.status_code, headers + +if __name__ == '__main__': + app.run(port=9000) # Change the port if needed +``` diff --git a/src/args.rs b/src/args.rs index fe0615b..cced09f 100644 --- a/src/args.rs +++ b/src/args.rs @@ -1,42 +1,64 @@ -use std::path::PathBuf; - -use clap::Parser; +use clap::{Parser, Subcommand}; #[derive(Parser)] pub struct Cli { - /// Path to the directory that contains the manifest TOML file of the project. - #[arg(short, long)] - pub path: Option, + #[command(subcommand)] + pub command: Commands, + + #[arg( + long, + short = 'q', + action = clap::ArgAction::Count, + global = true, + help = "Sets the verbosity level.", + )] + pub quiet: u8, - /// Target directory to output the generated project. - #[arg(short, long)] - pub target: Option, + /// Defaults all inputs. + #[arg(long, global = true)] + pub defaults: bool, - /// Git remote to use when cloning the origin repository. - #[arg(short, long)] - pub origin: Option, + /// Skips all confirmations. + #[arg(short, long, global = true)] + pub force: bool, +} - /// Branch to use when cloning the origin repository. - #[arg(short, long)] - pub branch: Option, +pub struct InterfaceArgs { + pub quiet: u8, + pub defaults: bool, + pub force: bool, +} - /// Context definition of the runtime spec. - #[arg(short, long)] - pub context: Option, +impl Cli { + pub fn split_interface(self) -> (Subcommands, InterfaceArgs) { + let command = match self.command { + Commands::SovSnapGenerator { command } => command, + }; - /// DA definition of the runtime. - #[arg(short, long)] - pub da_spec: Option, + ( + command, + InterfaceArgs { + quiet: self.quiet, + defaults: self.defaults, + force: self.force, + }, + ) + } +} - /// Runtime call definition. - #[arg(short, long)] - pub runtime: Option, +#[derive(Debug, Clone, Subcommand)] +pub enum Commands { + SovSnapGenerator { + #[command(subcommand)] + command: Subcommands, + }, +} - /// Defaults all inputs. - #[arg(long)] - pub defaults: bool, +#[derive(Debug, Clone, Subcommand)] +pub enum Subcommands { + /// Initializes the project from the provided path. + Init(super::init::Init), - /// Skips all confirmations. - #[arg(long)] - pub force: bool, + /// Builds a project initialized via `Init`. + Build(super::build::Build), } diff --git a/src/build.rs b/src/build.rs new file mode 100644 index 0000000..5d4bfde --- /dev/null +++ b/src/build.rs @@ -0,0 +1,48 @@ +use std::{env, path::PathBuf}; + +use clap::Parser; + +use super::interface::Interface; + +#[derive(Debug, Clone, Parser)] +pub struct Build { + /// Target directory to output the generated project. + #[arg(short, long)] + target: Option, +} + +pub fn build(args: Build, interface: &mut Interface) -> anyhow::Result<()> { + let Build { target } = args; + + let cwd = env::current_dir()?.display().to_string(); + + interface.prompt("Insert the target directory of the project"); + + let target = interface.path_or_read(Some(&cwd), target); + if target.is_file() { + interface.bail(format!( + "The provided target `{}` is a file; use a directory", + target.display() + )); + } + + interface.info(format!("Using target root `{}`...", target.display())); + + duct::cmd!("yarn", "install").dir(&target).run()?; + + interface.info(format!( + "Yarn packages installed on `{}`...", + target.display() + )); + + duct::cmd!("yarn", "update-wasm").dir(&target).run()?; + + interface.info(format!("WASM file built on `{}`...", target.display())); + + duct::cmd!("yarn", "build").dir(&target).run()?; + + interface.info(format!("Yarn project built on `{}`...", target.display())); + interface.info("To start the browser application, run `yarn start` on the project root."); + + Ok(()) +} diff --git a/src/definitions.rs b/src/definitions.rs deleted file mode 100644 index ef11726..0000000 --- a/src/definitions.rs +++ /dev/null @@ -1,6 +0,0 @@ -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Definitions { - pub context: String, - pub da_spec: String, - pub runtime: String, -} diff --git a/src/init.rs b/src/init.rs new file mode 100644 index 0000000..3484afe --- /dev/null +++ b/src/init.rs @@ -0,0 +1,197 @@ +use std::{env, fs, path::PathBuf}; + +use clap::Parser; + +use super::{ + interface::Interface, + manifest::{Dependency, Manifest}, +}; + +#[derive(Debug, Clone, Parser)] +pub struct Init { + /// Path of the runtime module cargo project. + #[arg(short, long)] + path: Option, + + /// Target directory to output the generated project. + #[arg(short, long)] + target: Option, + + /// Git remote to use when cloning the origin repository. + #[arg(short, long)] + origin: Option, + + /// Branch to use when cloning the origin repository. + #[arg(short, long)] + branch: Option, +} + +pub fn init(args: Init, interface: &mut Interface) -> anyhow::Result<()> { + let Init { + path, + target, + origin, + branch, + } = args; + + let cwd = env::current_dir()?; + interface.prompt("Insert the path to your `Cargo.toml`"); + let path = interface.path_or_read(Some(&cwd.display().to_string()), path); + let path = path + .is_dir() + .then(|| path.join("Cargo.toml")) + .unwrap_or(path); + + if !path.exists() { + anyhow::bail!( + "Failed to locate `Cargo.toml`; {} does not exist", + path.display() + ); + } + + if !path.is_file() { + anyhow::bail!( + "Failed to locate `Cargo.toml`; {} is not a file", + path.display() + ); + } + + let path = path.canonicalize()?; + + interface.info(format!("Using manifest `{}`...", path.display())); + + let manifest = Manifest::read(&path, interface)?; + + interface.prompt("Insert the target directory of the project"); + let target_default = cwd + .parent() + .unwrap_or_else(|| cwd.as_path()) + .join(format!("{}-snap", manifest.project.name)) + .display() + .to_string(); + + let target = interface.path_or_read(Some(&target_default), target); + if target.is_file() { + interface.bail(format!( + "The provided target `{}` is a file; use a directory", + target.display() + )); + } + + if target.exists() { + if fs::remove_dir(&target).is_err() { + interface.prompt(format!( + "The target directory `{}` already exists; overwrite? [y/n]", + target.display() + )); + + interface.read_confirmation(); + + fs::remove_dir_all(&target)?; + } + } + + interface.prompt("Insert the origin git repository of the snap template"); + let origin_default = "https://github.com/Sovereign-Labs/sov-snap"; + let origin = interface.line_or_read(Some(&origin_default), origin); + + interface.prompt("Insert the branch of the snap template"); + let branch_default = "v0.1.3"; + let branch = interface.line_or_read(Some(&branch_default), branch); + + interface.info(format!( + "Cloning the snap template into `{}`...", + target.display() + )); + + duct::cmd!( + "git", + "clone", + "--quiet", + "--progress", + "-c", + "advice.detachedHead=false", + "--branch", + branch, + "--single-branch", + "--depth", + "1", + origin, + &target, + ) + .run()?; + + interface.info(format!( + "Cloned the snap template into `{}`", + target.display() + )); + + let target_wasm = target.join("external").join("sov-wasm").canonicalize()?; + let target_wasm_manifest = target_wasm.join("Cargo.toml"); + let target_definitions = target_wasm.join("src").join("definitions.rs"); + + let borsh = manifest + .dependencies + .get(&Dependency::new("borsh")) + .cloned() + .unwrap_or_else(|| String::from("\"0.10.3\"")); + let serde_json = manifest + .dependencies + .get(&Dependency::new("serde_json")) + .cloned() + .unwrap_or_else(|| String::from("\"1.0\"")); + let sov_modules_api = manifest.dependencies.get(&Dependency::new("sov-modules-api")).cloned().unwrap_or_else(|| String::from(r#"{ git = "https://github.com/Sovereign-Labs/sovereign-sdk.git", rev = "df169be", features = ["serde"] }"#)); + let sov_mock_da = manifest.dependencies.get(&Dependency::new("sov-mock-da")).cloned().unwrap_or_else(|| String::from(r#"{ git = "https://github.com/Sovereign-Labs/sovereign-sdk.git", rev = "df169be" }"#)); + + let project = path + .parent() + .unwrap_or_else(|| path.as_path()) + .display() + .to_string(); + let wasm_manifest = format!( + r#"[package] +name = "sov-wasm" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +{} = {{ path = "{}" }} +borsh = {} +serde_json = {} +sov-modules-api = {} +sov-mock-da = {} +"#, + manifest.project.name, project, borsh, serde_json, sov_modules_api, sov_mock_da + ); + + let definitions = format!( + r#"pub type Context = sov_modules_api::default_context::ZkDefaultContext; +pub type DaSpec = sov_mock_da::MockDaSpec; +pub type RuntimeCall = {}::RuntimeCall; +"#, + manifest.project.formatted + ); + + fs::write(&target_wasm_manifest, wasm_manifest)?; + fs::write(&target_definitions, definitions)?; + + interface.info(format!( + "Generated the snap template into `{}`", + target.display() + )); + + interface.info(format!( + "Generated the WASM template into `{}`", + target_wasm.display() + )); + + interface.info(format!( + "Edit the generated WASM template and run `cargo sov-snap-generator build --path {}`", + target.display(), + )); + + Ok(()) +} diff --git a/src/interface.rs b/src/interface.rs index 926e8b2..2470857 100644 --- a/src/interface.rs +++ b/src/interface.rs @@ -1,402 +1,159 @@ -use std::{ - env, fs, - path::{Path, PathBuf}, - process::{self, Command, Output}, -}; +use std::{path::PathBuf, process, str}; +use console::{Key, Term}; use rustyline::{ - completion::{Completer, FilenameCompleter, Pair}, - error::ReadlineError, - hint::Hinter, - history::{DefaultHistory, History}, - Context, DefaultEditor, Editor, Helper, Highlighter, Validator, + completion::FilenameCompleter, error::ReadlineError, hint::Hinter, Context, Editor, }; +use rustyline_derive::{Completer, Helper, Highlighter, Validator}; -use super::{ - args::Cli, - definitions::Definitions, - manifest::{Dependencies, Manifest}, -}; +use super::args::InterfaceArgs; + +#[derive(Completer, Helper, Validator, Highlighter)] +pub struct LineParser { + filename_completer: FilenameCompleter, +} -type FilenameEditor = Editor; +impl Hinter for LineParser { + type Hint = String; + + fn hint(&self, line: &str, _pos: usize, _ctx: &Context<'_>) -> Option { + self.filename_completer + .complete_path(line, line.len()) + .ok() + .and_then(|(_, pairs)| { + pairs + .first() + .map(|pair| pair.replacement[line.len()..].to_string()) + }) + } +} -#[derive(Debug, Default)] pub struct Interface { - editor: Option, - filename_editor: Option, + args: InterfaceArgs, + rl: Editor<()>, + rl_path: Editor, } impl Interface { const PROMPT: &'static str = "> "; - /// Lazy load the inner editor. - fn editor(&mut self) -> anyhow::Result<&mut DefaultEditor> { - if self.editor.is_none() { - self.editor.replace(DefaultEditor::new()?); + pub fn info>(&self, message: M) { + if self.args.quiet < 1 { + println!("{}", message.as_ref()); } + } - self.editor - .as_mut() - .ok_or_else(|| anyhow::Error::msg("unavailable editor")) + pub fn error>(&self, message: M) { + if self.args.quiet < 2 { + eprintln!("{}", message.as_ref()); + } } - /// Lazy load the inner editor. - fn filename_editor(&mut self) -> anyhow::Result<&mut FilenameEditor> { - if self.filename_editor.is_none() { - self.filename_editor.replace(FilenameEditor::new()?); + pub fn prompt>(&self, message: M) { + if self.args.quiet < 3 { + println!("{}", message.as_ref()); } + } - self.filename_editor - .as_mut() - .ok_or_else(|| anyhow::Error::msg("unavailable editor")) + pub fn bail>(&self, message: M) { + self.error(message); + process::exit(1); } - fn read_line( - rl: &mut Editor, - args: &Cli, - prompt: Option<&str>, - mut initial_value: Option<&str>, - ) -> String - where - T: Helper, - U: History, - { - if args.defaults { - if let Some(value) = initial_value { - return value.to_string(); + pub fn read_line(&mut self, mut initial_value: Option<&str>) -> String { + if self.args.defaults { + if let Some(initial) = initial_value { + return initial.to_string(); } } - if let Some(prompt) = prompt { - println!("{}", prompt); - } - loop { - let readline = match initial_value.take() { - Some(initial) => rl.readline_with_initial(Self::PROMPT, (initial, "")), - None => rl.readline(Self::PROMPT), + let rl = match initial_value.take() { + Some(initial) => self.rl.readline_with_initial(Self::PROMPT, (initial, "")), + None => self.rl.readline(Self::PROMPT), }; - match readline { + match rl { Ok(line) => return line, Err(ReadlineError::Interrupted) => { - process::exit(0); + self.bail("CTRL-C"); } Err(ReadlineError::Eof) => { - eprintln!("CTRL-D"); - process::exit(0); + self.bail("CTRL-D"); } Err(err) => { - eprintln!("Input error: {}", err); + self.error(format!("error reading line: {}", err)); } } } } - fn expect_char( - &mut self, - args: &Cli, - prompt: Option<&str>, - initial_value: Option, - expected: char, - ) -> anyhow::Result<()> { - // TODO implement an actual read char function - let rl = self.editor()?; - let initial_value = initial_value.map(|c| c.to_string()); - let expected = expected.to_lowercase().to_string(); - let line = Self::read_line(rl, args, prompt, initial_value.as_ref().map(|s| s.as_str())) - .to_lowercase(); - - anyhow::ensure!(line == expected, "Expected {}, got {}", expected, line); - Ok(()) - } - - fn read_path( - &mut self, - args: &Cli, - prompt: Option<&str>, - initial_value: Option<&str>, - ) -> anyhow::Result { - let rl = self.filename_editor()?; - let path = Self::read_line(rl, args, prompt, initial_value); - - Ok(PathBuf::from(path)) - } - - pub fn manifest(&mut self, args: &Cli) -> anyhow::Result { - let dir_or_file = match args.path.as_ref().cloned() { - Some(path) => path, - None => { - let cwd = env::current_dir()?.display().to_string(); - self.read_path( - args, - Some("Insert the path to your `Cargo.toml`"), - Some(&cwd), - )? + pub fn read_path(&mut self, mut initial_value: Option<&str>) -> PathBuf { + if self.args.defaults { + if let Some(initial) = initial_value { + return PathBuf::from(initial); } - }; - - let path = if dir_or_file.is_dir() { - dir_or_file.join("Cargo.toml") - } else { - dir_or_file - }; - - if !path.exists() { - anyhow::bail!( - "Failed to locate `Cargo.toml`; {} does not exist", - path.display() - ); } - if !path.is_file() { - anyhow::bail!( - "Failed to locate `Cargo.toml`; {} is not a file", - path.display() - ); - } - - let path = path.canonicalize()?; - - println!("Using manifest `{}`...", path.display()); - let mut manifest = Manifest::try_from(path.as_path())?; - - if let Dependencies::Unresolved { - borsh, - serde_json, - sov_modules_api, - } = &manifest.dependencies - { - let rl = self.editor()?; - - let prompt = Some("Enter the manifest dependency for the base library"); - let initial = format!("{{ path = \"{}\" }}", manifest.parent.display()); - let base = Self::read_line(rl, args, prompt, Some(&initial)); - - let prompt = Some("Enter the manifest dependency for borsh"); - let borsh = Self::read_line(rl, args, prompt, borsh.as_ref().map(|s| s.as_str())); - - let prompt = Some("Enter the manifest dependency for serde_json"); - let serde_json = - Self::read_line(rl, args, prompt, serde_json.as_ref().map(|s| s.as_str())); - - let prompt = Some("Enter the manifest dependency for sov-modules-api"); - let sov_modules_api = Self::read_line( - rl, - args, - prompt, - sov_modules_api.as_ref().map(|s| s.as_str()), - ); - - manifest.dependencies = Dependencies::Resolved { - base, - borsh, - serde_json, - sov_modules_api, + loop { + let rl = match initial_value.take() { + Some(initial) => self + .rl_path + .readline_with_initial(Self::PROMPT, (initial, "")), + None => self.rl_path.readline(Self::PROMPT), }; - } - - println!("Reading project `{}`...", manifest.name); - Ok(manifest) - } - pub fn target_dir(&mut self, args: &Cli, manifest: &Manifest) -> anyhow::Result { - let target = match args.target.as_ref().cloned() { - Some(target) => target, - None => { - let name = format!("{}-snap", manifest.name); - let target = manifest - .parent - .parent() - .unwrap_or(&manifest.parent) - .join(name) - .display() - .to_string(); - - self.read_path( - args, - Some("Insert the target directory"), - Some(target.as_str()), - )? - } - }; - - if target.is_file() { - anyhow::bail!( - "The provided target `{}` is a file; use a directory", - target.display() - ); - } - - if target.exists() { - if fs::remove_dir(&target).is_err() { - if !args.force { - if args.defaults { - anyhow::bail!( - "The `defaults` flag is set, but the target `{}` already exists. This operation is not permitted. To bypass this check for the `--defaults` arguments, combine it with `--force`.", - target.display() - ); - } - - let prompt = format!( - "The provided target `{}` is not empty and will be erased; confirm? [y/n]", - target.display() - ); - - if self - .expect_char(args, Some(&prompt), Some('n'), 'y') - .is_err() - { - anyhow::bail!("Operation aborted"); - } + match rl { + Ok(line) => return PathBuf::from(line), + Err(ReadlineError::Interrupted) => { + //self.warn("CTRL-C"); + } + Err(ReadlineError::Eof) => { + self.bail("CTRL-D"); + } + Err(err) => { + self.error(format!("error reading path: {}", err)); } - - fs::remove_dir_all(&target)?; } } - - Ok(target) } - pub fn git_clone

(&mut self, args: &Cli, target: P) -> anyhow::Result - where - P: AsRef, - { - let origin = match args.origin.as_ref().cloned() { - Some(origin) => origin, - None => { - let rl = self.editor()?; - Self::read_line( - rl, - args, - Some("Insert the origin git repository of the snap template"), - Some("https://github.com/Sovereign-Labs/sov-snap"), - ) - } - }; - - let branch = match args.branch.as_ref().cloned() { - Some(branch) => branch, - None => { - let rl = self.editor()?; - Self::read_line( - rl, - args, - Some("Insert the branch of the snap template"), - Some("v0.1.2"), - ) - } - }; + pub fn read_confirmation(&self) { + if self.args.force { + return; + } - Command::new("git") - .arg("clone") - .arg("--quiet") - .arg("--progress") - .arg("-c") - .arg("advice.detachedHead=false") - .arg("--branch") - .arg(branch) - .arg("--single-branch") - .arg("--depth") - .arg("1") - .arg(origin) - .arg(target.as_ref()) - .output() - .map_err(Into::into) + match Term::stdout().read_key() { + Ok(Key::Char('y')) | Ok(Key::Char('Y')) => (), + Ok(_) => self.bail("Operation aborted"), + Err(e) => self.bail(format!("error reading char: {}", e)), + } } - pub fn definitions(&mut self, args: &Cli, manifest: &Manifest) -> anyhow::Result { - let context = match args.context.as_ref().cloned() { - Some(context) => context, - None => { - let rl = self.editor()?; - let initial = format!("{}::Context", manifest.name_replaced); - Self::read_line( - rl, - args, - Some("Insert the path of the runtime context"), - Some(&initial), - ) - } - }; - - let da_spec = match args.da_spec.as_ref().cloned() { - Some(da_spec) => da_spec, - None => { - let rl = self.editor()?; - let initial = format!("{}::DaSpec", manifest.name_replaced); - Self::read_line( - rl, - args, - Some("Insert the path of the DA runtime spec"), - Some(&initial), - ) - } - }; - - let runtime = match args.runtime.as_ref().cloned() { - Some(runtime) => runtime, - None => { - let rl = self.editor()?; - let initial = format!("{}::RuntimeCall", manifest.name_replaced); - Self::read_line( - rl, - args, - Some("Insert the path of the runtime call"), - Some(&initial), - ) - } - }; + pub fn path_or_read(&mut self, initial_value: Option<&str>, path: Option) -> PathBuf { + if let Some(p) = path { + return p; + } - Ok(Definitions { - context, - da_spec, - runtime, - }) + self.read_path(initial_value) } -} - -#[derive(Helper, Validator, Highlighter)] -pub struct FilenameParser { - filename_completer: FilenameCompleter, -} - -impl Completer for FilenameParser { - type Candidate = Pair; - fn complete( - &self, - line: &str, - pos: usize, - _ctx: &Context<'_>, - ) -> rustyline::Result<(usize, Vec)> { - // TODO filename completion is not being called; rustyline bug? - if line.is_empty() { - // hints current working dir by default - let cwd = env::current_dir() - .expect("failed to read current working dir") - .display() - .to_string(); - - let pair = Pair { - display: cwd.clone(), - replacement: cwd, - }; - - return Ok((0, vec![pair])); + pub fn line_or_read(&mut self, initial_value: Option<&str>, path: Option) -> String { + if let Some(p) = path { + return p; } - self.filename_completer.complete_path(line, pos) + self.read_line(initial_value) } } -impl Hinter for FilenameParser { - type Hint = String; +impl TryFrom for Interface { + type Error = anyhow::Error; - fn hint(&self, line: &str, pos: usize, ctx: &Context<'_>) -> Option { - self.complete(line, pos, ctx) - .ok() - .and_then(|(_, pairs)| pairs.first().cloned()) - .map(|pair| pair.replacement[pos..].to_string()) + fn try_from(args: InterfaceArgs) -> anyhow::Result { + let rl = Editor::new()?; + let rl_path = Editor::new()?; + + Ok(Self { args, rl, rl_path }) } } diff --git a/src/main.rs b/src/main.rs index 585accc..bc61ec6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,148 +1,28 @@ -use std::{ - fs, - io::{self, Write}, - path::Path, - process::Command, -}; - use clap::Parser; mod args; -mod definitions; +mod build; +mod init; mod interface; mod manifest; use args::Cli; -use definitions::Definitions; use interface::Interface; -use manifest::Dependencies; fn main() -> anyhow::Result<()> { - let args = Cli::parse(); - let mut interface = Interface::default(); - let manifest = interface.manifest(&args)?; - let target = interface.target_dir(&args, &manifest)?; - let definitions = interface.definitions(&args, &manifest)?; - - git_clone(&mut interface, &args, &target)?; - generate_wasm_project(&manifest, &target, &definitions)?; - generate_snap(&target)?; - - println!( - "Snap generated on `{}`", - target.join("packages").join("snap").display() - ); - - println!( - "To run the web development server, install Metamask Flask and run `yarn start` on `{}`", - target.display() - ); - - Ok(()) -} - -fn git_clone

(interface: &mut Interface, args: &Cli, target: P) -> anyhow::Result<()> -where - P: AsRef, -{ - let target = target.as_ref(); - println!("Cloning into directory `{}`...", target.display()); - - let output = interface.git_clone(&args, target)?; - io::stderr().write_all(&output.stderr)?; - io::stdout().write_all(&output.stdout)?; - if !output.status.success() { - anyhow::bail!("Git clone failed; did you forget to install git?"); - } - - Ok(()) -} - -fn generate_wasm_project

( - manifest: &manifest::Manifest, - target: P, - definitions: &Definitions, -) -> anyhow::Result<()> -where - P: AsRef, -{ - let target_dir = target.as_ref().join("external").join("sov-wasm"); - let target_manifest = target_dir.join("Cargo.toml"); - - println!("Writing manifest to `{}`...", target_manifest.display()); + let (command, interface) = Cli::parse().split_interface(); + let mut interface = Interface::try_from(interface)?; - let mut output = r#"[package] -name = "sov-wasm" -version = "0.1.0" -edition = "2021" + let result = match command { + args::Subcommands::Init(v) => init::init(v, &mut interface), + args::Subcommands::Build(v) => build::build(v, &mut interface), + }; -[lib] -crate-type = ["cdylib", "rlib"] - -[dependencies] -"# - .to_string(); - - if let Dependencies::Resolved { - base, - borsh, - serde_json, - sov_modules_api, - } = &manifest.dependencies - { - output.push_str(&format!("{} = {}\n", manifest.name, base)); - output.push_str(&format!("borsh = {}\n", borsh)); - output.push_str(&format!("serde_json = {}\n", serde_json)); - output.push_str(&format!("sov-modules-api = {}\n", sov_modules_api)); + if let Err(err) = result { + interface.bail(err.to_string()); } - fs::write(target_manifest, output.as_bytes())?; - - println!("Writing definitions..."); - - let target_definitions = target_dir.join("src").join("definitions.rs"); - let mut output = String::new(); - output.push_str(&format!("pub type Context = {};\n", definitions.context)); - output.push_str(&format!("pub type DaSpec = {};\n", definitions.da_spec)); - output.push_str(&format!( - "pub type RuntimeCall = {};\n", - definitions.runtime - )); - - fs::write(target_definitions, output.as_bytes())?; - - Ok(()) -} - -fn generate_snap

(target: P) -> anyhow::Result<()> -where - P: AsRef, -{ - let target = target.as_ref(); - - println!("Installing yarn dependencies on `{}`...", target.display()); - - let output = Command::new("yarn") - .arg("install") - .current_dir(target) - .output()?; - io::stderr().write_all(&output.stderr)?; - io::stdout().write_all(&output.stdout)?; - if !output.status.success() { - anyhow::bail!("Yarn command failed; did you forget to install yarn?"); - } - - println!("Installing yarn WASM file on `{}`...", target.display()); - - let output = Command::new("yarn") - .arg("update-wasm") - .current_dir(target) - .output()?; - io::stderr().write_all(&output.stderr)?; - io::stdout().write_all(&output.stdout)?; - if !output.status.success() { - anyhow::bail!("Yarn command failed; did you forget to install cargo, binaryen, or wabt?"); - } + interface.info("Done."); Ok(()) } diff --git a/src/manifest.rs b/src/manifest.rs index d3b759e..09a27f2 100644 --- a/src/manifest.rs +++ b/src/manifest.rs @@ -1,74 +1,153 @@ -use std::{ - fs, - path::{Path, PathBuf}, -}; +use std::{collections::HashMap, fs, path::Path}; +use anyhow::Context; use toml::Table; -pub struct Manifest { - pub path: PathBuf, - pub parent: PathBuf, +use super::interface::Interface; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Dependency { pub name: String, - pub name_replaced: String, - pub dependencies: Dependencies, + pub formatted: String, } -impl TryFrom<&Path> for Manifest { - type Error = anyhow::Error; +impl Dependency { + pub fn new(dep: S) -> Self + where + S: AsRef, + { + let str = dep.as_ref(); + let name = str.to_string(); + let formatted = str.replace("-", "_"); + + Self { name, formatted } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Manifest { + pub project: Dependency, + pub dependencies: HashMap, +} + +impl Manifest { + fn normalize

(mut deps: HashMap, path: P) -> HashMap + where + P: AsRef, + { + let path = match path.as_ref().parent() { + Some(p) => p, + None => return deps, + }; + + for (_, v) in deps.iter_mut() { + if let Ok(t) = toml::from_str::(&format!("dep = {}", v)) { + if let Some(p) = t.get("dep") { + if let Some(p) = p.as_table() { + if let Some(p) = p.get("path") { + if let Some(p) = p.as_str() { + if let Ok(p) = path.join(p).canonicalize() { + *v = format!("{{ path = \"{}\" }}", p.display()).into(); + } + } + } + } + } + } + } + + deps + } + + fn resolve_workspace

( + workspace: P, + interface: &mut Interface, + ) -> Option> + where + P: AsRef, + { + let mut workspace = workspace.as_ref().parent()?.parent()?; + + loop { + let path = workspace.join("Cargo.toml"); + if path.exists() { + let manifest = fs::read_to_string(&path).ok()?; + let manifest: Table = toml::from_str(&manifest).ok()?; + let deps = manifest + .get("workspace")? + .as_table()? + .get("dependencies")? + .as_table()? + .into_iter() + .map(|(dep, v)| (Dependency::new(dep), v.to_string())) + .collect::>(); + + interface.info(format!( + "Using workspace dependencies from `{}`", + workspace.display() + )); - fn try_from(path: &Path) -> Result { - let parent = path - .parent() - .ok_or_else(|| anyhow::Error::msg("No parent path for the manifest dir"))? - .to_path_buf(); + return Some(Self::normalize(deps, path)); + } + workspace = workspace.parent()?; + } + } + + pub fn read

(path: P, interface: &mut Interface) -> anyhow::Result + where + P: AsRef, + { + let path = path.as_ref(); let manifest = fs::read_to_string(path)?; let manifest: Table = toml::from_str(&manifest)?; - let name = manifest["package"]["name"] + let project = manifest + .get("package") + .with_context(|| format!("Could not find `package` on `{}`", path.display()))? + .get("name") + .with_context(|| format!("Could not find `name` on `{}`", path.display()))? .as_str() - .ok_or_else(|| anyhow::Error::msg("Invalid `package.name`"))? - .to_string(); + .with_context(|| format!("Invalid `name` on `{}`", path.display()))?; + let project = Dependency::new(project); - let dependencies = Dependencies::from(&manifest); - let name_replaced = name.replace("-", "_"); + let workspace = Self::resolve_workspace(path, interface); + let manifest = manifest + .get("dependencies") + .with_context(|| format!("Could not find `dependencies` in `{}`", path.display()))? + .as_table() + .with_context(|| format!("Invalid `dependencies` in `{}`", path.display()))?; - Ok(Self { - parent, - path: path.to_path_buf(), - name, - name_replaced, - dependencies, - }) - } -} + let mut dependencies = HashMap::new(); -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Dependencies { - Unresolved { - borsh: Option, - serde_json: Option, - sov_modules_api: Option, - }, - Resolved { - base: String, - borsh: String, - serde_json: String, - sov_modules_api: String, - }, -} + for (dep, v) in manifest { + let dep = Dependency::new(dep); + let mut val = v.to_string(); + + if let Some(t) = v.as_table() { + if let Some(t) = t.get("workspace") { + if let Some(b) = t.as_bool() { + if b { + if let Some(w) = &workspace { + if let Some(d) = w.get(&dep) { + val = d.to_string(); + } + } + } + } + } + } -impl From<&Table> for Dependencies { - fn from(manifest: &Table) -> Self { - let dependencies = &manifest["dependencies"]; - let borsh = dependencies.get("borsh").map(|d| d.to_string()); - let serde_json = dependencies.get("serde_json").map(|d| d.to_string()); - let sov_modules_api = dependencies.get("sov-modules-api").map(|d| d.to_string()); - - Self::Unresolved { - borsh, - serde_json, - sov_modules_api, + dependencies.insert(dep, val); } + + let dependencies = Self::normalize(dependencies, path); + + interface.info(format!("Parsed dependencies from `{}`", path.display())); + + Ok(Self { + project, + dependencies, + }) } }