diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index c96167ad..e06d913c 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -77,7 +77,7 @@ jobs: - if: startsWith(matrix.target, 'x86_64') # specify directories explicitly to avoid building the preflight library (otherwise it will fail with missing symbols) run: | - for I in cmd/soroban-cli cmd/crates/* cmd/crates/soroban-test/tests/fixtures/test-wasms/hello_world ; do + for I in cmd/crates/* cmd/crates/soroban-test/tests/fixtures/test-wasms/hello_world ; do cargo test --target ${{ matrix.target }} --manifest-path $I/Cargo.toml done diff --git a/Cargo.lock b/Cargo.lock index 45bd18dc..432aff82 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -142,7 +142,7 @@ checksum = "531b97fb4cd3dfdce92c35dedbfdc1f0b9d8091c8ca943d6dae340ef5012d514" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn", ] [[package]] @@ -273,7 +273,7 @@ dependencies = [ "num-bigint", "proc-macro2", "quote", - "syn 2.0.39", + "syn", ] [[package]] @@ -314,7 +314,6 @@ version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ - "jobserver", "libc", ] @@ -349,15 +348,6 @@ dependencies = [ "clap_derive", ] -[[package]] -name = "clap-markdown" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325f50228f76921784b6d9f2d62de6778d834483248eefecd27279174797e579" -dependencies = [ - "clap", -] - [[package]] name = "clap_builder" version = "4.4.18" @@ -388,7 +378,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.39", + "syn", ] [[package]] @@ -397,16 +387,6 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" -[[package]] -name = "codespan-reporting" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -dependencies = [ - "termcolor", - "unicode-width", -] - [[package]] name = "colorchoice" version = "1.0.0" @@ -560,7 +540,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30d2b3721e861707777e3195b0158f950ae6dc4a27e4d02ff9f67e3eb3de199e" dependencies = [ "quote", - "syn 2.0.39", + "syn", ] [[package]] @@ -601,51 +581,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", -] - -[[package]] -name = "cxx" -version = "1.0.112" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ab30434ea0ff6aa640a08dda5284026a366d47565496fd40b6cbfbdd7e31a2" -dependencies = [ - "cc", - "cxxbridge-flags", - "cxxbridge-macro", - "link-cplusplus", -] - -[[package]] -name = "cxx-build" -version = "1.0.112" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b649d7dfae8268450d53d109388b337b9352c7cba1fc10db4a1bc23c3dc189fb" -dependencies = [ - "cc", - "codespan-reporting", - "once_cell", - "proc-macro2", - "quote", - "scratch", - "syn 2.0.39", -] - -[[package]] -name = "cxxbridge-flags" -version = "1.0.112" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42281b20eba5218c539295c667c18e2f50211bb11902419194c6ed1ae808e547" - -[[package]] -name = "cxxbridge-macro" -version = "1.0.112" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45506e3c66512b0a65d291a6b452128b7b1dd9841e20d1e151addbd2c00ea50" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.39", + "syn", ] [[package]] @@ -669,7 +605,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.39", + "syn", ] [[package]] @@ -680,7 +616,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 2.0.39", + "syn", ] [[package]] @@ -711,7 +647,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn", ] [[package]] @@ -1369,15 +1305,6 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" -[[package]] -name = "jobserver" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" -dependencies = [ - "libc", -] - [[package]] name = "js-sys" version = "0.3.67" @@ -1492,15 +1419,6 @@ dependencies = [ "redox_syscall", ] -[[package]] -name = "link-cplusplus" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9" -dependencies = [ - "cc", -] - [[package]] name = "linux-raw-sys" version = "0.4.13" @@ -1611,7 +1529,7 @@ checksum = "cfb77679af88f8b125209d354a202862602672222e7f2313fdd6dc349bad4712" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn", ] [[package]] @@ -1687,7 +1605,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn", ] [[package]] @@ -1791,7 +1709,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn", ] [[package]] @@ -1888,7 +1806,7 @@ dependencies = [ "base64 0.21.7", "libc", "sha2 0.10.8", - "soroban-env-host", + "soroban-env-host 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", "soroban-simulation", ] @@ -1899,7 +1817,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", - "syn 2.0.39", + "syn", ] [[package]] @@ -2142,12 +2060,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "rustversion" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" - [[package]] name = "ryu" version = "1.0.16" @@ -2178,12 +2090,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "scratch" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" - [[package]] name = "sct" version = "0.7.1" @@ -2280,7 +2186,7 @@ checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn", ] [[package]] @@ -2320,7 +2226,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.39", + "syn", ] [[package]] @@ -2433,6 +2339,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "soroban-builtin-sdk-macros" +version = "20.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3aa4d0718c6bb74d5b9f77d54dde6cc08a7eb6ef0e76b524f723a48f1cf5db2c" +dependencies = [ + "itertools 0.11.0", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "soroban-builtin-sdk-macros" version = "20.1.0" @@ -2441,20 +2359,19 @@ dependencies = [ "itertools 0.11.0", "proc-macro2", "quote", - "syn 2.0.39", + "syn", ] [[package]] name = "soroban-cli" version = "20.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e995a047a684547f1962d1064329c291632ed035b67ca7c423c4978cc231bd8" dependencies = [ - "assert_cmd", - "assert_fs", "base64 0.21.7", "cargo_metadata", "chrono", "clap", - "clap-markdown", "clap_complete", "crate-git-revision 0.0.4", "csv", @@ -2473,7 +2390,6 @@ dependencies = [ "num-bigint", "openssl", "pathdiff", - "predicates 2.1.5", "rand", "regex", "rpassword", @@ -2484,12 +2400,12 @@ dependencies = [ "serde_json", "sha2 0.10.8", "shlex", - "soroban-env-host", - "soroban-ledger-snapshot", - "soroban-sdk", - "soroban-spec 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", + "soroban-env-host 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "soroban-ledger-snapshot 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "soroban-sdk 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "soroban-spec 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "soroban-spec-json", - "soroban-spec-rust", + "soroban-spec-rust 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "soroban-spec-tools", "soroban-spec-typescript", "stellar-strkey 0.0.7", @@ -2503,11 +2419,27 @@ dependencies = [ "tracing", "tracing-appender", "tracing-subscriber", - "wasm-opt", "wasmparser 0.90.0", "which", ] +[[package]] +name = "soroban-env-common" +version = "20.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0e3c4b5ea7936e814706f2a5da6af574260319bcc66fbf5517b39f19b5fa365" +dependencies = [ + "crate-git-revision 0.0.6", + "ethnum", + "num-derive", + "num-traits", + "serde", + "soroban-env-macros 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "soroban-wasmi 0.31.1-soroban.20.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "static_assertions", + "stellar-xdr", +] + [[package]] name = "soroban-env-common" version = "20.1.0" @@ -2519,21 +2451,57 @@ dependencies = [ "num-derive", "num-traits", "serde", - "soroban-env-macros", - "soroban-wasmi", + "soroban-env-macros 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", + "soroban-wasmi 0.31.1-soroban.20.0.0 (git+https://github.com/stellar/wasmi?rev=ab29800224d85ee64d4ac127bac84cdbb0276721)", "static_assertions", "stellar-xdr", ] +[[package]] +name = "soroban-env-guest" +version = "20.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ff111936103653515b4471b951b6325b966c3bb31c4c1bd5892ea741aa028ca" +dependencies = [ + "soroban-env-common 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "static_assertions", +] + [[package]] name = "soroban-env-guest" version = "20.1.0" source = "git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4#36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4" dependencies = [ - "soroban-env-common", + "soroban-env-common 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", "static_assertions", ] +[[package]] +name = "soroban-env-host" +version = "20.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3d7de1f83760dddf9302dcc32f8adfebaff41946045ea67196511aca8d9f00" +dependencies = [ + "curve25519-dalek 4.1.1", + "ed25519-dalek 2.0.0", + "getrandom", + "hex-literal", + "hmac 0.12.1", + "k256", + "num-derive", + "num-integer", + "num-traits", + "rand", + "rand_chacha", + "sha2 0.10.8", + "sha3", + "soroban-builtin-sdk-macros 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "soroban-env-common 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "soroban-wasmi 0.31.1-soroban.20.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "static_assertions", + "stellar-strkey 0.0.8", +] + [[package]] name = "soroban-env-host" version = "20.1.0" @@ -2553,13 +2521,28 @@ dependencies = [ "rand_chacha", "sha2 0.10.8", "sha3", - "soroban-builtin-sdk-macros", - "soroban-env-common", - "soroban-wasmi", + "soroban-builtin-sdk-macros 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", + "soroban-env-common 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", + "soroban-wasmi 0.31.1-soroban.20.0.0 (git+https://github.com/stellar/wasmi?rev=ab29800224d85ee64d4ac127bac84cdbb0276721)", "static_assertions", "stellar-strkey 0.0.8", ] +[[package]] +name = "soroban-env-macros" +version = "20.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff774562cca63a173127b0b6679dce9afde49fd34474eec041dc5612134dda7" +dependencies = [ + "itertools 0.11.0", + "proc-macro2", + "quote", + "serde", + "serde_json", + "stellar-xdr", + "syn", +] + [[package]] name = "soroban-env-macros" version = "20.1.0" @@ -2571,13 +2554,27 @@ dependencies = [ "serde", "serde_json", "stellar-xdr", - "syn 2.0.39", + "syn", ] [[package]] name = "soroban-hello" version = "20.2.0" +[[package]] +name = "soroban-ledger-snapshot" +version = "20.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d0f4b117c50bec49f02eab898957f92f13cae78ee3d17d7660821742251b8c2" +dependencies = [ + "serde", + "serde_json", + "serde_with", + "soroban-env-common 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "soroban-env-host 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror", +] + [[package]] name = "soroban-ledger-snapshot" version = "20.1.0" @@ -2586,11 +2583,28 @@ dependencies = [ "serde", "serde_json", "serde_with", - "soroban-env-common", - "soroban-env-host", + "soroban-env-common 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", + "soroban-env-host 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", "thiserror", ] +[[package]] +name = "soroban-sdk" +version = "20.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6c13c0f657bec67785cb7d204fcaccca553f937a42743c6acedfd993ad69f6" +dependencies = [ + "bytes-lit", + "rand", + "serde", + "serde_json", + "soroban-env-guest 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "soroban-env-host 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "soroban-ledger-snapshot 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "soroban-sdk-macros 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "stellar-strkey 0.0.8", +] + [[package]] name = "soroban-sdk" version = "20.1.0" @@ -2603,13 +2617,33 @@ dependencies = [ "rand", "serde", "serde_json", - "soroban-env-guest", - "soroban-env-host", - "soroban-ledger-snapshot", - "soroban-sdk-macros", + "soroban-env-guest 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", + "soroban-env-host 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", + "soroban-ledger-snapshot 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", + "soroban-sdk-macros 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", "stellar-strkey 0.0.8", ] +[[package]] +name = "soroban-sdk-macros" +version = "20.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc7e5d144250bbea143f0863462e58200e2a82e37fa611b09f239f3d8e849a83" +dependencies = [ + "crate-git-revision 0.0.6", + "darling", + "itertools 0.11.0", + "proc-macro2", + "quote", + "rustc_version", + "sha2 0.10.8", + "soroban-env-common 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "soroban-spec 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "soroban-spec-rust 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "stellar-xdr", + "syn", +] + [[package]] name = "soroban-sdk-macros" version = "20.1.0" @@ -2622,11 +2656,11 @@ dependencies = [ "quote", "rustc_version", "sha2 0.10.8", - "soroban-env-common", + "soroban-env-common 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", "soroban-spec 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", - "soroban-spec-rust", + "soroban-spec-rust 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", "stellar-xdr", - "syn 2.0.39", + "syn", ] [[package]] @@ -2636,7 +2670,7 @@ source = "git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a920 dependencies = [ "anyhow", "rand", - "soroban-env-host", + "soroban-env-host 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", "static_assertions", "thiserror", ] @@ -2679,6 +2713,22 @@ dependencies = [ "thiserror", ] +[[package]] +name = "soroban-spec-rust" +version = "20.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a1bf5452f0d97cf280f14b66de98571e819e198b124b33472e49dba1128ed9d" +dependencies = [ + "prettyplease", + "proc-macro2", + "quote", + "sha2 0.10.8", + "soroban-spec 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "stellar-xdr", + "syn", + "thiserror", +] + [[package]] name = "soroban-spec-rust" version = "20.1.0" @@ -2690,7 +2740,7 @@ dependencies = [ "sha2 0.10.8", "soroban-spec 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", "stellar-xdr", - "syn 2.0.39", + "syn", "thiserror", ] @@ -2744,9 +2794,9 @@ dependencies = [ "serde_json", "sha2 0.10.8", "soroban-cli", - "soroban-env-host", - "soroban-ledger-snapshot", - "soroban-sdk", + "soroban-env-host 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", + "soroban-ledger-snapshot 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", + "soroban-sdk 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", "soroban-spec 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", "soroban-spec-tools", "stellar-strkey 0.0.7", @@ -2760,7 +2810,20 @@ name = "soroban-token-sdk" version = "20.1.0" source = "git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb#e6c2c900ab82b5f6eec48f69cb2cb519e19819cb" dependencies = [ - "soroban-sdk", + "soroban-sdk 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", +] + +[[package]] +name = "soroban-wasmi" +version = "0.31.1-soroban.20.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1aaa682a67cbd2173f1d60cb1e7b951d490d7c4e0b7b6f5387cbb952e963c46" +dependencies = [ + "smallvec", + "spin", + "wasmi_arena 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmi_core 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmparser-nostd", ] [[package]] @@ -2770,8 +2833,8 @@ source = "git+https://github.com/stellar/wasmi?rev=ab29800224d85ee64d4ac127bac84 dependencies = [ "smallvec", "spin", - "wasmi_arena", - "wasmi_core", + "wasmi_arena 0.4.0 (git+https://github.com/stellar/wasmi?rev=ab29800224d85ee64d4ac127bac84cdbb0276721)", + "wasmi_core 0.13.0 (git+https://github.com/stellar/wasmi?rev=ab29800224d85ee64d4ac127bac84cdbb0276721)", "wasmparser-nostd", ] @@ -2843,42 +2906,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" -[[package]] -name = "strum" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" - -[[package]] -name = "strum_macros" -version = "0.24.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn 1.0.109", -] - [[package]] name = "subtle" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" -[[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" @@ -2938,28 +2971,28 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" name = "test_custom_types" version = "20.2.0" dependencies = [ - "soroban-sdk", + "soroban-sdk 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", ] [[package]] name = "test_hello_world" version = "20.2.0" dependencies = [ - "soroban-sdk", + "soroban-sdk 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", ] [[package]] name = "test_swap" version = "20.2.0" dependencies = [ - "soroban-sdk", + "soroban-sdk 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", ] [[package]] name = "test_token" version = "20.2.0" dependencies = [ - "soroban-sdk", + "soroban-sdk 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", "soroban-token-sdk", ] @@ -2967,7 +3000,7 @@ dependencies = [ name = "test_udt" version = "20.2.0" dependencies = [ - "soroban-sdk", + "soroban-sdk 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", ] [[package]] @@ -2987,7 +3020,7 @@ checksum = "268026685b2be38d7103e9e507c938a1fcb3d7e6eb15e87870b617bf37b6d581" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn", ] [[package]] @@ -3090,7 +3123,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn", ] [[package]] @@ -3195,7 +3228,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn", ] [[package]] @@ -3270,12 +3303,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "unicode-width" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" - [[package]] name = "untrusted" version = "0.9.0" @@ -3372,7 +3399,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.39", + "syn", "wasm-bindgen-shared", ] @@ -3394,7 +3421,7 @@ checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3406,50 +3433,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" [[package]] -name = "wasm-opt" -version = "0.114.2" +name = "wasmi_arena" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "effbef3bd1dde18acb401f73e740a6f3d4a1bc651e9773bddc512fe4d8d68f67" -dependencies = [ - "anyhow", - "libc", - "strum", - "strum_macros", - "tempfile", - "thiserror", - "wasm-opt-cxx-sys", - "wasm-opt-sys", -] +checksum = "401c1f35e413fac1846d4843745589d9ec678977ab35a384db8ae7830525d468" [[package]] -name = "wasm-opt-cxx-sys" -version = "0.114.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c09e24eb283919ace2ed5733bda4842a59ce4c8de110ef5c6d98859513d17047" -dependencies = [ - "anyhow", - "cxx", - "cxx-build", - "wasm-opt-sys", -] +name = "wasmi_arena" +version = "0.4.0" +source = "git+https://github.com/stellar/wasmi?rev=ab29800224d85ee64d4ac127bac84cdbb0276721#ab29800224d85ee64d4ac127bac84cdbb0276721" [[package]] -name = "wasm-opt-sys" -version = "0.114.2" +name = "wasmi_core" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f2f817bed2e8d65eb779fa37317e74de15585751f903c9118342d1970703a4" +checksum = "dcf1a7db34bff95b85c261002720c00c3a6168256dcb93041d3fa2054d19856a" dependencies = [ - "anyhow", - "cc", - "cxx", - "cxx-build", + "downcast-rs", + "libm", + "num-traits", + "paste", ] -[[package]] -name = "wasmi_arena" -version = "0.4.0" -source = "git+https://github.com/stellar/wasmi?rev=ab29800224d85ee64d4ac127bac84cdbb0276721#ab29800224d85ee64d4ac127bac84cdbb0276721" - [[package]] name = "wasmi_core" version = "0.13.0" @@ -3690,5 +3695,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn", ] diff --git a/Cargo.toml b/Cargo.toml index 9da4ca95..73dc70c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,12 @@ [workspace] resolver = "2" members = [ - "cmd/soroban-cli", "cmd/crates/*", "cmd/crates/soroban-test/tests/fixtures/test-wasms/*", "cmd/crates/soroban-test/tests/fixtures/hello", "cmd/soroban-rpc/lib/preflight", ] -default-members = ["cmd/soroban-cli", "cmd/crates/soroban-test"] +default-members = ["cmd/crates/soroban-test"] exclude = ["cmd/crates/soroban-test/tests/fixtures/hello"] [workspace.package] @@ -53,10 +52,6 @@ version = "=20.1.0" git = "https://github.com/stellar/rs-soroban-sdk" rev = "e6c2c900ab82b5f6eec48f69cb2cb519e19819cb" -[workspace.dependencies.soroban-cli] -version = "20.2.0" -path = "cmd/soroban-cli" - [workspace.dependencies.stellar-xdr] version = "=20.0.2" default-features = true @@ -80,6 +75,7 @@ wasmparser = "0.90.0" soroban-spec-json = "20.2.0" soroban-spec-tools = "20.2.0" soroban-spec-typescript = "20.2.0" +soroban-cli = "20.2.0" # [patch."https://github.com/stellar/rs-soroban-env"] diff --git a/Makefile b/Makefile index 23187151..fdc88efb 100644 --- a/Makefile +++ b/Makefile @@ -41,7 +41,6 @@ Cargo.lock: Cargo.toml cargo update --workspace install_rust: Cargo.lock - cargo install --path ./cmd/soroban-cli --debug cargo install --path ./cmd/crates/soroban-test/tests/fixtures/hello --root ./target --debug --quiet install: install_rust build-libpreflight diff --git a/cmd/soroban-cli/Cargo.toml b/cmd/soroban-cli/Cargo.toml deleted file mode 100644 index ad60c827..00000000 --- a/cmd/soroban-cli/Cargo.toml +++ /dev/null @@ -1,106 +0,0 @@ -[package] -name = "soroban-cli" -description = "Soroban CLI" -homepage = "https://github.com/stellar/soroban-cli" -repository = "https://github.com/stellar/soroban-cli" -authors = ["Stellar Development Foundation "] -license = "Apache-2.0" -readme = "README.md" -version = "20.2.0" -edition = "2021" -rust-version.workspace = true -autobins = false -default-run = "soroban" - -[[bin]] -name = "soroban" -path = "src/bin/main.rs" - -[package.metadata.binstall] -pkg-url = "{ repo }/releases/download/v{ version }/{ name }-{ version }-{ target }{ archive-suffix }" -bin-dir = "{ bin }{ binary-ext }" - -[[bin]] -name = "doc-gen" -path = "src/bin/doc-gen.rs" -required-features = ["clap-markdown"] - -[lib] -name = "soroban_cli" -path = "src/lib.rs" -doctest = false - -[features] -default = [] -opt = ["dep:wasm-opt"] - -[dependencies] -stellar-xdr = { workspace = true, features = ["cli"] } -soroban-env-host = { workspace = true } -soroban-spec = { workspace = true } -soroban-spec-json = { workspace = true } -soroban-spec-rust = { workspace = true } -soroban-spec-tools = { workspace = true } -soroban-spec-typescript = { workspace = true } -soroban-ledger-snapshot = { workspace = true } -stellar-strkey = { workspace = true } -soroban-sdk = { workspace = true } -clap = { version = "4.1.8", features = [ - "derive", - "env", - "deprecated", - "string", -] } -base64 = { workspace = true } -thiserror = { workspace = true } -serde = "1.0.82" -serde_derive = "1.0.82" -serde_json = "1.0.82" -serde-aux = "4.1.2" -hex = { workspace = true } -num-bigint = "0.4" -tokio = { version = "1", features = ["full"] } -termcolor = "1.1.3" -termcolor_output = "1.0.1" -clap_complete = "4.1.4" -rand = "0.8.5" -wasmparser = { workspace = true } -sha2 = { workspace = true } -csv = "1.1.6" -ed25519-dalek = "=2.0.0" -jsonrpsee-http-client = "0.20.1" -jsonrpsee-core = "0.20.1" -hyper = "0.14.27" -hyper-tls = "0.5" -http = "0.2.9" -regex = "1.6.0" -wasm-opt = { version = "0.114.0", optional = true } -chrono = "0.4.27" -rpassword = "7.2.0" -dirs = "4.0.0" -toml = "0.5.9" -itertools = "0.10.5" -shlex = "1.1.0" -sep5 = { workspace = true } -ethnum = { workspace = true } -clap-markdown = { version = "0.1.3", optional = true } -which = { workspace = true, features = ["regex"] } -strsim = "0.10.0" -heck = "0.4.1" -tracing = { workspace = true } -tracing-appender = { workspace = true } -tracing-subscriber = { workspace = true, features = ["env-filter"] } -cargo_metadata = "0.15.4" -pathdiff = "0.2.1" -dotenvy = "0.15.7" -# For hyper-tls -[target.'cfg(unix)'.dependencies] -openssl = { version = "0.10.55", features = ["vendored"] } - -[build-dependencies] -crate-git-revision = "0.0.4" - -[dev-dependencies] -assert_cmd = "2.0.4" -assert_fs = "1.0.7" -predicates = "2.1.5" diff --git a/cmd/soroban-cli/README.md b/cmd/soroban-cli/README.md deleted file mode 100644 index d17261b1..00000000 --- a/cmd/soroban-cli/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# soroban-cli - -CLI for running Soroban contracts locally in a test VM. Executes WASM files built using the [rs-soroban-sdk](https://github.com/stellar/rs-soroban-sdk). - -Soroban: https://soroban.stellar.org - -## Install - -``` -cargo install --locked soroban-cli -``` - -To install with the `opt` feature, which includes a WASM optimization feature and wasm-opt built in: - -``` -cargo install --locked soroban-cli --features opt -``` - -## Usage - -Can invoke a contract method as a subcommand with different arguments. Anything after the slop (`--`) is passed to the contract's CLI. You can use `--help` to learn about which methods are available and what their arguments are including an example of the type of the input. - -## Example - -``` -soroban invoke --id --wasm -- --help -soroban invoke --id --network futurenet -- --help -``` diff --git a/cmd/soroban-cli/build.rs b/cmd/soroban-cli/build.rs deleted file mode 100644 index b6e6dd92..00000000 --- a/cmd/soroban-cli/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - crate_git_revision::init(); -} diff --git a/cmd/soroban-cli/src/bin/doc-gen.rs b/cmd/soroban-cli/src/bin/doc-gen.rs deleted file mode 100644 index 096f9681..00000000 --- a/cmd/soroban-cli/src/bin/doc-gen.rs +++ /dev/null @@ -1,36 +0,0 @@ -use std::{ - env, fs, - path::{Path, PathBuf}, -}; - -type DynError = Box; - -fn main() -> Result<(), DynError> { - doc_gen()?; - Ok(()) -} - -fn doc_gen() -> std::io::Result<()> { - let out_dir = docs_dir(); - - fs::create_dir_all(out_dir.clone())?; - - std::fs::write( - out_dir.join("soroban-cli-full-docs.md"), - clap_markdown::help_markdown::(), - )?; - - Ok(()) -} - -fn project_root() -> PathBuf { - Path::new(&env!("CARGO_MANIFEST_DIR")) - .ancestors() - .nth(2) - .unwrap() - .to_path_buf() -} - -fn docs_dir() -> PathBuf { - project_root().join("docs") -} diff --git a/cmd/soroban-cli/src/bin/main.rs b/cmd/soroban-cli/src/bin/main.rs deleted file mode 100644 index 7a87099c..00000000 --- a/cmd/soroban-cli/src/bin/main.rs +++ /dev/null @@ -1,51 +0,0 @@ -use clap::CommandFactory; -use dotenvy::dotenv; -use tracing_subscriber::{fmt, EnvFilter}; - -use soroban_cli::{commands, Root}; - -#[tokio::main] -async fn main() { - let _ = dotenv().unwrap_or_default(); - let mut root = Root::new().unwrap_or_else(|e| match e { - commands::Error::Clap(e) => { - let mut cmd = Root::command(); - e.format(&mut cmd).exit(); - } - e => { - eprintln!("{e}"); - std::process::exit(1); - } - }); - // Now use root to setup the logger - if let Some(level) = root.global_args.log_level() { - let mut e_filter = EnvFilter::from_default_env() - .add_directive("hyper=off".parse().unwrap()) - .add_directive(format!("soroban_cli={level}").parse().unwrap()); - - for filter in &root.global_args.filter_logs { - e_filter = e_filter.add_directive( - filter - .parse() - .map_err(|e| { - eprintln!("{e}: {filter}"); - std::process::exit(1); - }) - .unwrap(), - ); - } - - let builder = fmt::Subscriber::builder() - .with_env_filter(e_filter) - .with_writer(std::io::stderr); - - let subscriber = builder.finish(); - tracing::subscriber::set_global_default(subscriber) - .expect("Failed to set the global tracing subscriber"); - } - - if let Err(e) = root.run().await { - eprintln!("error: {e}"); - std::process::exit(1); - } -} diff --git a/cmd/soroban-cli/src/commands/completion.rs b/cmd/soroban-cli/src/commands/completion.rs deleted file mode 100644 index f64386b4..00000000 --- a/cmd/soroban-cli/src/commands/completion.rs +++ /dev/null @@ -1,32 +0,0 @@ -use clap::{arg, CommandFactory, Parser}; -use clap_complete::{generate, Shell}; -use std::io; - -use crate::commands::Root; - -pub const LONG_ABOUT: &str = "\ -Print shell completion code for the specified shell - -Ensure the completion package for your shell is installed, -e.g., bash-completion for bash. - -To enable autocomplete in the current bash shell, run: - source <(soroban completion --shell bash) - -To enable autocomplete permanently, run: - echo \"source <(soroban completion --shell bash)\" >> ~/.bashrc"; - -#[derive(Parser, Debug, Clone)] -#[group(skip)] -pub struct Cmd { - /// The shell type - #[arg(long, value_enum)] - shell: Shell, -} - -impl Cmd { - pub fn run(&self) { - let cmd = &mut Root::command(); - generate(self.shell, cmd, "soroban", &mut io::stdout()); - } -} diff --git a/cmd/soroban-cli/src/commands/config/locator.rs b/cmd/soroban-cli/src/commands/config/locator.rs deleted file mode 100644 index 2688b043..00000000 --- a/cmd/soroban-cli/src/commands/config/locator.rs +++ /dev/null @@ -1,358 +0,0 @@ -use clap::arg; -use serde::de::DeserializeOwned; -use std::{ - ffi::OsStr, - fmt::Display, - fs, io, - path::{Path, PathBuf}, - str::FromStr, -}; - -use crate::{utils::find_config_dir, Pwd}; - -use super::{network::Network, secret::Secret}; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("Failed to find home directory")] - HomeDirNotFound, - #[error("Failed read current directory")] - CurrentDirNotFound, - #[error("Failed read current directory and no SOROBAN_CONFIG_HOME is set")] - NoConfigEnvVar, - #[error("Failed to create directory: {path:?}")] - DirCreationFailed { path: PathBuf }, - #[error( - "Failed to read secret's file: {path}.\nProbably need to use `soroban config identity add`" - )] - SecretFileRead { path: PathBuf }, - #[error( - "Failed to read network file: {path};\nProbably need to use `soroban config network add`" - )] - NetworkFileRead { path: PathBuf }, - #[error(transparent)] - Toml(#[from] toml::de::Error), - #[error("Seceret file failed to deserialize")] - Deserialization, - #[error("Failed to write identity file:{filepath}: {error}")] - IdCreationFailed { filepath: PathBuf, error: io::Error }, - #[error("Seceret file failed to deserialize")] - NetworkDeserialization, - #[error("Failed to write network file: {0}")] - NetworkCreationFailed(std::io::Error), - #[error("Error Identity directory is invalid: {name}")] - IdentityList { name: String }, - // #[error("Config file failed to deserialize")] - // CannotReadConfigFile, - #[error("Config file failed to serialize")] - ConfigSerialization, - // #[error("Config file failed write")] - // CannotWriteConfigFile, - #[error("XDG_CONFIG_HOME env variable is not a valid path. Got {0}")] - XdgConfigHome(String), - #[error(transparent)] - Io(#[from] std::io::Error), - #[error("Failed to remove {0}: {1}")] - ConfigRemoval(String, String), - #[error("Failed to find config {0} for {1}")] - ConfigMissing(String, String), - #[error(transparent)] - String(#[from] std::string::FromUtf8Error), - #[error(transparent)] - Secret(#[from] crate::commands::config::secret::Error), -} - -#[derive(Debug, clap::Args, Default, Clone)] -#[group(skip)] -pub struct Args { - /// Use global config - #[arg(long)] - pub global: bool, - - /// Location of config directory, default is "." - #[arg(long, help_heading = "TESTING_OPTIONS")] - pub config_dir: Option, -} - -pub enum Location { - Local(PathBuf), - Global(PathBuf), -} - -impl Display for Location { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{} {:?}", - match self { - Location::Local(_) => "Local", - Location::Global(_) => "Global", - }, - self.as_ref().parent().unwrap().parent().unwrap() - ) - } -} - -impl AsRef for Location { - fn as_ref(&self) -> &Path { - match self { - Location::Local(p) | Location::Global(p) => p.as_path(), - } - } -} - -impl Location { - #[must_use] - pub fn wrap(&self, p: PathBuf) -> Self { - match self { - Location::Local(_) => Location::Local(p), - Location::Global(_) => Location::Global(p), - } - } -} - -impl Args { - pub fn config_dir(&self) -> Result { - if self.global { - global_config_path() - } else { - self.local_config() - } - } - - pub fn local_and_global(&self) -> Result<[Location; 2], Error> { - Ok([ - Location::Local(self.local_config()?), - Location::Global(global_config_path()?), - ]) - } - - pub fn local_config(&self) -> Result { - let pwd = self.current_dir()?; - Ok(find_config_dir(pwd.clone()).unwrap_or_else(|_| pwd.join(".soroban"))) - } - - pub fn current_dir(&self) -> Result { - self.config_dir.as_ref().map_or_else( - || std::env::current_dir().map_err(|_| Error::CurrentDirNotFound), - |pwd| Ok(pwd.clone()), - ) - } - - pub fn write_identity(&self, name: &str, secret: &Secret) -> Result<(), Error> { - KeyType::Identity.write(name, secret, &self.config_dir()?) - } - - pub fn write_network(&self, name: &str, network: &Network) -> Result<(), Error> { - KeyType::Network.write(name, network, &self.config_dir()?) - } - - pub fn list_identities(&self) -> Result, Error> { - Ok(KeyType::Identity - .list_paths(&self.local_and_global()?)? - .into_iter() - .map(|(name, _)| name) - .collect()) - } - - pub fn list_identities_long(&self) -> Result, Error> { - Ok(KeyType::Identity - .list_paths(&self.local_and_global()?) - .into_iter() - .flatten() - .map(|(name, location)| { - let path = match location { - Location::Local(path) | Location::Global(path) => path, - }; - (name, format!("{}", path.display())) - }) - .collect()) - } - - pub fn list_networks(&self) -> Result, Error> { - Ok(KeyType::Network - .list_paths(&self.local_and_global()?) - .into_iter() - .flatten() - .map(|x| x.0) - .collect()) - } - - pub fn list_networks_long(&self) -> Result, Error> { - Ok(KeyType::Network - .list_paths(&self.local_and_global()?) - .into_iter() - .flatten() - .filter_map(|(name, location)| { - Some(( - name, - KeyType::read_from_path::(location.as_ref()).ok()?, - location, - )) - }) - .collect::>()) - } - pub fn read_identity(&self, name: &str) -> Result { - KeyType::Identity.read_with_global(name, &self.local_config()?) - } - - pub fn read_network(&self, name: &str) -> Result { - let res = KeyType::Network.read_with_global(name, &self.local_config()?); - if let Err(Error::ConfigMissing(_, _)) = &res { - if name == "futurenet" { - let network = Network::futurenet(); - self.write_network(name, &network)?; - return Ok(network); - } - } - res - } - - pub fn remove_identity(&self, name: &str) -> Result<(), Error> { - KeyType::Identity.remove(name, &self.config_dir()?) - } - - pub fn remove_network(&self, name: &str) -> Result<(), Error> { - KeyType::Network.remove(name, &self.config_dir()?) - } -} - -fn ensure_directory(dir: PathBuf) -> Result { - let parent = dir.parent().ok_or(Error::HomeDirNotFound)?; - std::fs::create_dir_all(parent).map_err(|_| dir_creation_failed(parent))?; - Ok(dir) -} - -fn dir_creation_failed(p: &Path) -> Error { - Error::DirCreationFailed { - path: p.to_path_buf(), - } -} - -fn read_dir(dir: &Path) -> Result, Error> { - let contents = std::fs::read_dir(dir)?; - let mut res = vec![]; - for entry in contents.filter_map(Result::ok) { - let path = entry.path(); - if let Some("toml") = path.extension().and_then(OsStr::to_str) { - if let Some(os_str) = path.file_stem() { - res.push((os_str.to_string_lossy().trim().to_string(), path)); - } - } - } - res.sort(); - Ok(res) -} - -pub enum KeyType { - Identity, - Network, -} - -impl Display for KeyType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match self { - KeyType::Identity => "identity", - KeyType::Network => "network", - } - ) - } -} - -impl KeyType { - pub fn read(&self, key: &str, pwd: &Path) -> Result { - let path = self.path(pwd, key); - Self::read_from_path(&path) - } - - pub fn read_from_path(path: &Path) -> Result { - let data = fs::read(path).map_err(|_| Error::NetworkFileRead { - path: path.to_path_buf(), - })?; - let res = toml::from_slice(data.as_slice()); - Ok(res?) - } - - pub fn read_with_global(&self, key: &str, pwd: &Path) -> Result { - for path in [pwd, global_config_path()?.as_path()] { - match self.read(key, path) { - Ok(t) => return Ok(t), - _ => continue, - } - } - Err(Error::ConfigMissing(self.to_string(), key.to_string())) - } - - pub fn write( - &self, - key: &str, - value: &T, - pwd: &Path, - ) -> Result<(), Error> { - let filepath = ensure_directory(self.path(pwd, key))?; - let data = toml::to_string(value).map_err(|_| Error::ConfigSerialization)?; - std::fs::write(&filepath, data).map_err(|error| Error::IdCreationFailed { filepath, error }) - } - - fn root(&self, pwd: &Path) -> PathBuf { - pwd.join(self.to_string()) - } - - fn path(&self, pwd: &Path, key: &str) -> PathBuf { - let mut path = self.root(pwd).join(key); - path.set_extension("toml"); - path - } - - pub fn list_paths(&self, paths: &[Location]) -> Result, Error> { - Ok(paths - .iter() - .flat_map(|p| self.list(p).unwrap_or_default()) - .collect()) - } - - pub fn list(&self, pwd: &Location) -> Result, Error> { - let path = self.root(pwd.as_ref()); - if path.exists() { - let mut files = read_dir(&path)?; - files.sort(); - - Ok(files - .into_iter() - .map(|(name, p)| (name, pwd.wrap(p))) - .collect()) - } else { - Ok(vec![]) - } - } - - pub fn remove(&self, key: &str, pwd: &Path) -> Result<(), Error> { - let path = self.path(pwd, key); - if path.exists() { - std::fs::remove_file(&path) - .map_err(|_| Error::ConfigRemoval(self.to_string(), key.to_string())) - } else { - Ok(()) - } - } -} - -fn global_config_path() -> Result { - Ok(if let Ok(config_home) = std::env::var("XDG_CONFIG_HOME") { - PathBuf::from_str(&config_home).map_err(|_| Error::XdgConfigHome(config_home))? - } else { - dirs::home_dir() - .ok_or(Error::HomeDirNotFound)? - .join(".config") - } - .join("soroban")) -} - -impl Pwd for Args { - fn set_pwd(&mut self, pwd: &Path) { - self.config_dir = Some(pwd.to_path_buf()); - } -} diff --git a/cmd/soroban-cli/src/commands/config/mod.rs b/cmd/soroban-cli/src/commands/config/mod.rs deleted file mode 100644 index be76e77f..00000000 --- a/cmd/soroban-cli/src/commands/config/mod.rs +++ /dev/null @@ -1,95 +0,0 @@ -use std::path::PathBuf; - -use clap::{arg, command, Parser}; -use serde::{Deserialize, Serialize}; - -use crate::Pwd; - -use self::{network::Network, secret::Secret}; - -use super::{keys, network}; - -pub mod locator; -pub mod secret; - -#[derive(Debug, Parser)] -pub enum Cmd { - /// Configure different networks. Depraecated, use `soroban network` instead. - #[command(subcommand)] - Network(network::Cmd), - /// Identity management. Deprecated, use `soroban keys` instead. - #[command(subcommand)] - Identity(keys::Cmd), -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Identity(#[from] keys::Error), - #[error(transparent)] - Network(#[from] network::Error), - #[error(transparent)] - Secret(#[from] secret::Error), - #[error(transparent)] - Config(#[from] locator::Error), -} - -impl Cmd { - pub async fn run(&self) -> Result<(), Error> { - match &self { - Cmd::Identity(identity) => identity.run().await?, - Cmd::Network(network) => network.run()?, - } - Ok(()) - } -} - -#[derive(Debug, clap::Args, Clone, Default)] -#[group(skip)] -pub struct Args { - #[command(flatten)] - pub network: network::Args, - - #[arg(long, visible_alias = "source", env = "SOROBAN_ACCOUNT")] - /// Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). Default: `identity generate --default-seed` - pub source_account: String, - - #[arg(long)] - /// If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - pub hd_path: Option, - - #[command(flatten)] - pub locator: locator::Args, -} - -impl Args { - pub fn key_pair(&self) -> Result { - let key = self.account(&self.source_account)?; - Ok(key.key_pair(self.hd_path)?) - } - - pub fn account(&self, account_str: &str) -> Result { - if let Ok(secret) = self.locator.read_identity(account_str) { - Ok(secret) - } else { - Ok(account_str.parse::()?) - } - } - - pub fn get_network(&self) -> Result { - Ok(self.network.get(&self.locator)?) - } - - pub fn config_dir(&self) -> Result { - Ok(self.locator.config_dir()?) - } -} - -impl Pwd for Args { - fn set_pwd(&mut self, pwd: &std::path::Path) { - self.locator.set_pwd(pwd); - } -} - -#[derive(Default, Serialize, Deserialize)] -pub struct Config {} diff --git a/cmd/soroban-cli/src/commands/config/secret.rs b/cmd/soroban-cli/src/commands/config/secret.rs deleted file mode 100644 index 4684e2a8..00000000 --- a/cmd/soroban-cli/src/commands/config/secret.rs +++ /dev/null @@ -1,143 +0,0 @@ -use clap::arg; -use serde::{Deserialize, Serialize}; -use std::{io::Write, str::FromStr}; -use stellar_strkey::ed25519::{PrivateKey, PublicKey}; - -use crate::utils; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("invalid secret key")] - InvalidSecretKey, - // #[error("seed_phrase must be 12 words long, found {len}")] - // InvalidSeedPhrase { len: usize }, - #[error("seceret input error")] - PasswordRead, - #[error(transparent)] - Secret(#[from] stellar_strkey::DecodeError), - #[error(transparent)] - SeedPhrase(#[from] sep5::error::Error), - #[error(transparent)] - Ed25519(#[from] ed25519_dalek::SignatureError), - #[error("Invalid address {0}")] - InvalidAddress(String), -} - -#[derive(Debug, clap::Args, Clone)] -#[group(skip)] -pub struct Args { - /// Add using secret_key - /// Can provide with SOROBAN_SECRET_KEY - #[arg(long, conflicts_with = "seed_phrase")] - pub secret_key: bool, - /// Add using 12 word seed phrase to generate secret_key - #[arg(long, conflicts_with = "secret_key")] - pub seed_phrase: bool, -} - -impl Args { - pub fn read_secret(&self) -> Result { - if let Ok(secret_key) = std::env::var("SOROBAN_SECRET_KEY") { - Ok(Secret::SecretKey { secret_key }) - } else if self.secret_key { - println!("Type a secret key: "); - let secret_key = read_password()?; - let secret_key = PrivateKey::from_string(&secret_key) - .map_err(|_| Error::InvalidSecretKey)? - .to_string(); - Ok(Secret::SecretKey { secret_key }) - } else if self.seed_phrase { - println!("Type a 12 word seed phrase: "); - let seed_phrase = read_password()?; - let seed_phrase: Vec<&str> = seed_phrase.split_whitespace().collect(); - // if seed_phrase.len() != 12 { - // let len = seed_phrase.len(); - // return Err(Error::InvalidSeedPhrase { len }); - // } - Ok(Secret::SeedPhrase { - seed_phrase: seed_phrase - .into_iter() - .map(ToString::to_string) - .collect::>() - .join(" "), - }) - } else { - Err(Error::PasswordRead {}) - } - } -} - -#[derive(Debug, Serialize, Deserialize)] -#[serde(untagged)] -pub enum Secret { - SecretKey { secret_key: String }, - SeedPhrase { seed_phrase: String }, -} - -impl FromStr for Secret { - type Err = Error; - - fn from_str(s: &str) -> Result { - if PrivateKey::from_string(s).is_ok() { - Ok(Secret::SecretKey { - secret_key: s.to_string(), - }) - } else if sep5::SeedPhrase::from_str(s).is_ok() { - Ok(Secret::SeedPhrase { - seed_phrase: s.to_string(), - }) - } else { - Err(Error::InvalidAddress(s.to_string())) - } - } -} - -impl From for Secret { - fn from(value: PrivateKey) -> Self { - Secret::SecretKey { - secret_key: value.to_string(), - } - } -} - -impl Secret { - pub fn private_key(&self, index: Option) -> Result { - Ok(match self { - Secret::SecretKey { secret_key } => PrivateKey::from_string(secret_key)?, - Secret::SeedPhrase { seed_phrase } => sep5::SeedPhrase::from_str(seed_phrase)? - .from_path_index(index.unwrap_or_default(), None)? - .private(), - }) - } - - pub fn public_key(&self, index: Option) -> Result { - let key = self.key_pair(index)?; - Ok(stellar_strkey::ed25519::PublicKey::from_payload( - key.verifying_key().as_bytes(), - )?) - } - - pub fn key_pair(&self, index: Option) -> Result { - Ok(utils::into_signing_key(&self.private_key(index)?)) - } - - pub fn from_seed(seed: Option<&str>) -> Result { - let seed_phrase = if let Some(seed) = seed.map(str::as_bytes) { - sep5::SeedPhrase::from_entropy(seed) - } else { - sep5::SeedPhrase::random(sep5::MnemonicType::Words12) - }? - .seed_phrase - .into_phrase(); - Ok(Secret::SeedPhrase { seed_phrase }) - } - - pub fn test_seed_phrase() -> Result { - Self::from_seed(Some("0000000000000000")) - } -} - -fn read_password() -> Result { - std::io::stdout().flush().map_err(|_| Error::PasswordRead)?; - rpassword::read_password().map_err(|_| Error::PasswordRead) -} diff --git a/cmd/soroban-cli/src/commands/contract/asset.rs b/cmd/soroban-cli/src/commands/contract/asset.rs deleted file mode 100644 index ad7be020..00000000 --- a/cmd/soroban-cli/src/commands/contract/asset.rs +++ /dev/null @@ -1,27 +0,0 @@ -use super::{deploy, id}; - -#[derive(Debug, clap::Subcommand)] -pub enum Cmd { - /// Get Id of builtin Soroban Asset Contract. Deprecated, use `soroban contract id asset` instead - Id(id::asset::Cmd), - /// Deploy builtin Soroban Asset Contract - Deploy(deploy::asset::Cmd), -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Id(#[from] id::asset::Error), - #[error(transparent)] - Deploy(#[from] deploy::asset::Error), -} - -impl Cmd { - pub async fn run(&self) -> Result<(), Error> { - match &self { - Cmd::Id(id) => id.run()?, - Cmd::Deploy(asset) => asset.run().await?, - } - Ok(()) - } -} diff --git a/cmd/soroban-cli/src/commands/contract/bindings.rs b/cmd/soroban-cli/src/commands/contract/bindings.rs deleted file mode 100644 index 1da94697..00000000 --- a/cmd/soroban-cli/src/commands/contract/bindings.rs +++ /dev/null @@ -1,38 +0,0 @@ -pub mod json; -pub mod rust; -pub mod typescript; - -#[derive(Debug, clap::Subcommand)] -pub enum Cmd { - /// Generate Json Bindings - Json(json::Cmd), - - /// Generate Rust bindings - Rust(rust::Cmd), - - /// Generate a TypeScript / JavaScript package - Typescript(typescript::Cmd), -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Json(#[from] json::Error), - - #[error(transparent)] - Rust(#[from] rust::Error), - - #[error(transparent)] - Typescript(#[from] typescript::Error), -} - -impl Cmd { - pub async fn run(&self) -> Result<(), Error> { - match &self { - Cmd::Json(json) => json.run()?, - Cmd::Rust(rust) => rust.run()?, - Cmd::Typescript(ts) => ts.run().await?, - } - Ok(()) - } -} diff --git a/cmd/soroban-cli/src/commands/contract/bindings/json.rs b/cmd/soroban-cli/src/commands/contract/bindings/json.rs deleted file mode 100644 index 060f9064..00000000 --- a/cmd/soroban-cli/src/commands/contract/bindings/json.rs +++ /dev/null @@ -1,29 +0,0 @@ -use std::fmt::Debug; - -use clap::{command, Parser}; -use soroban_spec_json; - -use crate::wasm; - -#[derive(Parser, Debug, Clone)] -#[group(skip)] -pub struct Cmd { - #[command(flatten)] - wasm: wasm::Args, -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("generate json from file: {0}")] - GenerateJsonFromFile(soroban_spec_json::GenerateFromFileError), -} - -impl Cmd { - pub fn run(&self) -> Result<(), Error> { - let wasm_path_str = self.wasm.wasm.to_string_lossy(); - let json = soroban_spec_json::generate_from_file(&wasm_path_str, None) - .map_err(Error::GenerateJsonFromFile)?; - println!("{json}"); - Ok(()) - } -} diff --git a/cmd/soroban-cli/src/commands/contract/bindings/rust.rs b/cmd/soroban-cli/src/commands/contract/bindings/rust.rs deleted file mode 100644 index 176732ec..00000000 --- a/cmd/soroban-cli/src/commands/contract/bindings/rust.rs +++ /dev/null @@ -1,39 +0,0 @@ -use std::fmt::Debug; - -use clap::{command, Parser}; -use soroban_spec_rust::{self, ToFormattedString}; - -use crate::wasm; - -#[derive(Parser, Debug, Clone)] -#[group(skip)] -pub struct Cmd { - #[command(flatten)] - wasm: wasm::Args, -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("generate rust from file: {0}")] - GenerateRustFromFile(soroban_spec_rust::GenerateFromFileError), - #[error("format rust error: {0}")] - FormatRust(String), -} - -impl Cmd { - pub fn run(&self) -> Result<(), Error> { - let wasm_path_str = self.wasm.wasm.to_string_lossy(); - let code = soroban_spec_rust::generate_from_file(&wasm_path_str, None) - .map_err(Error::GenerateRustFromFile)?; - match code.to_formatted_string() { - Ok(formatted) => { - println!("{formatted}"); - Ok(()) - } - Err(e) => { - println!("{code}"); - Err(Error::FormatRust(e.to_string())) - } - } - } -} diff --git a/cmd/soroban-cli/src/commands/contract/bindings/typescript.rs b/cmd/soroban-cli/src/commands/contract/bindings/typescript.rs deleted file mode 100644 index 19c7eecd..00000000 --- a/cmd/soroban-cli/src/commands/contract/bindings/typescript.rs +++ /dev/null @@ -1,131 +0,0 @@ -use std::{ffi::OsString, fmt::Debug, path::PathBuf}; - -use clap::{command, Parser}; -use soroban_spec_typescript::{self as typescript, boilerplate::Project}; - -use crate::wasm; -use crate::{ - commands::{ - config::locator, - contract::{self, fetch}, - network::{self, Network}, - }, - utils::contract_spec::{self, ContractSpec}, -}; - -#[derive(Parser, Debug, Clone)] -#[group(skip)] -pub struct Cmd { - /// Path to optional wasm binary - #[arg(long)] - pub wasm: Option, - /// Where to place generated project - #[arg(long)] - output_dir: PathBuf, - /// Whether to overwrite output directory if it already exists - #[arg(long)] - overwrite: bool, - /// The contract ID/address on the network - #[arg(long, visible_alias = "id")] - contract_id: String, - #[command(flatten)] - locator: locator::Args, - #[command(flatten)] - network: network::Args, -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("failed generate TS from file: {0}")] - GenerateTSFromFile(typescript::GenerateFromFileError), - #[error(transparent)] - Io(#[from] std::io::Error), - - #[error("--output-dir cannot be a file: {0:?}")] - IsFile(PathBuf), - - #[error("--output-dir already exists and you did not specify --overwrite: {0:?}")] - OutputDirExists(PathBuf), - - #[error("--output-dir filepath not representable as utf-8: {0:?}")] - NotUtf8(OsString), - - #[error(transparent)] - Network(#[from] network::Error), - - #[error(transparent)] - Locator(#[from] locator::Error), - #[error(transparent)] - Fetch(#[from] fetch::Error), - #[error(transparent)] - Spec(#[from] contract_spec::Error), - #[error(transparent)] - Wasm(#[from] wasm::Error), - #[error("Failed to get file name from path: {0:?}")] - FailedToGetFileName(PathBuf), -} - -impl Cmd { - pub async fn run(&self) -> Result<(), Error> { - let spec = if let Some(wasm) = &self.wasm { - let wasm: wasm::Args = wasm.into(); - wasm.parse()?.spec - } else { - let fetch = contract::fetch::Cmd { - contract_id: self.contract_id.clone(), - out_file: None, - locator: self.locator.clone(), - network: self.network.clone(), - }; - let bytes = fetch.get_bytes().await?; - ContractSpec::new(&bytes)?.spec - }; - if self.output_dir.is_file() { - return Err(Error::IsFile(self.output_dir.clone())); - } - if self.output_dir.exists() { - if self.overwrite { - std::fs::remove_dir_all(&self.output_dir)?; - } else { - return Err(Error::OutputDirExists(self.output_dir.clone())); - } - } - std::fs::create_dir_all(&self.output_dir)?; - let p: Project = self.output_dir.clone().try_into()?; - let Network { - rpc_url, - network_passphrase, - .. - } = self - .network - .get(&self.locator) - .ok() - .unwrap_or_else(Network::futurenet); - let absolute_path = self.output_dir.canonicalize()?; - let file_name = absolute_path - .file_name() - .ok_or_else(|| Error::FailedToGetFileName(absolute_path.clone()))?; - let contract_name = &file_name - .to_str() - .ok_or_else(|| Error::NotUtf8(file_name.to_os_string()))?; - p.init( - contract_name, - &self.contract_id, - &rpc_url, - &network_passphrase, - &spec, - )?; - std::process::Command::new("npm") - .arg("install") - .current_dir(&self.output_dir) - .spawn()? - .wait()?; - std::process::Command::new("npm") - .arg("run") - .arg("build") - .current_dir(&self.output_dir) - .spawn()? - .wait()?; - Ok(()) - } -} diff --git a/cmd/soroban-cli/src/commands/contract/build.rs b/cmd/soroban-cli/src/commands/contract/build.rs deleted file mode 100644 index ba17bd1b..00000000 --- a/cmd/soroban-cli/src/commands/contract/build.rs +++ /dev/null @@ -1,194 +0,0 @@ -use clap::Parser; -use itertools::Itertools; -use std::{ - collections::HashSet, - env, - ffi::OsStr, - fmt::Debug, - fs, io, - path::Path, - process::{Command, ExitStatus, Stdio}, -}; - -use cargo_metadata::{Metadata, MetadataCommand, Package}; - -/// Build a contract from source -/// -/// Builds all crates that are referenced by the cargo manifest (Cargo.toml) -/// that have cdylib as their crate-type. Crates are built for the wasm32 -/// target. Unless configured otherwise, crates are built with their default -/// features and with their release profile. -/// -/// To view the commands that will be executed, without executing them, use the -/// --print-commands-only option. -#[derive(Parser, Debug, Clone)] -pub struct Cmd { - /// Path to Cargo.toml - #[arg(long, default_value = "Cargo.toml")] - pub manifest_path: std::path::PathBuf, - /// Package to build - /// - /// If omitted, all packages that build for crate-type cdylib are built. - #[arg(long)] - pub package: Option, - /// Build with the specified profile - #[arg(long, default_value = "release")] - pub profile: String, - /// Build with the list of features activated, space or comma separated - #[arg(long, help_heading = "Features")] - pub features: Option, - /// Build with the all features activated - #[arg( - long, - conflicts_with = "features", - conflicts_with = "no_default_features", - help_heading = "Features" - )] - pub all_features: bool, - /// Build with the default feature not activated - #[arg(long, help_heading = "Features")] - pub no_default_features: bool, - /// Directory to copy wasm files to - /// - /// If provided, wasm files can be found in the cargo target directory, and - /// the specified directory. - /// - /// If ommitted, wasm files are written only to the cargo target directory. - #[arg(long)] - pub out_dir: Option, - /// Print commands to build without executing them - #[arg(long, conflicts_with = "out_dir", help_heading = "Other")] - pub print_commands_only: bool, -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Metadata(#[from] cargo_metadata::Error), - #[error(transparent)] - CargoCmd(io::Error), - #[error("exit status {0}")] - Exit(ExitStatus), - #[error("package {package} not found")] - PackageNotFound { package: String }, - #[error("creating out directory: {0}")] - CreatingOutDir(io::Error), - #[error("copying wasm file: {0}")] - CopyingWasmFile(io::Error), - #[error("getting the current directory: {0}")] - GettingCurrentDir(io::Error), -} - -impl Cmd { - pub fn run(&self) -> Result<(), Error> { - let working_dir = env::current_dir().map_err(Error::GettingCurrentDir)?; - - let metadata = self.metadata()?; - let packages = self.packages(&metadata); - let target_dir = &metadata.target_directory; - - if let Some(package) = &self.package { - if packages.is_empty() { - return Err(Error::PackageNotFound { - package: package.clone(), - }); - } - } - - for p in packages { - let mut cmd = Command::new("cargo"); - cmd.stdout(Stdio::piped()); - cmd.arg("rustc"); - let manifest_path = pathdiff::diff_paths(&p.manifest_path, &working_dir) - .unwrap_or(p.manifest_path.clone().into()); - cmd.arg(format!( - "--manifest-path={}", - manifest_path.to_string_lossy() - )); - cmd.arg("--crate-type=cdylib"); - cmd.arg("--target=wasm32-unknown-unknown"); - if self.profile == "release" { - cmd.arg("--release"); - } else { - cmd.arg(format!("--profile={}", self.profile)); - } - if self.all_features { - cmd.arg("--all-features"); - } - if self.no_default_features { - cmd.arg("--no-default-features"); - } - if let Some(features) = self.features() { - let requested: HashSet = features.iter().cloned().collect(); - let available = p.features.iter().map(|f| f.0).cloned().collect(); - let activate = requested.intersection(&available).join(","); - if !activate.is_empty() { - cmd.arg(format!("--features={activate}")); - } - } - let cmd_str = format!( - "cargo {}", - cmd.get_args().map(OsStr::to_string_lossy).join(" ") - ); - - if self.print_commands_only { - println!("{cmd_str}"); - } else { - eprintln!("{cmd_str}"); - let status = cmd.status().map_err(Error::CargoCmd)?; - if !status.success() { - return Err(Error::Exit(status)); - } - - if let Some(out_dir) = &self.out_dir { - fs::create_dir_all(out_dir).map_err(Error::CreatingOutDir)?; - - let file = format!("{}.wasm", p.name.replace('-', "_")); - let target_file_path = Path::new(target_dir) - .join("wasm32-unknown-unknown") - .join(&self.profile) - .join(&file); - let out_file_path = Path::new(out_dir).join(&file); - fs::copy(target_file_path, out_file_path).map_err(Error::CopyingWasmFile)?; - } - } - } - - Ok(()) - } - - fn features(&self) -> Option> { - self.features - .as_ref() - .map(|f| f.split(&[',', ' ']).map(String::from).collect()) - } - - fn packages(&self, metadata: &Metadata) -> Vec { - metadata - .packages - .iter() - .filter(|p| - // Filter by the package name if one is provided. - self.package.is_none() || Some(&p.name) == self.package.as_ref()) - .filter(|p| { - // Filter crates by those that build to cdylib (wasm), unless a - // package is provided. - self.package.is_some() - || p.targets - .iter() - .any(|t| t.crate_types.iter().any(|c| c == "cdylib")) - }) - .cloned() - .collect() - } - - fn metadata(&self) -> Result { - let mut cmd = MetadataCommand::new(); - cmd.no_deps(); - cmd.manifest_path(&self.manifest_path); - // Do not configure features on the metadata command, because we are - // only collecting non-dependency metadata, features have no impact on - // the output. - cmd.exec() - } -} diff --git a/cmd/soroban-cli/src/commands/contract/deploy.rs b/cmd/soroban-cli/src/commands/contract/deploy.rs deleted file mode 100644 index 9baf4459..00000000 --- a/cmd/soroban-cli/src/commands/contract/deploy.rs +++ /dev/null @@ -1,28 +0,0 @@ -pub mod asset; -pub mod wasm; - -#[derive(Debug, clap::Subcommand)] -pub enum Cmd { - /// Deploy builtin Soroban Asset Contract - Asset(asset::Cmd), - /// Deploy normal Wasm Contract - Wasm(wasm::Cmd), -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Asset(#[from] asset::Error), - #[error(transparent)] - Wasm(#[from] wasm::Error), -} - -impl Cmd { - pub async fn run(&self) -> Result<(), Error> { - match &self { - Cmd::Asset(asset) => asset.run().await?, - Cmd::Wasm(wasm) => wasm.run().await?, - } - Ok(()) - } -} diff --git a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs deleted file mode 100644 index c10bf816..00000000 --- a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs +++ /dev/null @@ -1,155 +0,0 @@ -use clap::{arg, command, Parser}; -use soroban_env_host::{ - xdr::{ - Asset, ContractDataDurability, ContractExecutable, ContractIdPreimage, CreateContractArgs, - Error as XdrError, Hash, HostFunction, InvokeHostFunctionOp, LedgerKey::ContractData, - LedgerKeyContractData, Memo, MuxedAccount, Operation, OperationBody, Preconditions, - ScAddress, ScVal, SequenceNumber, Transaction, TransactionExt, Uint256, VecM, - }, - HostError, -}; -use std::convert::Infallible; -use std::{array::TryFromSliceError, fmt::Debug, num::ParseIntError}; - -use crate::{ - commands::config, - rpc::{Client, Error as SorobanRpcError}, - utils::{contract_id_hash_from_asset, parsing::parse_asset}, -}; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - // TODO: the Display impl of host errors is pretty user-unfriendly - // (it just calls Debug). I think we can do better than that - Host(#[from] HostError), - #[error("error parsing int: {0}")] - ParseIntError(#[from] ParseIntError), - #[error(transparent)] - Client(#[from] SorobanRpcError), - #[error("internal conversion error: {0}")] - TryFromSliceError(#[from] TryFromSliceError), - #[error("xdr processing error: {0}")] - Xdr(#[from] XdrError), - #[error(transparent)] - Config(#[from] config::Error), - #[error(transparent)] - ParseAssetError(#[from] crate::utils::parsing::Error), -} - -impl From for Error { - fn from(_: Infallible) -> Self { - unreachable!() - } -} - -#[derive(Parser, Debug, Clone)] -#[group(skip)] -pub struct Cmd { - /// ID of the Stellar classic asset to wrap, e.g. "USDC:G...5" - #[arg(long)] - pub asset: String, - - #[command(flatten)] - pub config: config::Args, - #[command(flatten)] - pub fee: crate::fee::Args, -} - -impl Cmd { - pub async fn run(&self) -> Result<(), Error> { - // Parse asset - let asset = parse_asset(&self.asset)?; - - let res_str = self.run_against_rpc_server(asset).await?; - println!("{res_str}"); - Ok(()) - } - - async fn run_against_rpc_server(&self, asset: Asset) -> Result { - let network = self.config.get_network()?; - let client = Client::new(&network.rpc_url)?; - client - .verify_network_passphrase(Some(&network.network_passphrase)) - .await?; - let key = self.config.key_pair()?; - - // Get the account sequence number - let public_strkey = - stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); - // TODO: use symbols for the method names (both here and in serve) - let account_details = client.get_account(&public_strkey).await?; - let sequence: i64 = account_details.seq_num.into(); - let network_passphrase = &network.network_passphrase; - let contract_id = contract_id_hash_from_asset(&asset, network_passphrase)?; - let tx = build_wrap_token_tx( - &asset, - &contract_id, - sequence + 1, - self.fee.fee, - network_passphrase, - &key, - )?; - - client - .prepare_and_send_transaction(&tx, &key, &[], network_passphrase, None, None) - .await?; - - Ok(stellar_strkey::Contract(contract_id.0).to_string()) - } -} - -fn build_wrap_token_tx( - asset: &Asset, - contract_id: &Hash, - sequence: i64, - fee: u32, - _network_passphrase: &str, - key: &ed25519_dalek::SigningKey, -) -> Result { - let contract = ScAddress::Contract(contract_id.clone()); - let mut read_write = vec![ - ContractData(LedgerKeyContractData { - contract: contract.clone(), - key: ScVal::LedgerKeyContractInstance, - durability: ContractDataDurability::Persistent, - }), - ContractData(LedgerKeyContractData { - contract: contract.clone(), - key: ScVal::Vec(Some( - vec![ScVal::Symbol("Metadata".try_into().unwrap())].try_into()?, - )), - durability: ContractDataDurability::Persistent, - }), - ]; - if asset != &Asset::Native { - read_write.push(ContractData(LedgerKeyContractData { - contract, - key: ScVal::Vec(Some( - vec![ScVal::Symbol("Admin".try_into().unwrap())].try_into()?, - )), - durability: ContractDataDurability::Persistent, - })); - } - - let op = Operation { - source_account: None, - body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { - host_function: HostFunction::CreateContract(CreateContractArgs { - contract_id_preimage: ContractIdPreimage::Asset(asset.clone()), - executable: ContractExecutable::StellarAsset, - }), - auth: VecM::default(), - }), - }; - - Ok(Transaction { - source_account: MuxedAccount::Ed25519(Uint256(key.verifying_key().to_bytes())), - fee, - seq_num: SequenceNumber(sequence), - cond: Preconditions::None, - memo: Memo::None, - operations: vec![op].try_into()?, - ext: TransactionExt::V0, - }) -} diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs deleted file mode 100644 index 76c13017..00000000 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ /dev/null @@ -1,228 +0,0 @@ -use std::array::TryFromSliceError; -use std::fmt::Debug; -use std::num::ParseIntError; - -use clap::{arg, command, Parser}; -use rand::Rng; -use soroban_env_host::{ - xdr::{ - AccountId, ContractExecutable, ContractIdPreimage, ContractIdPreimageFromAddress, - CreateContractArgs, Error as XdrError, Hash, HostFunction, InvokeHostFunctionOp, Memo, - MuxedAccount, Operation, OperationBody, Preconditions, PublicKey, ScAddress, - SequenceNumber, Transaction, TransactionExt, Uint256, VecM, - }, - HostError, -}; - -use crate::commands::contract::{self, id::wasm::get_contract_id}; -use crate::{ - commands::{config, contract::install, HEADING_RPC}, - rpc::{self, Client}, - utils, wasm, -}; - -#[derive(Parser, Debug, Clone)] -#[command(group( - clap::ArgGroup::new("wasm_src") - .required(true) - .args(&["wasm", "wasm_hash"]), -))] -#[group(skip)] -pub struct Cmd { - /// WASM file to deploy - #[arg(long, group = "wasm_src")] - wasm: Option, - /// Hash of the already installed/deployed WASM file - #[arg(long = "wasm-hash", conflicts_with = "wasm", group = "wasm_src")] - wasm_hash: Option, - /// Custom salt 32-byte salt for the token id - #[arg( - long, - help_heading = HEADING_RPC, - )] - salt: Option, - #[command(flatten)] - config: config::Args, - #[command(flatten)] - pub fee: crate::fee::Args, - #[arg(long, short = 'i', default_value = "false")] - /// Whether to ignore safety checks when deploying contracts - pub ignore_checks: bool, -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Install(#[from] install::Error), - #[error(transparent)] - Host(#[from] HostError), - #[error("error parsing int: {0}")] - ParseIntError(#[from] ParseIntError), - #[error("internal conversion error: {0}")] - TryFromSliceError(#[from] TryFromSliceError), - #[error("xdr processing error: {0}")] - Xdr(#[from] XdrError), - #[error("jsonrpc error: {0}")] - JsonRpc(#[from] jsonrpsee_core::Error), - #[error("cannot parse salt: {salt}")] - CannotParseSalt { salt: String }, - #[error("cannot parse contract ID {contract_id}: {error}")] - CannotParseContractId { - contract_id: String, - error: stellar_strkey::DecodeError, - }, - #[error("cannot parse WASM hash {wasm_hash}: {error}")] - CannotParseWasmHash { - wasm_hash: String, - error: stellar_strkey::DecodeError, - }, - #[error("Must provide either --wasm or --wash-hash")] - WasmNotProvided, - #[error(transparent)] - Rpc(#[from] rpc::Error), - #[error(transparent)] - Config(#[from] config::Error), - #[error(transparent)] - StrKey(#[from] stellar_strkey::DecodeError), - #[error(transparent)] - Infallible(#[from] std::convert::Infallible), - #[error(transparent)] - WasmId(#[from] contract::id::wasm::Error), -} - -impl Cmd { - pub async fn run(&self) -> Result<(), Error> { - let res_str = self.run_and_get_contract_id().await?; - println!("{res_str}"); - Ok(()) - } - - pub async fn run_and_get_contract_id(&self) -> Result { - let wasm_hash = if let Some(wasm) = &self.wasm { - let hash = install::Cmd { - wasm: wasm::Args { wasm: wasm.clone() }, - config: self.config.clone(), - fee: self.fee.clone(), - ignore_checks: self.ignore_checks, - } - .run_and_get_hash() - .await?; - hex::encode(hash) - } else { - self.wasm_hash - .as_ref() - .ok_or(Error::WasmNotProvided)? - .to_string() - }; - - let hash = Hash(utils::contract_id_from_str(&wasm_hash).map_err(|e| { - Error::CannotParseWasmHash { - wasm_hash: wasm_hash.clone(), - error: e, - } - })?); - - self.run_against_rpc_server(hash).await - } - - async fn run_against_rpc_server(&self, wasm_hash: Hash) -> Result { - let network = self.config.get_network()?; - let salt: [u8; 32] = match &self.salt { - Some(h) => soroban_spec_tools::utils::padded_hex_from_str(h, 32) - .map_err(|_| Error::CannotParseSalt { salt: h.clone() })? - .try_into() - .map_err(|_| Error::CannotParseSalt { salt: h.clone() })?, - None => rand::thread_rng().gen::<[u8; 32]>(), - }; - - let client = Client::new(&network.rpc_url)?; - client - .verify_network_passphrase(Some(&network.network_passphrase)) - .await?; - let key = self.config.key_pair()?; - - // Get the account sequence number - let public_strkey = - stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); - - let account_details = client.get_account(&public_strkey).await?; - let sequence: i64 = account_details.seq_num.into(); - let (tx, contract_id) = build_create_contract_tx( - wasm_hash, - sequence + 1, - self.fee.fee, - &network.network_passphrase, - salt, - &key, - )?; - client - .prepare_and_send_transaction(&tx, &key, &[], &network.network_passphrase, None, None) - .await?; - Ok(stellar_strkey::Contract(contract_id.0).to_string()) - } -} - -fn build_create_contract_tx( - hash: Hash, - sequence: i64, - fee: u32, - network_passphrase: &str, - salt: [u8; 32], - key: &ed25519_dalek::SigningKey, -) -> Result<(Transaction, Hash), Error> { - let source_account = AccountId(PublicKey::PublicKeyTypeEd25519( - key.verifying_key().to_bytes().into(), - )); - - let contract_id_preimage = ContractIdPreimage::Address(ContractIdPreimageFromAddress { - address: ScAddress::Account(source_account), - salt: Uint256(salt), - }); - let contract_id = get_contract_id(contract_id_preimage.clone(), network_passphrase)?; - - let op = Operation { - source_account: None, - body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { - host_function: HostFunction::CreateContract(CreateContractArgs { - contract_id_preimage, - executable: ContractExecutable::Wasm(hash), - }), - auth: VecM::default(), - }), - }; - let tx = Transaction { - source_account: MuxedAccount::Ed25519(Uint256(key.verifying_key().to_bytes())), - fee, - seq_num: SequenceNumber(sequence), - cond: Preconditions::None, - memo: Memo::None, - operations: vec![op].try_into()?, - ext: TransactionExt::V0, - }; - - Ok((tx, Hash(contract_id.into()))) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_build_create_contract() { - let hash = hex::decode("0000000000000000000000000000000000000000000000000000000000000000") - .unwrap() - .try_into() - .unwrap(); - let result = build_create_contract_tx( - Hash(hash), - 300, - 1, - "Public Global Stellar Network ; September 2015", - [0u8; 32], - &utils::parse_secret_key("SBFGFF27Y64ZUGFAIG5AMJGQODZZKV2YQKAVUUN4HNE24XZXD2OEUVUP") - .unwrap(), - ); - - assert!(result.is_ok()); - } -} diff --git a/cmd/soroban-cli/src/commands/contract/extend.rs b/cmd/soroban-cli/src/commands/contract/extend.rs deleted file mode 100644 index 7e9f1e98..00000000 --- a/cmd/soroban-cli/src/commands/contract/extend.rs +++ /dev/null @@ -1,192 +0,0 @@ -use std::{fmt::Debug, path::Path, str::FromStr}; - -use clap::{command, Parser}; -use soroban_env_host::xdr::{ - Error as XdrError, ExtendFootprintTtlOp, ExtensionPoint, LedgerEntry, LedgerEntryChange, - LedgerEntryData, LedgerFootprint, Memo, MuxedAccount, Operation, OperationBody, Preconditions, - SequenceNumber, SorobanResources, SorobanTransactionData, Transaction, TransactionExt, - TransactionMeta, TransactionMetaV3, TtlEntry, Uint256, -}; - -use crate::{ - commands::config, - key, - rpc::{self, Client}, - wasm, Pwd, -}; - -const MAX_LEDGERS_TO_EXTEND: u32 = 535_679; - -#[derive(Parser, Debug, Clone)] -#[group(skip)] -pub struct Cmd { - /// Number of ledgers to extend the entries - #[arg(long, required = true)] - pub ledgers_to_extend: u32, - /// Only print the new Time To Live ledger - #[arg(long)] - pub ttl_ledger_only: bool, - #[command(flatten)] - pub key: key::Args, - #[command(flatten)] - pub config: config::Args, - #[command(flatten)] - pub fee: crate::fee::Args, -} - -impl FromStr for Cmd { - type Err = clap::error::Error; - - fn from_str(s: &str) -> Result { - use clap::{CommandFactory, FromArgMatches}; - Self::from_arg_matches_mut(&mut Self::command().get_matches_from(s.split_whitespace())) - } -} - -impl Pwd for Cmd { - fn set_pwd(&mut self, pwd: &Path) { - self.config.set_pwd(pwd); - } -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("parsing key {key}: {error}")] - CannotParseKey { - key: String, - error: soroban_spec_tools::Error, - }, - #[error("parsing XDR key {key}: {error}")] - CannotParseXdrKey { key: String, error: XdrError }, - - #[error(transparent)] - Config(#[from] config::Error), - #[error("either `--key` or `--key-xdr` are required")] - KeyIsRequired, - #[error("xdr processing error: {0}")] - Xdr(#[from] XdrError), - #[error("Ledger entry not found")] - LedgerEntryNotFound, - #[error("missing operation result")] - MissingOperationResult, - #[error(transparent)] - Rpc(#[from] rpc::Error), - #[error(transparent)] - Wasm(#[from] wasm::Error), - #[error(transparent)] - Key(#[from] key::Error), -} - -impl Cmd { - #[allow(clippy::too_many_lines)] - pub async fn run(&self) -> Result<(), Error> { - let ttl_ledger = self.run_against_rpc_server().await?; - if self.ttl_ledger_only { - println!("{ttl_ledger}"); - } else { - println!("New ttl ledger: {ttl_ledger}"); - } - - Ok(()) - } - - fn ledgers_to_extend(&self) -> u32 { - let res = u32::min(self.ledgers_to_extend, MAX_LEDGERS_TO_EXTEND); - if res < self.ledgers_to_extend { - tracing::warn!( - "Ledgers to extend is too large, using max value of {MAX_LEDGERS_TO_EXTEND}" - ); - } - res - } - - async fn run_against_rpc_server(&self) -> Result { - let network = self.config.get_network()?; - tracing::trace!(?network); - let keys = self.key.parse_keys()?; - let network = &self.config.get_network()?; - let client = Client::new(&network.rpc_url)?; - let key = self.config.key_pair()?; - let extend_to = self.ledgers_to_extend(); - - // Get the account sequence number - let public_strkey = - stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); - let account_details = client.get_account(&public_strkey).await?; - let sequence: i64 = account_details.seq_num.into(); - - let tx = Transaction { - source_account: MuxedAccount::Ed25519(Uint256(key.verifying_key().to_bytes())), - fee: self.fee.fee, - seq_num: SequenceNumber(sequence + 1), - cond: Preconditions::None, - memo: Memo::None, - operations: vec![Operation { - source_account: None, - body: OperationBody::ExtendFootprintTtl(ExtendFootprintTtlOp { - ext: ExtensionPoint::V0, - extend_to, - }), - }] - .try_into()?, - ext: TransactionExt::V1(SorobanTransactionData { - ext: ExtensionPoint::V0, - resources: SorobanResources { - footprint: LedgerFootprint { - read_only: keys.clone().try_into()?, - read_write: vec![].try_into()?, - }, - instructions: 0, - read_bytes: 0, - write_bytes: 0, - }, - resource_fee: 0, - }), - }; - - let (result, meta, events) = client - .prepare_and_send_transaction(&tx, &key, &[], &network.network_passphrase, None, None) - .await?; - - tracing::trace!(?result); - tracing::trace!(?meta); - if !events.is_empty() { - tracing::info!("Events:\n {events:#?}"); - } - - // The transaction from core will succeed regardless of whether it actually found & extended - // the entry, so we have to inspect the result meta to tell if it worked or not. - let TransactionMeta::V3(TransactionMetaV3 { operations, .. }) = meta else { - return Err(Error::LedgerEntryNotFound); - }; - - // Simply check if there is exactly one entry here. We only support extending a single - // entry via this command (which we should fix separately, but). - if operations.len() == 0 { - return Err(Error::LedgerEntryNotFound); - } - - if operations[0].changes.is_empty() { - let entry = client.get_full_ledger_entries(&keys).await?; - let extension = entry.entries[0].live_until_ledger_seq; - if entry.latest_ledger + i64::from(extend_to) < i64::from(extension) { - return Ok(extension); - } - } - - match (&operations[0].changes[0], &operations[0].changes[1]) { - ( - LedgerEntryChange::State(_), - LedgerEntryChange::Updated(LedgerEntry { - data: - LedgerEntryData::Ttl(TtlEntry { - live_until_ledger_seq, - .. - }), - .. - }), - ) => Ok(*live_until_ledger_seq), - _ => Err(Error::LedgerEntryNotFound), - } - } -} diff --git a/cmd/soroban-cli/src/commands/contract/fetch.rs b/cmd/soroban-cli/src/commands/contract/fetch.rs deleted file mode 100644 index 61a82fc4..00000000 --- a/cmd/soroban-cli/src/commands/contract/fetch.rs +++ /dev/null @@ -1,185 +0,0 @@ -use std::convert::Infallible; - -use std::io::Write; -use std::path::{Path, PathBuf}; -use std::str::FromStr; -use std::{fmt::Debug, fs, io}; - -use clap::{arg, command, Parser}; -use soroban_env_host::{ - budget::Budget, - storage::Storage, - xdr::{ - self, ContractCodeEntry, ContractDataDurability, ContractDataEntry, ContractExecutable, - Error as XdrError, LedgerEntryData, LedgerKey, LedgerKeyContractCode, - LedgerKeyContractData, ScAddress, ScContractInstance, ScVal, - }, -}; - -use soroban_spec::read::FromWasmError; -use stellar_strkey::DecodeError; - -use super::super::config::{self, locator}; -use crate::commands::network::{self, Network}; -use crate::{ - rpc::{self, Client}, - utils, Pwd, -}; - -#[derive(Parser, Debug, Default, Clone)] -#[allow(clippy::struct_excessive_bools)] -#[group(skip)] -pub struct Cmd { - /// Contract ID to fetch - #[arg(long = "id", env = "SOROBAN_CONTRACT_ID")] - pub contract_id: String, - /// Where to write output otherwise stdout is used - #[arg(long, short = 'o')] - pub out_file: Option, - #[command(flatten)] - pub locator: locator::Args, - #[command(flatten)] - pub network: network::Args, -} - -impl FromStr for Cmd { - type Err = clap::error::Error; - - fn from_str(s: &str) -> Result { - use clap::{CommandFactory, FromArgMatches}; - Self::from_arg_matches_mut(&mut Self::command().get_matches_from(s.split_whitespace())) - } -} - -impl Pwd for Cmd { - fn set_pwd(&mut self, pwd: &Path) { - self.locator.set_pwd(pwd); - } -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Rpc(#[from] rpc::Error), - #[error(transparent)] - Config(#[from] config::Error), - #[error(transparent)] - Locator(#[from] locator::Error), - #[error(transparent)] - Xdr(#[from] XdrError), - #[error(transparent)] - Spec(#[from] soroban_spec::read::FromWasmError), - #[error(transparent)] - Io(#[from] std::io::Error), - #[error("missing result")] - MissingResult, - #[error("unexpected contract code data type: {0:?}")] - UnexpectedContractCodeDataType(LedgerEntryData), - #[error("reading file {0:?}: {1}")] - CannotWriteContractFile(PathBuf, io::Error), - #[error("cannot parse contract ID {0}: {1}")] - CannotParseContractId(String, DecodeError), - #[error("network details not provided")] - NetworkNotProvided, - #[error(transparent)] - Network(#[from] network::Error), - #[error("cannot create contract directory for {0:?}")] - CannotCreateContractDir(PathBuf), -} - -impl From for Error { - fn from(_: Infallible) -> Self { - unreachable!() - } -} - -impl Cmd { - pub async fn run(&self) -> Result<(), Error> { - let bytes = self.get_bytes().await?; - if let Some(out_file) = &self.out_file { - if let Some(parent) = out_file.parent() { - if !parent.exists() { - fs::create_dir_all(parent) - .map_err(|_| Error::CannotCreateContractDir(out_file.clone()))?; - } - } - fs::write(out_file, bytes) - .map_err(|io| Error::CannotWriteContractFile(out_file.clone(), io)) - } else { - let stdout = std::io::stdout(); - let mut handle = stdout.lock(); - handle.write_all(&bytes)?; - handle.flush()?; - Ok(()) - } - } - - pub async fn get_bytes(&self) -> Result, Error> { - self.run_against_rpc_server().await - } - - pub fn network(&self) -> Result { - Ok(self.network.get(&self.locator)?) - } - - pub async fn run_against_rpc_server(&self) -> Result, Error> { - let network = self.network()?; - tracing::trace!(?network); - let contract_id = self.contract_id()?; - let client = Client::new(&network.rpc_url)?; - client - .verify_network_passphrase(Some(&network.network_passphrase)) - .await?; - // async closures are not yet stable - Ok(client.get_remote_wasm(&contract_id).await?) - } - - fn contract_id(&self) -> Result<[u8; 32], Error> { - utils::contract_id_from_str(&self.contract_id) - .map_err(|e| Error::CannotParseContractId(self.contract_id.clone(), e)) - } -} - -pub fn get_contract_wasm_from_storage( - storage: &mut Storage, - contract_id: [u8; 32], -) -> Result, FromWasmError> { - let key = LedgerKey::ContractData(LedgerKeyContractData { - contract: ScAddress::Contract(contract_id.into()), - key: ScVal::LedgerKeyContractInstance, - durability: ContractDataDurability::Persistent, - }); - match storage.get(&key.into(), &Budget::default()) { - Ok(rc) => match rc.as_ref() { - xdr::LedgerEntry { - data: - LedgerEntryData::ContractData(ContractDataEntry { - val: ScVal::ContractInstance(ScContractInstance { executable, .. }), - .. - }), - .. - } => match executable { - ContractExecutable::Wasm(hash) => { - if let Ok(rc) = storage.get( - &LedgerKey::ContractCode(LedgerKeyContractCode { hash: hash.clone() }) - .into(), - &Budget::default(), - ) { - match rc.as_ref() { - xdr::LedgerEntry { - data: LedgerEntryData::ContractCode(ContractCodeEntry { code, .. }), - .. - } => Ok(code.to_vec()), - _ => Err(FromWasmError::NotFound), - } - } else { - Err(FromWasmError::NotFound) - } - } - ContractExecutable::StellarAsset => todo!(), - }, - _ => Err(FromWasmError::NotFound), - }, - _ => Err(FromWasmError::NotFound), - } -} diff --git a/cmd/soroban-cli/src/commands/contract/id.rs b/cmd/soroban-cli/src/commands/contract/id.rs deleted file mode 100644 index bb8744d5..00000000 --- a/cmd/soroban-cli/src/commands/contract/id.rs +++ /dev/null @@ -1,28 +0,0 @@ -pub mod asset; -pub mod wasm; - -#[derive(Debug, clap::Subcommand)] -pub enum Cmd { - /// Deploy builtin Soroban Asset Contract - Asset(asset::Cmd), - /// Deploy normal Wasm Contract - Wasm(wasm::Cmd), -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Asset(#[from] asset::Error), - #[error(transparent)] - Wasm(#[from] wasm::Error), -} - -impl Cmd { - pub fn run(&self) -> Result<(), Error> { - match &self { - Cmd::Asset(asset) => asset.run()?, - Cmd::Wasm(wasm) => wasm.run()?, - } - Ok(()) - } -} diff --git a/cmd/soroban-cli/src/commands/contract/id/asset.rs b/cmd/soroban-cli/src/commands/contract/id/asset.rs deleted file mode 100644 index 34e5767a..00000000 --- a/cmd/soroban-cli/src/commands/contract/id/asset.rs +++ /dev/null @@ -1,36 +0,0 @@ -use clap::{arg, command, Parser}; - -use crate::commands::config; - -use crate::utils::contract_id_hash_from_asset; -use crate::utils::parsing::parse_asset; - -#[derive(Parser, Debug, Clone)] -#[group(skip)] -pub struct Cmd { - /// ID of the Stellar classic asset to wrap, e.g. "USDC:G...5" - #[arg(long)] - pub asset: String, - - #[command(flatten)] - pub config: config::Args, -} -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - ParseError(#[from] crate::utils::parsing::Error), - #[error(transparent)] - ConfigError(#[from] crate::commands::config::Error), - #[error(transparent)] - Xdr(#[from] soroban_env_host::xdr::Error), -} -impl Cmd { - pub fn run(&self) -> Result<(), Error> { - let asset = parse_asset(&self.asset)?; - let network = self.config.get_network()?; - let contract_id = contract_id_hash_from_asset(&asset, &network.network_passphrase)?; - let strkey_contract_id = stellar_strkey::Contract(contract_id.0).to_string(); - println!("{strkey_contract_id}"); - Ok(()) - } -} diff --git a/cmd/soroban-cli/src/commands/contract/id/wasm.rs b/cmd/soroban-cli/src/commands/contract/id/wasm.rs deleted file mode 100644 index 9c02f07d..00000000 --- a/cmd/soroban-cli/src/commands/contract/id/wasm.rs +++ /dev/null @@ -1,68 +0,0 @@ -use clap::{arg, command, Parser}; -use sha2::{Digest, Sha256}; -use soroban_env_host::xdr::{ - self, AccountId, ContractIdPreimage, ContractIdPreimageFromAddress, Hash, HashIdPreimage, - HashIdPreimageContractId, Limits, PublicKey, ScAddress, Uint256, WriteXdr, -}; - -use crate::commands::config; - -#[derive(Parser, Debug, Clone)] -#[group(skip)] -pub struct Cmd { - /// ID of the Soroban contract - #[arg(long)] - pub salt: String, - - #[command(flatten)] - pub config: config::Args, -} -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - ParseError(#[from] crate::utils::parsing::Error), - #[error(transparent)] - ConfigError(#[from] crate::commands::config::Error), - #[error(transparent)] - Xdr(#[from] xdr::Error), - #[error("cannot parse salt {0}")] - CannotParseSalt(String), -} -impl Cmd { - pub fn run(&self) -> Result<(), Error> { - let salt: [u8; 32] = soroban_spec_tools::utils::padded_hex_from_str(&self.salt, 32) - .map_err(|_| Error::CannotParseSalt(self.salt.clone()))? - .try_into() - .map_err(|_| Error::CannotParseSalt(self.salt.clone()))?; - let contract_id_preimage = - contract_preimage(&self.config.key_pair()?.verifying_key(), salt); - let contract_id = get_contract_id( - contract_id_preimage.clone(), - &self.config.get_network()?.network_passphrase, - )?; - let strkey_contract_id = stellar_strkey::Contract(contract_id.0).to_string(); - println!("{strkey_contract_id}"); - Ok(()) - } -} - -pub fn contract_preimage(key: &ed25519_dalek::VerifyingKey, salt: [u8; 32]) -> ContractIdPreimage { - let source_account = AccountId(PublicKey::PublicKeyTypeEd25519(key.to_bytes().into())); - ContractIdPreimage::Address(ContractIdPreimageFromAddress { - address: ScAddress::Account(source_account), - salt: Uint256(salt), - }) -} - -pub fn get_contract_id( - contract_id_preimage: ContractIdPreimage, - network_passphrase: &str, -) -> Result { - let network_id = Hash(Sha256::digest(network_passphrase.as_bytes()).into()); - let preimage = HashIdPreimage::ContractId(HashIdPreimageContractId { - network_id, - contract_id_preimage, - }); - let preimage_xdr = preimage.to_xdr(Limits::none())?; - Ok(Hash(Sha256::digest(preimage_xdr).into())) -} diff --git a/cmd/soroban-cli/src/commands/contract/inspect.rs b/cmd/soroban-cli/src/commands/contract/inspect.rs deleted file mode 100644 index 355c18ca..00000000 --- a/cmd/soroban-cli/src/commands/contract/inspect.rs +++ /dev/null @@ -1,49 +0,0 @@ -use clap::{command, Parser}; -use soroban_env_host::xdr; -use std::{fmt::Debug, path::PathBuf}; -use tracing::debug; - -use super::SpecOutput; -use crate::{commands::config::locator, wasm}; - -#[derive(Parser, Debug, Clone)] -#[group(skip)] -pub struct Cmd { - #[command(flatten)] - wasm: wasm::Args, - /// Output just XDR in base64 - #[arg(long, default_value = "docs")] - output: SpecOutput, - - #[clap(flatten)] - locator: locator::Args, -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Wasm(#[from] wasm::Error), - #[error("missing spec for {0:?}")] - MissingSpec(PathBuf), - #[error(transparent)] - Xdr(#[from] xdr::Error), - #[error(transparent)] - Spec(#[from] crate::utils::contract_spec::Error), -} - -impl Cmd { - pub fn run(&self) -> Result<(), Error> { - let wasm = self.wasm.parse()?; - debug!("File: {}", self.wasm.wasm.to_string_lossy()); - let output = match self.output { - SpecOutput::XdrBase64 => wasm - .spec_base64 - .clone() - .ok_or_else(|| Error::MissingSpec(self.wasm.wasm.clone()))?, - SpecOutput::XdrBase64Array => wasm.spec_as_json_array()?, - SpecOutput::Docs => wasm.to_string(), - }; - println!("{output}"); - Ok(()) - } -} diff --git a/cmd/soroban-cli/src/commands/contract/install.rs b/cmd/soroban-cli/src/commands/contract/install.rs deleted file mode 100644 index 90577529..00000000 --- a/cmd/soroban-cli/src/commands/contract/install.rs +++ /dev/null @@ -1,223 +0,0 @@ -use std::array::TryFromSliceError; -use std::fmt::Debug; -use std::num::ParseIntError; - -use clap::{command, Parser}; -use soroban_env_host::xdr::{ - Error as XdrError, Hash, HostFunction, InvokeHostFunctionOp, Memo, MuxedAccount, Operation, - OperationBody, Preconditions, ScMetaEntry, ScMetaV0, SequenceNumber, Transaction, - TransactionExt, TransactionResult, TransactionResultResult, Uint256, VecM, -}; - -use super::restore; -use crate::key; -use crate::rpc::{self, Client}; -use crate::{commands::config, utils, wasm}; - -const CONTRACT_META_SDK_KEY: &str = "rssdkver"; -const PUBLIC_NETWORK_PASSPHRASE: &str = "Public Global Stellar Network ; September 2015"; - -#[derive(Parser, Debug, Clone)] -#[group(skip)] -pub struct Cmd { - #[command(flatten)] - pub config: config::Args, - #[command(flatten)] - pub fee: crate::fee::Args, - #[command(flatten)] - pub wasm: wasm::Args, - #[arg(long, short = 'i', default_value = "false")] - /// Whether to ignore safety checks when deploying contracts - pub ignore_checks: bool, -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("error parsing int: {0}")] - ParseIntError(#[from] ParseIntError), - #[error("internal conversion error: {0}")] - TryFromSliceError(#[from] TryFromSliceError), - #[error("xdr processing error: {0}")] - Xdr(#[from] XdrError), - #[error("jsonrpc error: {0}")] - JsonRpc(#[from] jsonrpsee_core::Error), - #[error(transparent)] - Rpc(#[from] rpc::Error), - #[error(transparent)] - Config(#[from] config::Error), - #[error(transparent)] - Wasm(#[from] wasm::Error), - #[error("unexpected ({length}) simulate transaction result length")] - UnexpectedSimulateTransactionResultSize { length: usize }, - #[error(transparent)] - Restore(#[from] restore::Error), - #[error("cannot parse WASM file {wasm}: {error}")] - CannotParseWasm { - wasm: std::path::PathBuf, - error: wasm::Error, - }, - #[error("the deployed smart contract {wasm} was built with Soroban Rust SDK v{version}, a release candidate version not intended for use with the Stellar Public Network. To deploy anyway, use --ignore-checks")] - ContractCompiledWithReleaseCandidateSdk { - wasm: std::path::PathBuf, - version: String, - }, -} - -impl Cmd { - pub async fn run(&self) -> Result<(), Error> { - let res_str = hex::encode(self.run_and_get_hash().await?); - println!("{res_str}"); - Ok(()) - } - - pub async fn run_and_get_hash(&self) -> Result { - self.run_against_rpc_server(&self.wasm.read()?).await - } - - async fn run_against_rpc_server(&self, contract: &[u8]) -> Result { - let network = self.config.get_network()?; - let client = Client::new(&network.rpc_url)?; - client - .verify_network_passphrase(Some(&network.network_passphrase)) - .await?; - let wasm_spec = &self.wasm.parse().map_err(|e| Error::CannotParseWasm { - wasm: self.wasm.wasm.clone(), - error: e, - })?; - // Check Rust SDK version if using the public network. - if let Some(rs_sdk_ver) = get_contract_meta_sdk_version(wasm_spec) { - if rs_sdk_ver.contains("rc") - && !self.ignore_checks - && network.network_passphrase == PUBLIC_NETWORK_PASSPHRASE - { - return Err(Error::ContractCompiledWithReleaseCandidateSdk { - wasm: self.wasm.wasm.clone(), - version: rs_sdk_ver, - }); - } else if rs_sdk_ver.contains("rc") - && network.network_passphrase == PUBLIC_NETWORK_PASSPHRASE - { - tracing::warn!("the deployed smart contract {path} was built with Soroban Rust SDK v{rs_sdk_ver}, a release candidate version not intended for use with the Stellar Public Network", path = self.wasm.wasm.display()); - } - } - let key = self.config.key_pair()?; - - // Get the account sequence number - let public_strkey = - stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); - let account_details = client.get_account(&public_strkey).await?; - let sequence: i64 = account_details.seq_num.into(); - - let (tx_without_preflight, hash) = - build_install_contract_code_tx(contract, sequence + 1, self.fee.fee, &key)?; - - // Currently internal errors are not returned if the contract code is expired - if let ( - TransactionResult { - result: TransactionResultResult::TxInternalError, - .. - }, - _, - _, - ) = client - .prepare_and_send_transaction( - &tx_without_preflight, - &key, - &[], - &network.network_passphrase, - None, - None, - ) - .await? - { - // Now just need to restore it and don't have to install again - restore::Cmd { - key: key::Args { - contract_id: None, - key: None, - key_xdr: None, - wasm: Some(self.wasm.wasm.clone()), - wasm_hash: None, - durability: super::Durability::Persistent, - }, - config: self.config.clone(), - fee: self.fee.clone(), - ledgers_to_extend: None, - ttl_ledger_only: true, - } - .run_against_rpc_server() - .await?; - } - - Ok(hash) - } -} - -fn get_contract_meta_sdk_version(wasm_spec: &utils::contract_spec::ContractSpec) -> Option { - let rs_sdk_version_option = if let Some(_meta) = &wasm_spec.meta_base64 { - wasm_spec.meta.iter().find(|entry| match entry { - ScMetaEntry::ScMetaV0(ScMetaV0 { key, .. }) => { - key.to_utf8_string_lossy().contains(CONTRACT_META_SDK_KEY) - } - }) - } else { - None - }; - if let Some(rs_sdk_version_entry) = &rs_sdk_version_option { - match rs_sdk_version_entry { - ScMetaEntry::ScMetaV0(ScMetaV0 { val, .. }) => { - return Some(val.to_utf8_string_lossy()); - } - } - } - None -} - -pub(crate) fn build_install_contract_code_tx( - source_code: &[u8], - sequence: i64, - fee: u32, - key: &ed25519_dalek::SigningKey, -) -> Result<(Transaction, Hash), XdrError> { - let hash = utils::contract_hash(source_code)?; - - let op = Operation { - source_account: Some(MuxedAccount::Ed25519(Uint256( - key.verifying_key().to_bytes(), - ))), - body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { - host_function: HostFunction::UploadContractWasm(source_code.try_into()?), - auth: VecM::default(), - }), - }; - - let tx = Transaction { - source_account: MuxedAccount::Ed25519(Uint256(key.verifying_key().to_bytes())), - fee, - seq_num: SequenceNumber(sequence), - cond: Preconditions::None, - memo: Memo::None, - operations: vec![op].try_into()?, - ext: TransactionExt::V0, - }; - - Ok((tx, hash)) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_build_install_contract_code() { - let result = build_install_contract_code_tx( - b"foo", - 300, - 1, - &utils::parse_secret_key("SBFGFF27Y64ZUGFAIG5AMJGQODZZKV2YQKAVUUN4HNE24XZXD2OEUVUP") - .unwrap(), - ); - - assert!(result.is_ok()); - } -} diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs deleted file mode 100644 index 669342b0..00000000 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ /dev/null @@ -1,484 +0,0 @@ -use std::collections::HashMap; -use std::convert::{Infallible, TryInto}; -use std::ffi::OsString; -use std::num::ParseIntError; -use std::path::{Path, PathBuf}; -use std::str::FromStr; -use std::{fmt::Debug, fs, io}; - -use clap::{arg, command, value_parser, Parser}; -use ed25519_dalek::SigningKey; -use heck::ToKebabCase; - -use soroban_env_host::{ - xdr::{ - self, Error as XdrError, Hash, HostFunction, InvokeContractArgs, InvokeHostFunctionOp, - LedgerEntryData, LedgerFootprint, Memo, MuxedAccount, Operation, OperationBody, - Preconditions, ScAddress, ScSpecEntry, ScSpecFunctionV0, ScSpecTypeDef, ScVal, ScVec, - SequenceNumber, SorobanAuthorizationEntry, SorobanResources, Transaction, TransactionExt, - Uint256, VecM, - }, - HostError, -}; - -use soroban_spec::read::FromWasmError; -use stellar_strkey::DecodeError; - -use super::super::{ - config::{self, locator}, - events, -}; -use crate::{ - commands::global, - rpc::{self, Client}, - utils::{self, contract_spec}, - Pwd, -}; -use soroban_spec_tools::Spec; - -#[derive(Parser, Debug, Default, Clone)] -#[allow(clippy::struct_excessive_bools)] -#[group(skip)] -pub struct Cmd { - /// Contract ID to invoke - #[arg(long = "id", env = "SOROBAN_CONTRACT_ID")] - pub contract_id: String, - // For testing only - #[arg(skip)] - pub wasm: Option, - /// Output the cost execution to stderr - #[arg(long = "cost")] - pub cost: bool, - /// Function name as subcommand, then arguments for that function as `--arg-name value` - #[arg(last = true, id = "CONTRACT_FN_AND_ARGS")] - pub slop: Vec, - #[command(flatten)] - pub config: config::Args, - #[command(flatten)] - pub fee: crate::fee::Args, -} - -impl FromStr for Cmd { - type Err = clap::error::Error; - - fn from_str(s: &str) -> Result { - use clap::{CommandFactory, FromArgMatches}; - Self::from_arg_matches_mut(&mut Self::command().get_matches_from(s.split_whitespace())) - } -} - -impl Pwd for Cmd { - fn set_pwd(&mut self, pwd: &Path) { - self.config.set_pwd(pwd); - } -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("parsing argument {arg}: {error}")] - CannotParseArg { - arg: String, - error: soroban_spec_tools::Error, - }, - #[error("cannot add contract to ledger entries: {0}")] - CannotAddContractToLedgerEntries(XdrError), - #[error(transparent)] - // TODO: the Display impl of host errors is pretty user-unfriendly - // (it just calls Debug). I think we can do better than that - Host(#[from] HostError), - #[error("reading file {0:?}: {1}")] - CannotReadContractFile(PathBuf, io::Error), - #[error("committing file {filepath}: {error}")] - CannotCommitEventsFile { - filepath: std::path::PathBuf, - error: events::Error, - }, - #[error("cannot parse contract ID {0}: {1}")] - CannotParseContractId(String, DecodeError), - #[error("function {0} was not found in the contract")] - FunctionNotFoundInContractSpec(String), - #[error("parsing contract spec: {0}")] - CannotParseContractSpec(FromWasmError), - // }, - #[error("function name {0} is too long")] - FunctionNameTooLong(String), - #[error("argument count ({current}) surpasses maximum allowed count ({maximum})")] - MaxNumberOfArgumentsReached { current: usize, maximum: usize }, - #[error("cannot print result {result:?}: {error}")] - CannotPrintResult { - result: ScVal, - error: soroban_spec_tools::Error, - }, - #[error(transparent)] - Xdr(#[from] XdrError), - #[error("error parsing int: {0}")] - ParseIntError(#[from] ParseIntError), - #[error(transparent)] - Rpc(#[from] rpc::Error), - #[error("unexpected contract code data type: {0:?}")] - UnexpectedContractCodeDataType(LedgerEntryData), - #[error("missing operation result")] - MissingOperationResult, - #[error("missing result")] - MissingResult, - #[error(transparent)] - StrVal(#[from] soroban_spec_tools::Error), - #[error("error loading signing key: {0}")] - SignatureError(#[from] ed25519_dalek::SignatureError), - #[error(transparent)] - Config(#[from] config::Error), - #[error("unexpected ({length}) simulate transaction result length")] - UnexpectedSimulateTransactionResultSize { length: usize }, - #[error("Missing argument {0}")] - MissingArgument(String), - #[error(transparent)] - Clap(#[from] clap::Error), - #[error(transparent)] - Locator(#[from] locator::Error), - #[error("Contract Error\n{0}: {1}")] - ContractInvoke(String, String), - #[error(transparent)] - StrKey(#[from] stellar_strkey::DecodeError), - #[error(transparent)] - ContractSpec(#[from] contract_spec::Error), - #[error("")] - MissingFileArg(PathBuf), -} - -impl From for Error { - fn from(_: Infallible) -> Self { - unreachable!() - } -} - -impl Cmd { - fn build_host_function_parameters( - &self, - contract_id: [u8; 32], - spec_entries: &[ScSpecEntry], - ) -> Result<(String, Spec, InvokeContractArgs, Vec), Error> { - let spec = Spec(Some(spec_entries.to_vec())); - let mut cmd = clap::Command::new(self.contract_id.clone()) - .no_binary_name(true) - .term_width(300) - .max_term_width(300); - - for ScSpecFunctionV0 { name, .. } in spec.find_functions()? { - cmd = cmd.subcommand(build_custom_cmd(&name.to_utf8_string_lossy(), &spec)?); - } - cmd.build(); - let long_help = cmd.render_long_help(); - let mut matches_ = cmd.get_matches_from(&self.slop); - let Some((function, matches_)) = &matches_.remove_subcommand() else { - println!("{long_help}"); - std::process::exit(1); - }; - - let func = spec.find_function(function)?; - // create parsed_args in same order as the inputs to func - let mut signers: Vec = vec![]; - let parsed_args = func - .inputs - .iter() - .map(|i| { - let name = i.name.to_utf8_string()?; - if let Some(mut val) = matches_.get_raw(&name) { - let mut s = val.next().unwrap().to_string_lossy().to_string(); - if matches!(i.type_, ScSpecTypeDef::Address) { - let cmd = crate::commands::keys::address::Cmd { - name: s.clone(), - hd_path: Some(0), - locator: self.config.locator.clone(), - }; - if let Ok(address) = cmd.public_key() { - s = address.to_string(); - } - if let Ok(key) = cmd.private_key() { - signers.push(key); - } - } - spec.from_string(&s, &i.type_) - .map_err(|error| Error::CannotParseArg { arg: name, error }) - } else if matches!(i.type_, ScSpecTypeDef::Option(_)) { - Ok(ScVal::Void) - } else if let Some(arg_path) = - matches_.get_one::(&fmt_arg_file_name(&name)) - { - if matches!(i.type_, ScSpecTypeDef::Bytes | ScSpecTypeDef::BytesN(_)) { - Ok(ScVal::try_from( - &std::fs::read(arg_path) - .map_err(|_| Error::MissingFileArg(arg_path.clone()))?, - ) - .map_err(|()| Error::CannotParseArg { - arg: name.clone(), - error: soroban_spec_tools::Error::Unknown, - })?) - } else { - let file_contents = std::fs::read_to_string(arg_path) - .map_err(|_| Error::MissingFileArg(arg_path.clone()))?; - tracing::debug!( - "file {arg_path:?}, has contents:\n{file_contents}\nAnd type {:#?}\n{}", - i.type_, - file_contents.len() - ); - spec.from_string(&file_contents, &i.type_) - .map_err(|error| Error::CannotParseArg { arg: name, error }) - } - } else { - Err(Error::MissingArgument(name)) - } - }) - .collect::, Error>>()?; - - let contract_address_arg = ScAddress::Contract(Hash(contract_id)); - let function_symbol_arg = function - .try_into() - .map_err(|()| Error::FunctionNameTooLong(function.clone()))?; - - let final_args = - parsed_args - .clone() - .try_into() - .map_err(|_| Error::MaxNumberOfArgumentsReached { - current: parsed_args.len(), - maximum: ScVec::default().max_len(), - })?; - - let invoke_args = InvokeContractArgs { - contract_address: contract_address_arg, - function_name: function_symbol_arg, - args: final_args, - }; - - Ok((function.clone(), spec, invoke_args, signers)) - } - - pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> { - let res = self.invoke(global_args).await?; - println!("{res}"); - Ok(()) - } - - pub async fn invoke(&self, global_args: &global::Args) -> Result { - self.run_against_rpc_server(global_args).await - } - - pub async fn run_against_rpc_server( - &self, - global_args: &global::Args, - ) -> Result { - let network = self.config.get_network()?; - tracing::trace!(?network); - let contract_id = self.contract_id()?; - let spec_entries = self.spec_entries()?; - if let Some(spec_entries) = &spec_entries { - // For testing wasm arg parsing - let _ = self.build_host_function_parameters(contract_id, spec_entries)?; - } - let client = Client::new(&network.rpc_url)?; - client - .verify_network_passphrase(Some(&network.network_passphrase)) - .await?; - let key = self.config.key_pair()?; - - // Get the account sequence number - let public_strkey = - stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); - let account_details = client.get_account(&public_strkey).await?; - let sequence: i64 = account_details.seq_num.into(); - - // Get the contract - let spec_entries = client.get_remote_contract_spec(&contract_id).await?; - - // Get the ledger footprint - let (function, spec, host_function_params, signers) = - self.build_host_function_parameters(contract_id, &spec_entries)?; - let tx = build_invoke_contract_tx( - host_function_params.clone(), - sequence + 1, - self.fee.fee, - &key, - )?; - - let (result, meta, events) = client - .prepare_and_send_transaction( - &tx, - &key, - &signers, - &network.network_passphrase, - Some(log_events), - (global_args.verbose || global_args.very_verbose || self.cost) - .then_some(log_resources), - ) - .await?; - - tracing::debug!(?result); - crate::log::diagnostic_events(&events, tracing::Level::INFO); - let xdr::TransactionMeta::V3(xdr::TransactionMetaV3 { - soroban_meta: Some(xdr::SorobanTransactionMeta { return_value, .. }), - .. - }) = meta - else { - return Err(Error::MissingOperationResult); - }; - - output_to_string(&spec, &return_value, &function) - } - - pub fn read_wasm(&self) -> Result>, Error> { - Ok(if let Some(wasm) = self.wasm.as_ref() { - Some(fs::read(wasm).map_err(|e| Error::CannotReadContractFile(wasm.clone(), e))?) - } else { - None - }) - } - - pub fn spec_entries(&self) -> Result>, Error> { - self.read_wasm()? - .map(|wasm| { - soroban_spec::read::from_wasm(&wasm).map_err(Error::CannotParseContractSpec) - }) - .transpose() - } -} - -impl Cmd { - fn contract_id(&self) -> Result<[u8; 32], Error> { - utils::contract_id_from_str(&self.contract_id) - .map_err(|e| Error::CannotParseContractId(self.contract_id.clone(), e)) - } -} - -fn log_events( - footprint: &LedgerFootprint, - auth: &[VecM], - events: &[xdr::DiagnosticEvent], -) { - crate::log::auth(auth); - crate::log::diagnostic_events(events, tracing::Level::TRACE); - crate::log::footprint(footprint); -} - -fn log_resources(resources: &SorobanResources) { - crate::log::cost(resources); -} - -pub fn output_to_string(spec: &Spec, res: &ScVal, function: &str) -> Result { - let mut res_str = String::new(); - if let Some(output) = spec.find_function(function)?.outputs.first() { - res_str = spec - .xdr_to_json(res, output) - .map_err(|e| Error::CannotPrintResult { - result: res.clone(), - error: e, - })? - .to_string(); - } - Ok(res_str) -} - -fn build_invoke_contract_tx( - parameters: InvokeContractArgs, - sequence: i64, - fee: u32, - key: &SigningKey, -) -> Result { - let op = Operation { - source_account: None, - body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { - host_function: HostFunction::InvokeContract(parameters), - auth: VecM::default(), - }), - }; - Ok(Transaction { - source_account: MuxedAccount::Ed25519(Uint256(key.verifying_key().to_bytes())), - fee, - seq_num: SequenceNumber(sequence), - cond: Preconditions::None, - memo: Memo::None, - operations: vec![op].try_into()?, - ext: TransactionExt::V0, - }) -} - -fn build_custom_cmd(name: &str, spec: &Spec) -> Result { - let func = spec - .find_function(name) - .map_err(|_| Error::FunctionNotFoundInContractSpec(name.to_string()))?; - - // Parse the function arguments - let inputs_map = &func - .inputs - .iter() - .map(|i| (i.name.to_utf8_string().unwrap(), i.type_.clone())) - .collect::>(); - let name: &'static str = Box::leak(name.to_string().into_boxed_str()); - let mut cmd = clap::Command::new(name) - .no_binary_name(true) - .term_width(300) - .max_term_width(300); - let kebab_name = name.to_kebab_case(); - if kebab_name != name { - cmd = cmd.alias(kebab_name); - } - let doc: &'static str = Box::leak(func.doc.to_utf8_string_lossy().into_boxed_str()); - let long_doc: &'static str = Box::leak(arg_file_help(doc).into_boxed_str()); - - cmd = cmd.about(Some(doc)).long_about(long_doc); - for (name, type_) in inputs_map { - let mut arg = clap::Arg::new(name); - let file_arg_name = fmt_arg_file_name(name); - let mut file_arg = clap::Arg::new(&file_arg_name); - arg = arg - .long(name) - .alias(name.to_kebab_case()) - .num_args(1) - .value_parser(clap::builder::NonEmptyStringValueParser::new()) - .long_help(spec.doc(name, type_)?); - - file_arg = file_arg - .long(&file_arg_name) - .alias(file_arg_name.to_kebab_case()) - .num_args(1) - .hide(true) - .value_parser(value_parser!(PathBuf)) - .conflicts_with(name); - - if let Some(value_name) = spec.arg_value_name(type_, 0) { - let value_name: &'static str = Box::leak(value_name.into_boxed_str()); - arg = arg.value_name(value_name); - } - - // Set up special-case arg rules - arg = match type_ { - xdr::ScSpecTypeDef::Bool => arg - .num_args(0..1) - .default_missing_value("true") - .default_value("false") - .num_args(0..=1), - xdr::ScSpecTypeDef::Option(_val) => arg.required(false), - xdr::ScSpecTypeDef::I256 - | xdr::ScSpecTypeDef::I128 - | xdr::ScSpecTypeDef::I64 - | xdr::ScSpecTypeDef::I32 => arg.allow_hyphen_values(true), - _ => arg, - }; - - cmd = cmd.arg(arg); - cmd = cmd.arg(file_arg); - } - Ok(cmd) -} - -fn fmt_arg_file_name(name: &str) -> String { - format!("{name}-file-path") -} - -fn arg_file_help(docs: &str) -> String { - format!( - r#"{docs} -Usage Notes: -Each arg has a corresponding ---file-path which is a path to a file containing the corresponding JSON argument. -Note: The only types which aren't JSON are Bytes and Bytes which are raw bytes"# - ) -} diff --git a/cmd/soroban-cli/src/commands/contract/mod.rs b/cmd/soroban-cli/src/commands/contract/mod.rs deleted file mode 100644 index 35be97a7..00000000 --- a/cmd/soroban-cli/src/commands/contract/mod.rs +++ /dev/null @@ -1,158 +0,0 @@ -pub mod asset; -pub mod bindings; -pub mod build; -pub mod deploy; -pub mod extend; -pub mod fetch; -pub mod id; -pub mod inspect; -pub mod install; -pub mod invoke; -pub mod optimize; -pub mod read; -pub mod restore; - -use crate::commands::global; - -#[derive(Debug, clap::Subcommand)] -pub enum Cmd { - /// Utilities to deploy a Stellar Asset Contract or get its id - #[command(subcommand)] - Asset(asset::Cmd), - /// Generate code client bindings for a contract - #[command(subcommand)] - Bindings(bindings::Cmd), - - Build(build::Cmd), - - /// Extend the time to live ledger of a contract-data ledger entry. - /// - /// If no keys are specified the contract itself is extended. - Extend(extend::Cmd), - - /// Deploy a wasm contract - Deploy(deploy::wasm::Cmd), - - /// Fetch a contract's Wasm binary - Fetch(fetch::Cmd), - - /// Generate the contract id for a given contract or asset - #[command(subcommand)] - Id(id::Cmd), - - /// Inspect a WASM file listing contract functions, meta, etc - Inspect(inspect::Cmd), - - /// Install a WASM file to the ledger without creating a contract instance - Install(install::Cmd), - - /// Invoke a contract function - /// - /// Generates an "implicit CLI" for the specified contract on-the-fly using the contract's - /// schema, which gets embedded into every Soroban contract. The "slop" in this command, - /// everything after the `--`, gets passed to this implicit CLI. Get in-depth help for a given - /// contract: - /// - /// soroban contract invoke ... -- --help - Invoke(invoke::Cmd), - - /// Optimize a WASM file - Optimize(optimize::Cmd), - - /// Print the current value of a contract-data ledger entry - Read(read::Cmd), - - /// Restore an evicted value for a contract-data legder entry. - /// - /// If no keys are specificed the contract itself is restored. - Restore(restore::Cmd), -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Asset(#[from] asset::Error), - - #[error(transparent)] - Bindings(#[from] bindings::Error), - - #[error(transparent)] - Build(#[from] build::Error), - - #[error(transparent)] - Extend(#[from] extend::Error), - - #[error(transparent)] - Deploy(#[from] deploy::wasm::Error), - - #[error(transparent)] - Fetch(#[from] fetch::Error), - #[error(transparent)] - Id(#[from] id::Error), - - #[error(transparent)] - Inspect(#[from] inspect::Error), - - #[error(transparent)] - Install(#[from] install::Error), - - #[error(transparent)] - Invoke(#[from] invoke::Error), - - #[error(transparent)] - Optimize(#[from] optimize::Error), - - #[error(transparent)] - Read(#[from] read::Error), - - #[error(transparent)] - Restore(#[from] restore::Error), -} - -impl Cmd { - pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> { - match &self { - Cmd::Asset(asset) => asset.run().await?, - Cmd::Bindings(bindings) => bindings.run().await?, - Cmd::Build(build) => build.run()?, - Cmd::Extend(extend) => extend.run().await?, - Cmd::Deploy(deploy) => deploy.run().await?, - Cmd::Id(id) => id.run()?, - Cmd::Inspect(inspect) => inspect.run()?, - Cmd::Install(install) => install.run().await?, - Cmd::Invoke(invoke) => invoke.run(global_args).await?, - Cmd::Optimize(optimize) => optimize.run()?, - Cmd::Fetch(fetch) => fetch.run().await?, - Cmd::Read(read) => read.run().await?, - Cmd::Restore(restore) => restore.run().await?, - } - Ok(()) - } -} - -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, clap::ValueEnum)] -pub enum Durability { - /// Persistent - Persistent, - /// Temporary - Temporary, -} - -impl From<&Durability> for soroban_env_host::xdr::ContractDataDurability { - fn from(d: &Durability) -> Self { - match d { - Durability::Persistent => soroban_env_host::xdr::ContractDataDurability::Persistent, - Durability::Temporary => soroban_env_host::xdr::ContractDataDurability::Temporary, - } - } -} - -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, clap::ValueEnum)] -pub enum SpecOutput { - /// XDR of array of contract spec entries - XdrBase64, - /// Array of xdr of contract spec entries - XdrBase64Array, - /// Pretty print of contract spec entries - Docs, -} diff --git a/cmd/soroban-cli/src/commands/contract/optimize.rs b/cmd/soroban-cli/src/commands/contract/optimize.rs deleted file mode 100644 index 751dabb1..00000000 --- a/cmd/soroban-cli/src/commands/contract/optimize.rs +++ /dev/null @@ -1,80 +0,0 @@ -use clap::{arg, command, Parser}; -use std::fmt::Debug; -#[cfg(feature = "opt")] -use wasm_opt::{Feature, OptimizationError, OptimizationOptions}; - -use crate::wasm; - -#[derive(Parser, Debug, Clone)] -#[group(skip)] -pub struct Cmd { - #[command(flatten)] - wasm: wasm::Args, - /// Path to write the optimized WASM file to (defaults to same location as --wasm with .optimized.wasm suffix) - #[arg(long)] - wasm_out: Option, -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Wasm(#[from] wasm::Error), - #[cfg(feature = "opt")] - #[error("optimization error: {0}")] - OptimizationError(OptimizationError), - #[cfg(not(feature = "opt"))] - #[error("Must install with \"opt\" feature, e.g. `cargo install soroban-cli --features opt")] - Install, -} - -impl Cmd { - #[cfg(not(feature = "opt"))] - pub fn run(&self) -> Result<(), Error> { - Err(Error::Install) - } - - #[cfg(feature = "opt")] - pub fn run(&self) -> Result<(), Error> { - let wasm_size = self.wasm.len()?; - - println!( - "Reading: {} ({} bytes)", - self.wasm.wasm.to_string_lossy(), - wasm_size - ); - - let wasm_out = self.wasm_out.as_ref().cloned().unwrap_or_else(|| { - let mut wasm_out = self.wasm.wasm.clone(); - wasm_out.set_extension("optimized.wasm"); - wasm_out - }); - println!("Writing to: {}...", wasm_out.to_string_lossy()); - - let mut options = OptimizationOptions::new_optimize_for_size_aggressively(); - options.converge = true; - - // Explicitly set to MVP + sign-ext + mutable-globals, which happens to - // also be the default featureset, but just to be extra clear we set it - // explicitly. - // - // Formerly Soroban supported only the MVP feature set, but Rust 1.70 as - // well as Clang generate code with sign-ext + mutable-globals enabled, - // so Soroban has taken a change to support them also. - options.mvp_features_only(); - options.enable_feature(Feature::MutableGlobals); - options.enable_feature(Feature::SignExt); - - options - .run(&self.wasm.wasm, &wasm_out) - .map_err(Error::OptimizationError)?; - - let wasm_out_size = wasm::len(&wasm_out)?; - println!( - "Optimized: {} ({} bytes)", - wasm_out.to_string_lossy(), - wasm_out_size - ); - - Ok(()) - } -} diff --git a/cmd/soroban-cli/src/commands/contract/read.rs b/cmd/soroban-cli/src/commands/contract/read.rs deleted file mode 100644 index 842832d5..00000000 --- a/cmd/soroban-cli/src/commands/contract/read.rs +++ /dev/null @@ -1,180 +0,0 @@ -use std::{ - fmt::Debug, - io::{self, stdout}, -}; - -use clap::{command, Parser, ValueEnum}; -use soroban_env_host::{ - xdr::{ - ContractDataEntry, Error as XdrError, LedgerEntryData, LedgerKey, LedgerKeyContractData, - ScVal, WriteXdr, - }, - HostError, -}; -use soroban_sdk::xdr::Limits; - -use crate::{ - commands::config, - key, - rpc::{self, Client, FullLedgerEntries, FullLedgerEntry}, -}; - -#[derive(Parser, Debug, Clone)] -#[group(skip)] -pub struct Cmd { - /// Type of output to generate - #[arg(long, value_enum, default_value("string"))] - pub output: Output, - #[command(flatten)] - pub key: key::Args, - #[command(flatten)] - config: config::Args, -} - -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, ValueEnum)] -pub enum Output { - /// String - String, - /// Json - Json, - /// XDR - Xdr, -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("parsing key {key}: {error}")] - CannotParseKey { - key: String, - error: soroban_spec_tools::Error, - }, - #[error("parsing XDR key {key}: {error}")] - CannotParseXdrKey { key: String, error: XdrError }, - #[error("cannot parse contract ID {contract_id}: {error}")] - CannotParseContractId { - contract_id: String, - error: stellar_strkey::DecodeError, - }, - #[error("cannot print result {result:?}: {error}")] - CannotPrintResult { - result: ScVal, - error: soroban_spec_tools::Error, - }, - #[error("cannot print result {result:?}: {error}")] - CannotPrintJsonResult { - result: ScVal, - error: serde_json::Error, - }, - #[error("cannot print as csv: {error}")] - CannotPrintAsCsv { error: csv::Error }, - #[error("cannot print: {error}")] - CannotPrintFlush { error: io::Error }, - #[error(transparent)] - Config(#[from] config::Error), - #[error("either `--key` or `--key-xdr` are required when querying a network")] - KeyIsRequired, - #[error(transparent)] - Rpc(#[from] rpc::Error), - #[error(transparent)] - Xdr(#[from] XdrError), - #[error(transparent)] - // TODO: the Display impl of host errors is pretty user-unfriendly - // (it just calls Debug). I think we can do better than that - Host(#[from] HostError), - #[error("no matching contract data entries were found for the specified contract id")] - NoContractDataEntryFoundForContractID, - #[error(transparent)] - Key(#[from] key::Error), - #[error("Only contract data and code keys are allowed")] - OnlyDataAllowed, -} - -impl Cmd { - pub async fn run(&self) -> Result<(), Error> { - let entries = self.run_against_rpc_server().await?; - self.output_entries(&entries) - } - - async fn run_against_rpc_server(&self) -> Result { - let network = self.config.get_network()?; - tracing::trace!(?network); - let network = &self.config.get_network()?; - let client = Client::new(&network.rpc_url)?; - let keys = self.key.parse_keys()?; - Ok(client.get_full_ledger_entries(&keys).await?) - } - - fn output_entries(&self, entries: &FullLedgerEntries) -> Result<(), Error> { - if entries.entries.is_empty() { - return Err(Error::NoContractDataEntryFoundForContractID); - } - tracing::trace!("{entries:#?}"); - let mut out = csv::Writer::from_writer(stdout()); - for FullLedgerEntry { - key, - val, - live_until_ledger_seq, - last_modified_ledger, - } in &entries.entries - { - let ( - LedgerKey::ContractData(LedgerKeyContractData { key, .. }), - LedgerEntryData::ContractData(ContractDataEntry { val, .. }), - ) = (key, val) - else { - return Err(Error::OnlyDataAllowed); - }; - let output = match self.output { - Output::String => [ - soroban_spec_tools::to_string(key).map_err(|e| Error::CannotPrintResult { - result: key.clone(), - error: e, - })?, - soroban_spec_tools::to_string(val).map_err(|e| Error::CannotPrintResult { - result: val.clone(), - error: e, - })?, - last_modified_ledger.to_string(), - live_until_ledger_seq.to_string(), - ], - Output::Json => [ - serde_json::to_string_pretty(&key).map_err(|error| { - Error::CannotPrintJsonResult { - result: key.clone(), - error, - } - })?, - serde_json::to_string_pretty(&val).map_err(|error| { - Error::CannotPrintJsonResult { - result: val.clone(), - error, - } - })?, - serde_json::to_string_pretty(&last_modified_ledger).map_err(|error| { - Error::CannotPrintJsonResult { - result: val.clone(), - error, - } - })?, - serde_json::to_string_pretty(&live_until_ledger_seq).map_err(|error| { - Error::CannotPrintJsonResult { - result: val.clone(), - error, - } - })?, - ], - Output::Xdr => [ - key.to_xdr_base64(Limits::none())?, - val.to_xdr_base64(Limits::none())?, - last_modified_ledger.to_xdr_base64(Limits::none())?, - live_until_ledger_seq.to_xdr_base64(Limits::none())?, - ], - }; - out.write_record(output) - .map_err(|e| Error::CannotPrintAsCsv { error: e })?; - } - out.flush() - .map_err(|e| Error::CannotPrintFlush { error: e })?; - Ok(()) - } -} diff --git a/cmd/soroban-cli/src/commands/contract/restore.rs b/cmd/soroban-cli/src/commands/contract/restore.rs deleted file mode 100644 index 38b8a84a..00000000 --- a/cmd/soroban-cli/src/commands/contract/restore.rs +++ /dev/null @@ -1,206 +0,0 @@ -use std::{fmt::Debug, path::Path, str::FromStr}; - -use clap::{command, Parser}; -use soroban_env_host::xdr::{ - Error as XdrError, ExtensionPoint, LedgerEntry, LedgerEntryChange, LedgerEntryData, - LedgerFootprint, Memo, MuxedAccount, Operation, OperationBody, OperationMeta, Preconditions, - RestoreFootprintOp, SequenceNumber, SorobanResources, SorobanTransactionData, Transaction, - TransactionExt, TransactionMeta, TransactionMetaV3, TtlEntry, Uint256, -}; -use stellar_strkey::DecodeError; - -use crate::{ - commands::{ - config::{self, locator}, - contract::extend, - }, - key, - rpc::{self, Client}, - wasm, Pwd, -}; - -#[derive(Parser, Debug, Clone)] -#[group(skip)] -pub struct Cmd { - #[command(flatten)] - pub key: key::Args, - /// Number of ledgers to extend the entry - #[arg(long)] - pub ledgers_to_extend: Option, - /// Only print the new Time To Live ledger - #[arg(long)] - pub ttl_ledger_only: bool, - #[command(flatten)] - pub config: config::Args, - #[command(flatten)] - pub fee: crate::fee::Args, -} - -impl FromStr for Cmd { - type Err = clap::error::Error; - - fn from_str(s: &str) -> Result { - use clap::{CommandFactory, FromArgMatches}; - Self::from_arg_matches_mut(&mut Self::command().get_matches_from(s.split_whitespace())) - } -} - -impl Pwd for Cmd { - fn set_pwd(&mut self, pwd: &Path) { - self.config.set_pwd(pwd); - } -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("parsing key {key}: {error}")] - CannotParseKey { - key: String, - error: soroban_spec_tools::Error, - }, - #[error("parsing XDR key {key}: {error}")] - CannotParseXdrKey { key: String, error: XdrError }, - #[error("cannot parse contract ID {0}: {1}")] - CannotParseContractId(String, DecodeError), - #[error(transparent)] - Config(#[from] config::Error), - #[error("either `--key` or `--key-xdr` are required")] - KeyIsRequired, - #[error("xdr processing error: {0}")] - Xdr(#[from] XdrError), - #[error("Ledger entry not found")] - LedgerEntryNotFound, - #[error(transparent)] - Locator(#[from] locator::Error), - #[error("missing operation result")] - MissingOperationResult, - #[error(transparent)] - Rpc(#[from] rpc::Error), - #[error(transparent)] - Wasm(#[from] wasm::Error), - #[error(transparent)] - Key(#[from] key::Error), - #[error(transparent)] - Extend(#[from] extend::Error), -} - -impl Cmd { - #[allow(clippy::too_many_lines)] - pub async fn run(&self) -> Result<(), Error> { - let expiration_ledger_seq = self.run_against_rpc_server().await?; - - if let Some(ledgers_to_extend) = self.ledgers_to_extend { - extend::Cmd { - key: self.key.clone(), - ledgers_to_extend, - config: self.config.clone(), - fee: self.fee.clone(), - ttl_ledger_only: false, - } - .run() - .await?; - } else { - println!("New ttl ledger: {expiration_ledger_seq}"); - } - - Ok(()) - } - - pub async fn run_against_rpc_server(&self) -> Result { - let network = self.config.get_network()?; - tracing::trace!(?network); - let entry_keys = self.key.parse_keys()?; - let network = &self.config.get_network()?; - let client = Client::new(&network.rpc_url)?; - let key = self.config.key_pair()?; - - // Get the account sequence number - let public_strkey = - stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); - let account_details = client.get_account(&public_strkey).await?; - let sequence: i64 = account_details.seq_num.into(); - - let tx = Transaction { - source_account: MuxedAccount::Ed25519(Uint256(key.verifying_key().to_bytes())), - fee: self.fee.fee, - seq_num: SequenceNumber(sequence + 1), - cond: Preconditions::None, - memo: Memo::None, - operations: vec![Operation { - source_account: None, - body: OperationBody::RestoreFootprint(RestoreFootprintOp { - ext: ExtensionPoint::V0, - }), - }] - .try_into()?, - ext: TransactionExt::V1(SorobanTransactionData { - ext: ExtensionPoint::V0, - resources: SorobanResources { - footprint: LedgerFootprint { - read_only: vec![].try_into()?, - read_write: entry_keys.try_into()?, - }, - instructions: 0, - read_bytes: 0, - write_bytes: 0, - }, - resource_fee: 0, - }), - }; - - let (result, meta, events) = client - .prepare_and_send_transaction(&tx, &key, &[], &network.network_passphrase, None, None) - .await?; - - tracing::trace!(?result); - tracing::trace!(?meta); - if !events.is_empty() { - tracing::info!("Events:\n {events:#?}"); - } - - // The transaction from core will succeed regardless of whether it actually found & - // restored the entry, so we have to inspect the result meta to tell if it worked or not. - let TransactionMeta::V3(TransactionMetaV3 { operations, .. }) = meta else { - return Err(Error::LedgerEntryNotFound); - }; - tracing::debug!("Operations:\nlen:{}\n{operations:#?}", operations.len()); - - // Simply check if there is exactly one entry here. We only support extending a single - // entry via this command (which we should fix separately, but). - if operations.len() == 0 { - return Err(Error::LedgerEntryNotFound); - } - - if operations.len() != 1 { - tracing::warn!( - "Unexpected number of operations: {}. Currently only handle one.", - operations[0].changes.len() - ); - } - parse_operations(&operations).ok_or(Error::MissingOperationResult) - } -} - -fn parse_operations(ops: &[OperationMeta]) -> Option { - ops.first().and_then(|op| { - op.changes.iter().find_map(|entry| match entry { - LedgerEntryChange::Updated(LedgerEntry { - data: - LedgerEntryData::Ttl(TtlEntry { - live_until_ledger_seq, - .. - }), - .. - }) - | LedgerEntryChange::Created(LedgerEntry { - data: - LedgerEntryData::Ttl(TtlEntry { - live_until_ledger_seq, - .. - }), - .. - }) => Some(*live_until_ledger_seq), - _ => None, - }) - }) -} diff --git a/cmd/soroban-cli/src/commands/events.rs b/cmd/soroban-cli/src/commands/events.rs deleted file mode 100644 index aa46bbe2..00000000 --- a/cmd/soroban-cli/src/commands/events.rs +++ /dev/null @@ -1,221 +0,0 @@ -use clap::{arg, command, Parser}; -use std::io; - -use soroban_env_host::xdr::{self, Limits, ReadXdr}; - -use super::{config::locator, network}; -use crate::{rpc, utils}; - -#[derive(Parser, Debug, Clone)] -#[group(skip)] -pub struct Cmd { - /// The first ledger sequence number in the range to pull events - /// https://developers.stellar.org/docs/encyclopedia/ledger-headers#ledger-sequence - #[arg(long, conflicts_with = "cursor", required_unless_present = "cursor")] - start_ledger: Option, - /// The cursor corresponding to the start of the event range. - #[arg( - long, - conflicts_with = "start_ledger", - required_unless_present = "start_ledger" - )] - cursor: Option, - /// Output formatting options for event stream - #[arg(long, value_enum, default_value = "pretty")] - output: OutputFormat, - /// The maximum number of events to display (defer to the server-defined limit). - #[arg(short, long, default_value = "10")] - count: usize, - /// A set of (up to 5) contract IDs to filter events on. This parameter can - /// be passed multiple times, e.g. `--id C123.. --id C456..`, or passed with - /// multiple parameters, e.g. `--id C123 C456`. - /// - /// Though the specification supports multiple filter objects (i.e. - /// combinations of type, IDs, and topics), only one set can be specified on - /// the command-line today, though that set can have multiple IDs/topics. - #[arg( - long = "id", - num_args = 1..=6, - help_heading = "FILTERS" - )] - contract_ids: Vec, - /// A set of (up to 4) topic filters to filter event topics on. A single - /// topic filter can contain 1-4 different segment filters, separated by - /// commas, with an asterisk (* character) indicating a wildcard segment. - /// - /// For example, this is one topic filter with two segments: - /// - /// --topic "AAAABQAAAAdDT1VOVEVSAA==,*" - /// - /// This is two topic filters with one and two segments each: - /// - /// --topic "AAAABQAAAAdDT1VOVEVSAA==" --topic '*,*' - /// - /// Note that all of these topic filters are combined with the contract IDs - /// into a single filter (i.e. combination of type, IDs, and topics). - #[arg( - long = "topic", - num_args = 1..=5, - help_heading = "FILTERS" - )] - topic_filters: Vec, - /// Specifies which type of contract events to display. - #[arg( - long = "type", - value_enum, - default_value = "all", - help_heading = "FILTERS" - )] - event_type: rpc::EventType, - #[command(flatten)] - locator: locator::Args, - #[command(flatten)] - network: network::Args, -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("cursor is not valid")] - InvalidCursor, - #[error("filepath does not exist: {path}")] - InvalidFile { path: String }, - #[error("filepath ({path}) cannot be read: {error}")] - CannotReadFile { path: String, error: String }, - #[error("cannot parse topic filter {topic} into 1-4 segments")] - InvalidTopicFilter { topic: String }, - #[error("invalid segment ({segment}) in topic filter ({topic}): {error}")] - InvalidSegment { - topic: String, - segment: String, - error: xdr::Error, - }, - #[error("cannot parse contract ID {contract_id}: {error}")] - InvalidContractId { - contract_id: String, - error: stellar_strkey::DecodeError, - }, - #[error("invalid JSON string: {error} ({debug})")] - InvalidJson { - debug: String, - error: serde_json::Error, - }, - #[error("invalid timestamp in event: {ts}")] - InvalidTimestamp { ts: String }, - #[error("missing start_ledger and cursor")] - MissingStartLedgerAndCursor, - #[error("missing target")] - MissingTarget, - #[error(transparent)] - Rpc(#[from] rpc::Error), - #[error(transparent)] - Generic(#[from] Box), - #[error(transparent)] - Io(#[from] io::Error), - #[error(transparent)] - Xdr(#[from] xdr::Error), - #[error(transparent)] - Serde(#[from] serde_json::Error), - #[error(transparent)] - Network(#[from] network::Error), - #[error(transparent)] - Locator(#[from] locator::Error), -} - -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, clap::ValueEnum)] -pub enum OutputFormat { - /// Colorful, human-oriented console output - Pretty, - /// Human-oriented console output without colors - Plain, - /// JSONified console output - Json, -} - -impl Cmd { - pub async fn run(&mut self) -> Result<(), Error> { - // Validate that topics are made up of segments. - for topic in &self.topic_filters { - for (i, segment) in topic.split(',').enumerate() { - if i > 4 { - return Err(Error::InvalidTopicFilter { - topic: topic.to_string(), - }); - } - - if segment != "*" { - if let Err(e) = xdr::ScVal::from_xdr_base64(segment, Limits::none()) { - return Err(Error::InvalidSegment { - topic: topic.to_string(), - segment: segment.to_string(), - error: e, - }); - } - } - } - } - - // Validate contract_ids - for id in &mut self.contract_ids { - utils::contract_id_from_str(id).map_err(|e| Error::InvalidContractId { - contract_id: id.clone(), - error: e, - })?; - } - - let response = self.run_against_rpc_server().await?; - - for event in &response.events { - match self.output { - // Should we pretty-print the JSON like we're doing here or just - // dump an event in raw JSON on each line? The latter is easier - // to consume programmatically. - OutputFormat::Json => { - println!( - "{}", - serde_json::to_string_pretty(&event).map_err(|e| { - Error::InvalidJson { - debug: format!("{event:#?}"), - error: e, - } - })?, - ); - } - OutputFormat::Plain => println!("{event}"), - OutputFormat::Pretty => event.pretty_print()?, - } - } - println!("Latest Ledger: {}", response.latest_ledger); - - Ok(()) - } - - async fn run_against_rpc_server(&self) -> Result { - let start = self.start()?; - let network = self.network.get(&self.locator)?; - - let client = rpc::Client::new(&network.rpc_url)?; - client - .verify_network_passphrase(Some(&network.network_passphrase)) - .await?; - client - .get_events( - start, - Some(self.event_type), - &self.contract_ids, - &self.topic_filters, - Some(self.count), - ) - .await - .map_err(Error::Rpc) - } - - fn start(&self) -> Result { - let start = match (self.start_ledger, self.cursor.clone()) { - (Some(start), _) => rpc::EventStart::Ledger(start), - (_, Some(c)) => rpc::EventStart::Cursor(c), - // should never happen because of required_unless_present flags - _ => return Err(Error::MissingStartLedgerAndCursor), - }; - Ok(start) - } -} diff --git a/cmd/soroban-cli/src/commands/global.rs b/cmd/soroban-cli/src/commands/global.rs deleted file mode 100644 index c606bd1b..00000000 --- a/cmd/soroban-cli/src/commands/global.rs +++ /dev/null @@ -1,61 +0,0 @@ -use clap::arg; -use std::path::PathBuf; - -use super::config; - -#[derive(Debug, clap::Args, Clone, Default)] -#[group(skip)] -#[allow(clippy::struct_excessive_bools)] -pub struct Args { - #[clap(flatten)] - pub locator: config::locator::Args, - - /// Filter logs output. To turn on "soroban_cli::log::footprint=debug" or off "=off". Can also use env var `RUST_LOG`. - #[arg(long, short = 'f')] - pub filter_logs: Vec, - - /// Do not write logs to stderr including `INFO` - #[arg(long, short = 'q')] - pub quiet: bool, - - /// Log DEBUG events - #[arg(long, short = 'v')] - pub verbose: bool, - - /// Log DEBUG and TRACE events - #[arg(long, visible_alias = "vv")] - pub very_verbose: bool, - - /// List installed plugins. E.g. `soroban-hello` - #[arg(long)] - pub list: bool, -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("reading file {filepath}: {error}")] - CannotReadLedgerFile { - filepath: PathBuf, - error: soroban_ledger_snapshot::Error, - }, - - #[error("committing file {filepath}: {error}")] - CannotCommitLedgerFile { - filepath: PathBuf, - error: soroban_ledger_snapshot::Error, - }, -} - -impl Args { - pub fn log_level(&self) -> Option { - if self.quiet { - None - } else if self.very_verbose { - Some(tracing::Level::TRACE) - } else if self.verbose { - Some(tracing::Level::DEBUG) - } else { - Some(tracing::Level::INFO) - } - } -} diff --git a/cmd/soroban-cli/src/commands/keys/add.rs b/cmd/soroban-cli/src/commands/keys/add.rs deleted file mode 100644 index 2868c737..00000000 --- a/cmd/soroban-cli/src/commands/keys/add.rs +++ /dev/null @@ -1,33 +0,0 @@ -use clap::command; - -use super::super::config::{locator, secret}; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Secret(#[from] secret::Error), - - #[error(transparent)] - Config(#[from] locator::Error), -} - -#[derive(Debug, clap::Parser, Clone)] -#[group(skip)] -pub struct Cmd { - /// Name of identity - pub name: String, - - #[command(flatten)] - pub secrets: secret::Args, - - #[command(flatten)] - pub config_locator: locator::Args, -} - -impl Cmd { - pub fn run(&self) -> Result<(), Error> { - Ok(self - .config_locator - .write_identity(&self.name, &self.secrets.read_secret()?)?) - } -} diff --git a/cmd/soroban-cli/src/commands/keys/address.rs b/cmd/soroban-cli/src/commands/keys/address.rs deleted file mode 100644 index d13381b4..00000000 --- a/cmd/soroban-cli/src/commands/keys/address.rs +++ /dev/null @@ -1,54 +0,0 @@ -use crate::commands::config::secret; - -use super::super::config::locator; -use clap::arg; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Config(#[from] locator::Error), - - #[error(transparent)] - Secret(#[from] secret::Error), - - #[error(transparent)] - StrKey(#[from] stellar_strkey::DecodeError), -} - -#[derive(Debug, clap::Parser, Clone)] -#[group(skip)] -pub struct Cmd { - /// Name of identity to lookup, default test identity used if not provided - pub name: String, - - /// If identity is a seed phrase use this hd path, default is 0 - #[arg(long)] - pub hd_path: Option, - - #[command(flatten)] - pub locator: locator::Args, -} - -impl Cmd { - pub fn run(&self) -> Result<(), Error> { - println!("{}", self.public_key()?); - Ok(()) - } - - pub fn private_key(&self) -> Result { - Ok(self - .locator - .read_identity(&self.name)? - .key_pair(self.hd_path)?) - } - - pub fn public_key(&self) -> Result { - if let Ok(key) = stellar_strkey::ed25519::PublicKey::from_string(&self.name) { - Ok(key) - } else { - Ok(stellar_strkey::ed25519::PublicKey::from_payload( - self.private_key()?.verifying_key().as_bytes(), - )?) - } - } -} diff --git a/cmd/soroban-cli/src/commands/keys/fund.rs b/cmd/soroban-cli/src/commands/keys/fund.rs deleted file mode 100644 index b6c088f1..00000000 --- a/cmd/soroban-cli/src/commands/keys/fund.rs +++ /dev/null @@ -1,34 +0,0 @@ -use clap::command; - -use crate::commands::network; - -use super::address; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Address(#[from] address::Error), - #[error(transparent)] - Network(#[from] network::Error), -} - -#[derive(Debug, clap::Parser, Clone)] -#[group(skip)] -pub struct Cmd { - #[command(flatten)] - pub network: network::Args, - /// Address to fund - #[command(flatten)] - pub address: address::Cmd, -} - -impl Cmd { - pub async fn run(&self) -> Result<(), Error> { - let addr = self.address.public_key()?; - self.network - .get(&self.address.locator)? - .fund_address(&addr) - .await?; - Ok(()) - } -} diff --git a/cmd/soroban-cli/src/commands/keys/generate.rs b/cmd/soroban-cli/src/commands/keys/generate.rs deleted file mode 100644 index 07782b21..00000000 --- a/cmd/soroban-cli/src/commands/keys/generate.rs +++ /dev/null @@ -1,75 +0,0 @@ -use clap::{arg, command}; - -use crate::commands::network; - -use super::super::config::{ - locator, - secret::{self, Secret}, -}; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Config(#[from] locator::Error), - #[error(transparent)] - Secret(#[from] secret::Error), - #[error(transparent)] - Network(#[from] network::Error), -} - -#[derive(Debug, clap::Parser, Clone)] -#[group(skip)] -pub struct Cmd { - /// Name of identity - pub name: String, - /// Do not fund address - #[arg(long)] - pub no_fund: bool, - /// Optional seed to use when generating seed phrase. - /// Random otherwise. - #[arg(long, conflicts_with = "default_seed")] - pub seed: Option, - - /// Output the generated identity as a secret key - #[arg(long, short = 's')] - pub as_secret: bool, - - #[command(flatten)] - pub config_locator: locator::Args, - - /// When generating a secret key, which hd_path should be used from the original seed_phrase. - #[arg(long)] - pub hd_path: Option, - - /// Generate the default seed phrase. Useful for testing. - /// Equivalent to --seed 0000000000000000 - #[arg(long, short = 'd', conflicts_with = "seed")] - pub default_seed: bool, - - #[command(flatten)] - pub network: network::Args, -} - -impl Cmd { - pub async fn run(&self) -> Result<(), Error> { - let seed_phrase = if self.default_seed { - Secret::test_seed_phrase() - } else { - Secret::from_seed(self.seed.as_deref()) - }?; - let secret = if self.as_secret { - seed_phrase.private_key(self.hd_path)?.into() - } else { - seed_phrase - }; - self.config_locator.write_identity(&self.name, &secret)?; - if !self.no_fund { - let addr = secret.public_key(self.hd_path)?; - let network = self.network.get(&self.config_locator)?; - network.fund_address(&addr).await.unwrap_or_else(|_| { - tracing::warn!("Failed to fund address: {addr} on at {}", network.rpc_url); - }); - } - Ok(()) - } -} diff --git a/cmd/soroban-cli/src/commands/keys/ls.rs b/cmd/soroban-cli/src/commands/keys/ls.rs deleted file mode 100644 index bc46ffcd..00000000 --- a/cmd/soroban-cli/src/commands/keys/ls.rs +++ /dev/null @@ -1,45 +0,0 @@ -use clap::command; - -use super::super::config::locator; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Config(#[from] locator::Error), -} - -#[derive(Debug, clap::Parser, Clone)] -#[group(skip)] -pub struct Cmd { - #[command(flatten)] - pub config_locator: locator::Args, - - #[arg(long, short = 'l')] - pub long: bool, -} - -impl Cmd { - pub fn run(&self) -> Result<(), Error> { - let res = if self.long { self.ls_l() } else { self.ls() }?.join("\n"); - println!("{res}"); - Ok(()) - } - - pub fn ls(&self) -> Result, Error> { - let mut list = self.config_locator.list_identities()?; - let test = "test".to_string(); - if !list.contains(&test) { - list.push(test); - } - Ok(list) - } - - pub fn ls_l(&self) -> Result, Error> { - Ok(self - .config_locator - .list_identities_long()? - .into_iter() - .map(|(name, location)| format!("{location}\nName: {name}\n")) - .collect::>()) - } -} diff --git a/cmd/soroban-cli/src/commands/keys/mod.rs b/cmd/soroban-cli/src/commands/keys/mod.rs deleted file mode 100644 index 42814092..00000000 --- a/cmd/soroban-cli/src/commands/keys/mod.rs +++ /dev/null @@ -1,63 +0,0 @@ -use clap::Parser; - -pub mod add; -pub mod address; -pub mod fund; -pub mod generate; -pub mod ls; -pub mod rm; -pub mod show; - -#[derive(Debug, Parser)] -pub enum Cmd { - /// Add a new identity (keypair, ledger, macOS keychain) - Add(add::Cmd), - /// Given an identity return its address (public key) - Address(address::Cmd), - /// Fund an identity on a test network - Fund(fund::Cmd), - /// Generate a new identity with a seed phrase, currently 12 words - Generate(generate::Cmd), - /// List identities - Ls(ls::Cmd), - /// Remove an identity - Rm(rm::Cmd), - /// Given an identity return its private key - Show(show::Cmd), -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Add(#[from] add::Error), - - #[error(transparent)] - Address(#[from] address::Error), - #[error(transparent)] - Fund(#[from] fund::Error), - - #[error(transparent)] - Generate(#[from] generate::Error), - #[error(transparent)] - Rm(#[from] rm::Error), - #[error(transparent)] - Ls(#[from] ls::Error), - - #[error(transparent)] - Show(#[from] show::Error), -} - -impl Cmd { - pub async fn run(&self) -> Result<(), Error> { - match self { - Cmd::Add(cmd) => cmd.run()?, - Cmd::Address(cmd) => cmd.run()?, - Cmd::Fund(cmd) => cmd.run().await?, - Cmd::Generate(cmd) => cmd.run().await?, - Cmd::Ls(cmd) => cmd.run()?, - Cmd::Rm(cmd) => cmd.run()?, - Cmd::Show(cmd) => cmd.run()?, - }; - Ok(()) - } -} diff --git a/cmd/soroban-cli/src/commands/keys/rm.rs b/cmd/soroban-cli/src/commands/keys/rm.rs deleted file mode 100644 index df48108d..00000000 --- a/cmd/soroban-cli/src/commands/keys/rm.rs +++ /dev/null @@ -1,25 +0,0 @@ -use clap::command; - -use super::super::config::locator; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Locator(#[from] locator::Error), -} - -#[derive(Debug, clap::Parser, Clone)] -#[group(skip)] -pub struct Cmd { - /// Identity to remove - pub name: String, - - #[command(flatten)] - pub config: locator::Args, -} - -impl Cmd { - pub fn run(&self) -> Result<(), Error> { - Ok(self.config.remove_identity(&self.name)?) - } -} diff --git a/cmd/soroban-cli/src/commands/keys/show.rs b/cmd/soroban-cli/src/commands/keys/show.rs deleted file mode 100644 index b99478cb..00000000 --- a/cmd/soroban-cli/src/commands/keys/show.rs +++ /dev/null @@ -1,43 +0,0 @@ -use clap::arg; - -use super::super::config::{locator, secret}; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Config(#[from] locator::Error), - - #[error(transparent)] - Secret(#[from] secret::Error), - - #[error(transparent)] - StrKey(#[from] stellar_strkey::DecodeError), -} - -#[derive(Debug, clap::Parser, Clone)] -#[group(skip)] -pub struct Cmd { - /// Name of identity to lookup, default is test identity - pub name: String, - - /// If identity is a seed phrase use this hd path, default is 0 - #[arg(long)] - pub hd_path: Option, - - #[command(flatten)] - pub locator: locator::Args, -} - -impl Cmd { - pub fn run(&self) -> Result<(), Error> { - println!("{}", self.private_key()?.to_string()); - Ok(()) - } - - pub fn private_key(&self) -> Result { - Ok(self - .locator - .read_identity(&self.name)? - .private_key(self.hd_path)?) - } -} diff --git a/cmd/soroban-cli/src/commands/lab/mod.rs b/cmd/soroban-cli/src/commands/lab/mod.rs deleted file mode 100644 index f405efe6..00000000 --- a/cmd/soroban-cli/src/commands/lab/mod.rs +++ /dev/null @@ -1,31 +0,0 @@ -use clap::Subcommand; -use stellar_xdr::cli as xdr; - -pub mod token; - -#[derive(Debug, Subcommand)] -pub enum Cmd { - /// Wrap, create, and manage token contracts - Token(token::Root), - - /// Decode xdr - Xdr(xdr::Root), -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Token(#[from] token::Error), - #[error(transparent)] - Xdr(#[from] xdr::Error), -} - -impl Cmd { - pub async fn run(&self) -> Result<(), Error> { - match &self { - Cmd::Token(token) => token.run().await?, - Cmd::Xdr(xdr) => xdr.run()?, - } - Ok(()) - } -} diff --git a/cmd/soroban-cli/src/commands/lab/token/mod.rs b/cmd/soroban-cli/src/commands/lab/token/mod.rs deleted file mode 100644 index bd7eacf3..00000000 --- a/cmd/soroban-cli/src/commands/lab/token/mod.rs +++ /dev/null @@ -1,38 +0,0 @@ -use std::fmt::Debug; - -use crate::commands::contract::{deploy, id}; -use clap::{Parser, Subcommand}; - -#[derive(Parser, Debug)] -pub struct Root { - #[clap(subcommand)] - cmd: Cmd, -} - -#[derive(Subcommand, Debug)] -enum Cmd { - /// Deploy a token contract to wrap an existing Stellar classic asset for smart contract usage - /// Deprecated, use `soroban contract deploy asset` instead - Wrap(deploy::asset::Cmd), - /// Compute the expected contract id for the given asset - /// Deprecated, use `soroban contract id asset` instead - Id(id::asset::Cmd), -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Wrap(#[from] deploy::asset::Error), - #[error(transparent)] - Id(#[from] id::asset::Error), -} - -impl Root { - pub async fn run(&self) -> Result<(), Error> { - match &self.cmd { - Cmd::Wrap(wrap) => wrap.run().await?, - Cmd::Id(id) => id.run()?, - } - Ok(()) - } -} diff --git a/cmd/soroban-cli/src/commands/mod.rs b/cmd/soroban-cli/src/commands/mod.rs deleted file mode 100644 index 952869af..00000000 --- a/cmd/soroban-cli/src/commands/mod.rs +++ /dev/null @@ -1,160 +0,0 @@ -use std::str::FromStr; - -use clap::{command, error::ErrorKind, CommandFactory, FromArgMatches, Parser}; - -pub mod completion; -pub mod config; -pub mod contract; -pub mod events; -pub mod global; -pub mod keys; -pub mod lab; -pub mod network; -pub mod plugin; -pub mod version; - -pub const HEADING_RPC: &str = "Options (RPC)"; -const ABOUT: &str = "Build, deploy, & interact with contracts; set identities to sign with; configure networks; generate keys; and more. - -Intro: https://soroban.stellar.org -CLI Reference: https://github.com/stellar/soroban-tools/tree/main/docs/soroban-cli-full-docs.md"; - -// long_about is shown when someone uses `--help`; short help when using `-h` -const LONG_ABOUT: &str = " - -The easiest way to get started is to generate a new identity: - - soroban config identity generate alice - -You can use identities with the `--source` flag in other commands later. - -Commands that relate to smart contract interactions are organized under the `contract` subcommand. List them: - - soroban contract --help - -A Soroban contract has its interface schema types embedded in the binary that gets deployed on-chain, making it possible to dynamically generate a custom CLI for each. `soroban contract invoke` makes use of this: - - soroban contract invoke --id CCR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OTE2 --source alice --network testnet -- \ - --help - -Anything after the `--` double dash (the \"slop\") is parsed as arguments to the contract-specific CLI, generated on-the-fly from the embedded schema. For the hello world example, with a function called `hello` that takes one string argument `to`, here's how you invoke it: - - soroban contract invoke --id CCR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OTE2 --source alice --network testnet -- \ - hello --to world - -Full CLI reference: https://github.com/stellar/soroban-tools/tree/main/docs/soroban-cli-full-docs.md"; - -#[derive(Parser, Debug)] -#[command( - name = "soroban", - about = ABOUT, - version = version::long(), - long_about = ABOUT.to_string() + LONG_ABOUT, - disable_help_subcommand = true, -)] -pub struct Root { - #[clap(flatten)] - pub global_args: global::Args, - - #[command(subcommand)] - pub cmd: Cmd, -} - -impl Root { - pub fn new() -> Result { - Self::try_parse().map_err(|e| { - if std::env::args().any(|s| s == "--list") { - let plugins = plugin::list().unwrap_or_default(); - if plugins.is_empty() { - println!("No Plugins installed. E.g. soroban-hello"); - } else { - println!("Installed Plugins:\n {}", plugins.join("\n ")); - } - std::process::exit(0); - } - match e.kind() { - ErrorKind::InvalidSubcommand => match plugin::run() { - Ok(()) => Error::Clap(e), - Err(e) => Error::Plugin(e), - }, - _ => Error::Clap(e), - } - }) - } - - pub fn from_arg_matches(itr: I) -> Result - where - I: IntoIterator, - T: Into + Clone, - { - Self::from_arg_matches_mut(&mut Self::command().get_matches_from(itr)) - } - pub async fn run(&mut self) -> Result<(), Error> { - match &mut self.cmd { - Cmd::Completion(completion) => completion.run(), - Cmd::Contract(contract) => contract.run(&self.global_args).await?, - Cmd::Events(events) => events.run().await?, - Cmd::Lab(lab) => lab.run().await?, - Cmd::Network(network) => network.run()?, - Cmd::Version(version) => version.run(), - Cmd::Keys(id) => id.run().await?, - Cmd::Config(c) => c.run().await?, - }; - Ok(()) - } -} - -impl FromStr for Root { - type Err = clap::Error; - - fn from_str(s: &str) -> Result { - Self::from_arg_matches(s.split_whitespace()) - } -} - -#[derive(Parser, Debug)] -pub enum Cmd { - /// Print shell completion code for the specified shell. - #[command(long_about = completion::LONG_ABOUT)] - Completion(completion::Cmd), - /// Deprecated, use `soroban keys` and `soroban network` instead - #[command(subcommand)] - Config(config::Cmd), - /// Tools for smart contract developers - #[command(subcommand)] - Contract(contract::Cmd), - /// Watch the network for contract events - Events(events::Cmd), - /// Create and manage identities including keys and addresses - #[command(subcommand)] - Keys(keys::Cmd), - /// Experiment with early features and expert tools - #[command(subcommand)] - Lab(lab::Cmd), - /// Start and configure networks - #[command(subcommand)] - Network(network::Cmd), - /// Print version information - Version(version::Cmd), -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - // TODO: stop using Debug for displaying errors - #[error(transparent)] - Contract(#[from] contract::Error), - #[error(transparent)] - Events(#[from] events::Error), - #[error(transparent)] - Keys(#[from] keys::Error), - #[error(transparent)] - Lab(#[from] lab::Error), - #[error(transparent)] - Config(#[from] config::Error), - #[error(transparent)] - Clap(#[from] clap::error::Error), - #[error(transparent)] - Plugin(#[from] plugin::Error), - #[error(transparent)] - Network(#[from] network::Error), -} diff --git a/cmd/soroban-cli/src/commands/network/add.rs b/cmd/soroban-cli/src/commands/network/add.rs deleted file mode 100644 index b6a2ddd3..00000000 --- a/cmd/soroban-cli/src/commands/network/add.rs +++ /dev/null @@ -1,32 +0,0 @@ -use super::super::config::{locator, secret}; -use clap::command; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Secret(#[from] secret::Error), - - #[error(transparent)] - Config(#[from] locator::Error), -} - -#[derive(Debug, clap::Parser, Clone)] -#[group(skip)] -pub struct Cmd { - /// Name of network - pub name: String, - - #[command(flatten)] - pub network: super::Network, - - #[command(flatten)] - pub config_locator: locator::Args, -} - -impl Cmd { - pub fn run(&self) -> Result<(), Error> { - Ok(self - .config_locator - .write_network(&self.name, &self.network)?) - } -} diff --git a/cmd/soroban-cli/src/commands/network/ls.rs b/cmd/soroban-cli/src/commands/network/ls.rs deleted file mode 100644 index cc542b3e..00000000 --- a/cmd/soroban-cli/src/commands/network/ls.rs +++ /dev/null @@ -1,44 +0,0 @@ -use clap::command; - -use super::locator; -use crate::commands::config::locator::Location; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Config(#[from] locator::Error), -} - -#[derive(Debug, clap::Parser, Clone)] -#[group(skip)] -pub struct Cmd { - #[command(flatten)] - pub config_locator: locator::Args, - /// Get more info about the networks - #[arg(long, short = 'l')] - pub long: bool, -} - -impl Cmd { - pub fn run(&self) -> Result<(), Error> { - let res = if self.long { self.ls_l() } else { self.ls() }?.join("\n"); - println!("{res}"); - Ok(()) - } - - pub fn ls(&self) -> Result, Error> { - Ok(self.config_locator.list_networks()?) - } - - pub fn ls_l(&self) -> Result, Error> { - Ok(self - .config_locator - .list_networks_long()? - .iter() - .filter_map(|(name, network, location)| { - (!self.config_locator.global || matches!(location, Location::Global(_))) - .then(|| Some(format!("{location}\nName: {name}\n{network:#?}\n")))? - }) - .collect()) - } -} diff --git a/cmd/soroban-cli/src/commands/network/mod.rs b/cmd/soroban-cli/src/commands/network/mod.rs deleted file mode 100644 index 22cba190..00000000 --- a/cmd/soroban-cli/src/commands/network/mod.rs +++ /dev/null @@ -1,197 +0,0 @@ -use std::str::FromStr; - -use clap::{arg, Parser}; -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use stellar_strkey::ed25519::PublicKey; - -use crate::{ - commands::HEADING_RPC, - rpc::{self, Client}, -}; - -use super::config::locator; - -pub mod add; -pub mod ls; -pub mod rm; - -#[derive(Debug, Parser)] -pub enum Cmd { - /// Add a new network - Add(add::Cmd), - /// Remove a network - Rm(rm::Cmd), - /// List networks - Ls(ls::Cmd), -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Add(#[from] add::Error), - - #[error(transparent)] - Rm(#[from] rm::Error), - - #[error(transparent)] - Ls(#[from] ls::Error), - - #[error(transparent)] - Config(#[from] locator::Error), - - #[error("network arg or rpc url and network passphrase are required if using the network")] - Network, - #[error(transparent)] - Rpc(#[from] rpc::Error), - #[error(transparent)] - Hyper(#[from] hyper::Error), - #[error("Failed to parse JSON from {0}, {1}")] - FailedToParseJSON(String, serde_json::Error), - #[error("Invalid URL {0}")] - InvalidUrl(String), - #[error("Inproper response {0}")] - InproperResponse(String), - #[error("Currently not supported on windows. Please visit:\n{0}")] - WindowsNotSupported(String), -} - -impl Cmd { - pub fn run(&self) -> Result<(), Error> { - match self { - Cmd::Add(cmd) => cmd.run()?, - Cmd::Rm(new) => new.run()?, - Cmd::Ls(cmd) => cmd.run()?, - }; - Ok(()) - } -} - -#[derive(Debug, clap::Args, Clone, Default)] -#[group(skip)] -pub struct Args { - /// RPC server endpoint - #[arg( - long = "rpc-url", - requires = "network_passphrase", - required_unless_present = "network", - env = "SOROBAN_RPC_URL", - help_heading = HEADING_RPC, - )] - pub rpc_url: Option, - /// Network passphrase to sign the transaction sent to the rpc server - #[arg( - long = "network-passphrase", - requires = "rpc_url", - required_unless_present = "network", - env = "SOROBAN_NETWORK_PASSPHRASE", - help_heading = HEADING_RPC, - )] - pub network_passphrase: Option, - /// Name of network to use from config - #[arg( - long, - required_unless_present = "rpc_url", - env = "SOROBAN_NETWORK", - help_heading = HEADING_RPC, - )] - pub network: Option, -} - -impl Args { - pub fn get(&self, locator: &locator::Args) -> Result { - if let Some(name) = self.network.as_deref() { - if let Ok(network) = locator.read_network(name) { - return Ok(network); - } - } - if let (Some(rpc_url), Some(network_passphrase)) = - (self.rpc_url.clone(), self.network_passphrase.clone()) - { - Ok(Network { - rpc_url, - network_passphrase, - }) - } else { - Err(Error::Network) - } - } -} - -#[derive(Debug, clap::Args, Serialize, Deserialize, Clone)] -#[group(skip)] -pub struct Network { - /// RPC server endpoint - #[arg( - long = "rpc-url", - env = "SOROBAN_RPC_URL", - help_heading = HEADING_RPC, - )] - pub rpc_url: String, - /// Network passphrase to sign the transaction sent to the rpc server - #[arg( - long, - env = "SOROBAN_NETWORK_PASSPHRASE", - help_heading = HEADING_RPC, - )] - pub network_passphrase: String, -} - -impl Network { - pub async fn helper_url(&self, addr: &str) -> Result { - tracing::debug!("address {addr:?}"); - let client = Client::new(&self.rpc_url)?; - let helper_url_root = client.friendbot_url().await?; - let uri = http::Uri::from_str(&helper_url_root) - .map_err(|_| Error::InvalidUrl(helper_url_root.to_string()))?; - http::Uri::from_str(&format!("{uri:?}?addr={addr}")) - .map_err(|_| Error::InvalidUrl(helper_url_root.to_string())) - } - - #[allow(clippy::similar_names)] - pub async fn fund_address(&self, addr: &PublicKey) -> Result<(), Error> { - let uri = self.helper_url(&addr.to_string()).await?; - tracing::debug!("URL {uri:?}"); - let response = match uri.scheme_str() { - Some("http") => hyper::Client::new().get(uri.clone()).await?, - Some("https") => { - #[cfg(target_os = "windows")] - { - return Err(Error::WindowsNotSupported(uri.to_string())); - } - #[cfg(not(target_os = "windows"))] - { - let https = hyper_tls::HttpsConnector::new(); - hyper::Client::builder() - .build::<_, hyper::Body>(https) - .get(uri.clone()) - .await? - } - } - _ => { - return Err(Error::InvalidUrl(uri.to_string())); - } - }; - let body = hyper::body::to_bytes(response.into_body()).await?; - let res = serde_json::from_slice::(&body) - .map_err(|e| Error::FailedToParseJSON(uri.to_string(), e))?; - tracing::debug!("{res:#?}"); - if let Some(detail) = res.get("detail").and_then(Value::as_str) { - if detail.contains("createAccountAlreadyExist") { - tracing::warn!("Account already exists"); - } - } else if res.get("successful").is_none() { - return Err(Error::InproperResponse(res.to_string())); - } - Ok(()) - } -} - -impl Network { - pub fn futurenet() -> Self { - Network { - rpc_url: "https://rpc-futurenet.stellar.org:443".to_owned(), - network_passphrase: "Test SDF Future Network ; October 2022".to_owned(), - } - } -} diff --git a/cmd/soroban-cli/src/commands/network/rm.rs b/cmd/soroban-cli/src/commands/network/rm.rs deleted file mode 100644 index 7051dc6b..00000000 --- a/cmd/soroban-cli/src/commands/network/rm.rs +++ /dev/null @@ -1,24 +0,0 @@ -use super::locator; -use clap::command; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Locator(#[from] locator::Error), -} - -#[derive(Debug, clap::Parser, Clone)] -#[group(skip)] -pub struct Cmd { - /// Network to remove - pub name: String, - - #[command(flatten)] - pub config: locator::Args, -} - -impl Cmd { - pub fn run(&self) -> Result<(), Error> { - Ok(self.config.remove_network(&self.name)?) - } -} diff --git a/cmd/soroban-cli/src/commands/plugin.rs b/cmd/soroban-cli/src/commands/plugin.rs deleted file mode 100644 index 27c191f0..00000000 --- a/cmd/soroban-cli/src/commands/plugin.rs +++ /dev/null @@ -1,96 +0,0 @@ -use std::process::Command; - -use clap::CommandFactory; -use which::which; - -use crate::{utils, Root}; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("Plugin not provided. Should be `soroban plugin` for a binary `soroban-plugin`")] - MissingSubcommand, - #[error(transparent)] - IO(#[from] std::io::Error), - #[error( - r#"error: no such command: `{0}` - - {1}View all installed plugins with `soroban --list`"# - )] - ExecutableNotFound(String, String), - #[error(transparent)] - Which(#[from] which::Error), - #[error(transparent)] - Regex(#[from] regex::Error), -} - -const SUBCOMMAND_TOLERANCE: f64 = 0.75; -const PLUGIN_TOLERANCE: f64 = 0.75; -const MIN_LENGTH: usize = 4; - -/// Tries to run a plugin, if the plugin's name is similar enough to any of the current subcommands return Ok. -/// Otherwise only errors can be returned because this process will exit with the plugin. -pub fn run() -> Result<(), Error> { - let (name, args) = { - let mut args = std::env::args().skip(1); - let name = args.next().ok_or(Error::MissingSubcommand)?; - (name, args) - }; - - if Root::command().get_subcommands().any(|c| { - let sc_name = c.get_name(); - sc_name.starts_with(&name) - || (name.len() >= MIN_LENGTH && strsim::jaro(sc_name, &name) >= SUBCOMMAND_TOLERANCE) - }) { - return Ok(()); - } - - let bin = which(format!("soroban-{name}")).map_err(|_| { - let suggestion = if let Ok(bins) = list() { - let suggested_name = bins - .iter() - .map(|b| (b, strsim::jaro_winkler(&name, b))) - .filter(|(_, i)| *i > PLUGIN_TOLERANCE) - .min_by(|a, b| a.1.total_cmp(&b.1)) - .map(|(a, _)| a.to_string()) - .unwrap_or_default(); - if suggested_name.is_empty() { - suggested_name - } else { - format!( - r#"Did you mean `{suggested_name}`? - "# - ) - } - } else { - String::new() - }; - Error::ExecutableNotFound(name, suggestion) - })?; - std::process::exit( - Command::new(bin) - .args(args) - .spawn()? - .wait()? - .code() - .unwrap(), - ); -} - -const MAX_HEX_LENGTH: usize = 10; - -pub fn list() -> Result, Error> { - let re_str = if cfg!(target_os = "windows") { - r"^soroban-.*.exe$" - } else { - r"^soroban-.*" - }; - let re = regex::Regex::new(re_str)?; - Ok(which::which_re(re)? - .filter_map(|b| { - let s = b.file_name()?.to_str()?; - Some(s.strip_suffix(".exe").unwrap_or(s).to_string()) - }) - .filter(|s| !(utils::is_hex_string(s) && s.len() > MAX_HEX_LENGTH)) - .map(|s| s.replace("soroban-", "")) - .collect()) -} diff --git a/cmd/soroban-cli/src/commands/version.rs b/cmd/soroban-cli/src/commands/version.rs deleted file mode 100644 index d9fc091b..00000000 --- a/cmd/soroban-cli/src/commands/version.rs +++ /dev/null @@ -1,32 +0,0 @@ -use clap::Parser; -use soroban_env_host::meta; -use std::fmt::Debug; - -const GIT_REVISION: &str = env!("GIT_REVISION"); - -#[derive(Parser, Debug, Clone)] -#[group(skip)] -pub struct Cmd; - -impl Cmd { - #[allow(clippy::unused_self)] - pub fn run(&self) { - println!("soroban {}", long()); - } -} - -pub fn long() -> String { - let env = soroban_env_host::VERSION; - let xdr = soroban_env_host::VERSION.xdr; - [ - format!("{} ({GIT_REVISION})", env!("CARGO_PKG_VERSION")), - format!("soroban-env {} ({})", env.pkg, env.rev), - format!("soroban-env interface version {}", meta::INTERFACE_VERSION), - format!( - "stellar-xdr {} ({}) -xdr curr ({})", - xdr.pkg, xdr.rev, xdr.xdr_curr, - ), - ] - .join("\n") -} diff --git a/cmd/soroban-cli/src/fee.rs b/cmd/soroban-cli/src/fee.rs deleted file mode 100644 index ee8b9614..00000000 --- a/cmd/soroban-cli/src/fee.rs +++ /dev/null @@ -1,16 +0,0 @@ -use crate::commands::HEADING_RPC; -use clap::arg; - -#[derive(Debug, clap::Args, Clone)] -#[group(skip)] -pub struct Args { - /// fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm - #[arg(long, default_value = "100", env = "SOROBAN_FEE", help_heading = HEADING_RPC)] - pub fee: u32, -} - -impl Default for Args { - fn default() -> Self { - Self { fee: 100 } - } -} diff --git a/cmd/soroban-cli/src/key.rs b/cmd/soroban-cli/src/key.rs deleted file mode 100644 index e9901abd..00000000 --- a/cmd/soroban-cli/src/key.rs +++ /dev/null @@ -1,110 +0,0 @@ -use clap::arg; -use soroban_env_host::xdr::{ - self, LedgerKey, LedgerKeyContractCode, LedgerKeyContractData, Limits, ReadXdr, ScAddress, - ScVal, -}; -use std::path::PathBuf; - -use crate::{ - commands::contract::Durability, - utils::{self}, - wasm, -}; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Spec(#[from] soroban_spec_tools::Error), - #[error(transparent)] - Xdr(#[from] xdr::Error), - #[error("cannot parse contract ID {0}: {1}")] - CannotParseContractId(String, stellar_strkey::DecodeError), - #[error(transparent)] - Wasm(#[from] wasm::Error), -} - -#[derive(Debug, clap::Args, Clone)] -#[group(skip)] -pub struct Args { - /// Contract ID to which owns the data entries. - /// If no keys provided the Contract's instance will be extended - #[arg( - long = "id", - required_unless_present = "wasm", - required_unless_present = "wasm_hash" - )] - pub contract_id: Option, - /// Storage key (symbols only) - #[arg(long = "key", conflicts_with = "key_xdr")] - pub key: Option>, - /// Storage key (base64-encoded XDR) - #[arg(long = "key-xdr", conflicts_with = "key")] - pub key_xdr: Option>, - /// Path to Wasm file of contract code to extend - #[arg( - long, - conflicts_with = "contract_id", - conflicts_with = "key", - conflicts_with = "key_xdr", - conflicts_with = "wasm_hash" - )] - pub wasm: Option, - /// Path to Wasm file of contract code to extend - #[arg( - long, - conflicts_with = "contract_id", - conflicts_with = "key", - conflicts_with = "key_xdr", - conflicts_with = "wasm" - )] - pub wasm_hash: Option, - /// Storage entry durability - #[arg(long, value_enum, required = true)] - pub durability: Durability, -} - -impl Args { - pub fn parse_keys(&self) -> Result, Error> { - let keys = if let Some(keys) = &self.key { - keys.iter() - .map(|key| { - Ok(soroban_spec_tools::from_string_primitive( - key, - &xdr::ScSpecTypeDef::Symbol, - )?) - }) - .collect::, Error>>()? - } else if let Some(keys) = &self.key_xdr { - keys.iter() - .map(|s| Ok(ScVal::from_xdr_base64(s, Limits::none())?)) - .collect::, Error>>()? - } else if let Some(wasm) = &self.wasm { - return Ok(vec![crate::wasm::Args { wasm: wasm.clone() }.try_into()?]); - } else if let Some(wasm_hash) = &self.wasm_hash { - return Ok(vec![LedgerKey::ContractCode(LedgerKeyContractCode { - hash: xdr::Hash( - utils::contract_id_from_str(wasm_hash) - .map_err(|e| Error::CannotParseContractId(wasm_hash.clone(), e))?, - ), - })]); - } else { - vec![ScVal::LedgerKeyContractInstance] - }; - let contract_id = contract_id(self.contract_id.as_ref().unwrap())?; - - Ok(keys - .into_iter() - .map(|key| { - LedgerKey::ContractData(LedgerKeyContractData { - contract: ScAddress::Contract(xdr::Hash(contract_id)), - durability: (&self.durability).into(), - key, - }) - }) - .collect()) - } -} - -fn contract_id(s: &str) -> Result<[u8; 32], Error> { - utils::contract_id_from_str(s).map_err(|e| Error::CannotParseContractId(s.to_string(), e)) -} diff --git a/cmd/soroban-cli/src/lib.rs b/cmd/soroban-cli/src/lib.rs deleted file mode 100644 index 3aad487c..00000000 --- a/cmd/soroban-cli/src/lib.rs +++ /dev/null @@ -1,53 +0,0 @@ -#![allow( - clippy::missing_errors_doc, - clippy::must_use_candidate, - clippy::missing_panics_doc -)] -pub mod commands; -pub mod fee; -pub mod key; -pub mod log; -pub mod rpc; -pub mod toid; -pub mod utils; -pub mod wasm; - -use std::path::Path; - -pub use commands::Root; - -pub fn parse_cmd(s: &str) -> Result -where - T: clap::CommandFactory + clap::FromArgMatches, -{ - let input = shlex::split(s).ok_or_else(|| { - clap::Error::raw( - clap::error::ErrorKind::InvalidValue, - format!("Invalid input for command:\n{s}"), - ) - })?; - T::from_arg_matches_mut(&mut T::command().no_binary_name(true).get_matches_from(input)) -} - -pub trait CommandParser { - fn parse(s: &str) -> Result; - - fn parse_arg_vec(s: &[&str]) -> Result; -} - -impl CommandParser for T -where - T: clap::CommandFactory + clap::FromArgMatches, -{ - fn parse(s: &str) -> Result { - parse_cmd(s) - } - - fn parse_arg_vec(args: &[&str]) -> Result { - T::from_arg_matches_mut(&mut T::command().no_binary_name(true).get_matches_from(args)) - } -} - -pub trait Pwd { - fn set_pwd(&mut self, pwd: &Path); -} diff --git a/cmd/soroban-cli/src/log.rs b/cmd/soroban-cli/src/log.rs deleted file mode 100644 index 16121982..00000000 --- a/cmd/soroban-cli/src/log.rs +++ /dev/null @@ -1,13 +0,0 @@ -pub mod auth; -pub mod budget; -pub mod cost; -pub mod diagnostic_event; -pub mod footprint; -pub mod host_event; - -pub use auth::*; -pub use budget::*; -pub use cost::*; -pub use diagnostic_event::*; -pub use footprint::*; -pub use host_event::*; diff --git a/cmd/soroban-cli/src/log/auth.rs b/cmd/soroban-cli/src/log/auth.rs deleted file mode 100644 index c37e7ed3..00000000 --- a/cmd/soroban-cli/src/log/auth.rs +++ /dev/null @@ -1,7 +0,0 @@ -use soroban_env_host::xdr::{SorobanAuthorizationEntry, VecM}; - -pub fn auth(auth: &[VecM]) { - if !auth.is_empty() { - tracing::debug!("{auth:#?}"); - } -} diff --git a/cmd/soroban-cli/src/log/budget.rs b/cmd/soroban-cli/src/log/budget.rs deleted file mode 100644 index 59ff4aad..00000000 --- a/cmd/soroban-cli/src/log/budget.rs +++ /dev/null @@ -1,5 +0,0 @@ -use soroban_env_host::budget::Budget; - -pub fn budget(budget: &Budget) { - tracing::debug!("{budget:#?}"); -} diff --git a/cmd/soroban-cli/src/log/cost.rs b/cmd/soroban-cli/src/log/cost.rs deleted file mode 100644 index 3e049a6c..00000000 --- a/cmd/soroban-cli/src/log/cost.rs +++ /dev/null @@ -1,27 +0,0 @@ -use soroban_env_host::xdr::SorobanResources; -use std::fmt::{Debug, Display}; - -struct Cost<'a>(&'a SorobanResources); - -impl Debug for Cost<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // TODO: Should we output the footprint here? - writeln!(f, "==================== Cost ====================")?; - writeln!(f, "CPU used: {}", self.0.instructions,)?; - writeln!(f, "Bytes read: {}", self.0.read_bytes,)?; - writeln!(f, "Bytes written: {}", self.0.write_bytes,)?; - writeln!(f, "==============================================")?; - Ok(()) - } -} - -impl Display for Cost<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Debug::fmt(self, f) - } -} - -pub fn cost(resources: &SorobanResources) { - let cost = Cost(resources); - tracing::debug!(?cost); -} diff --git a/cmd/soroban-cli/src/log/diagnostic_event.rs b/cmd/soroban-cli/src/log/diagnostic_event.rs deleted file mode 100644 index 68af67a4..00000000 --- a/cmd/soroban-cli/src/log/diagnostic_event.rs +++ /dev/null @@ -1,11 +0,0 @@ -pub fn diagnostic_events(events: &[impl std::fmt::Debug], level: tracing::Level) { - for (i, event) in events.iter().enumerate() { - if level == tracing::Level::TRACE { - tracing::trace!("{i}: {event:#?}"); - } else if level == tracing::Level::INFO { - tracing::info!("{i}: {event:#?}"); - } else if level == tracing::Level::ERROR { - tracing::error!("{i}: {event:#?}"); - } - } -} diff --git a/cmd/soroban-cli/src/log/footprint.rs b/cmd/soroban-cli/src/log/footprint.rs deleted file mode 100644 index bfbc9f7a..00000000 --- a/cmd/soroban-cli/src/log/footprint.rs +++ /dev/null @@ -1,5 +0,0 @@ -use soroban_env_host::xdr::LedgerFootprint; - -pub fn footprint(footprint: &LedgerFootprint) { - tracing::debug!("{footprint:#?}"); -} diff --git a/cmd/soroban-cli/src/log/host_event.rs b/cmd/soroban-cli/src/log/host_event.rs deleted file mode 100644 index 4238a74c..00000000 --- a/cmd/soroban-cli/src/log/host_event.rs +++ /dev/null @@ -1,7 +0,0 @@ -use soroban_env_host::events::HostEvent; - -pub fn host_events(events: &[HostEvent]) { - for (i, event) in events.iter().enumerate() { - tracing::info!("{i}: {event:#?}"); - } -} diff --git a/cmd/soroban-cli/src/rpc/fixtures/event_response.json b/cmd/soroban-cli/src/rpc/fixtures/event_response.json deleted file mode 100644 index 6f520fdf..00000000 --- a/cmd/soroban-cli/src/rpc/fixtures/event_response.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "events": [{ - "eventType": "system", - "ledger": "43601283", - "ledgerClosedAt": "2022-11-16T16:10:41Z", - "contractId": "CDR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OO5Z", - "id": "0164090849041387521-0000000003", - "pagingToken": "164090849041387521-3", - "topic": [ - "AAAABQAAAAh0cmFuc2Zlcg==", - "AAAAAQB6Mcc=" - ], - "value": "AAAABQAAAApHaWJNb255UGxzAAA=" - }, { - "eventType": "contract", - "ledger": "43601284", - "ledgerClosedAt": "2022-11-16T16:10:41Z", - "contractId": "CDR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OO5Z", - "id": "0164090849041387521-0000000003", - "pagingToken": "164090849041387521-3", - "topic": [ - "AAAABQAAAAh0cmFuc2Zlcg==", - "AAAAAQB6Mcc=" - ], - "value": "AAAABQAAAApHaWJNb255UGxzAAA=" - }, { - "eventType": "system", - "ledger": "43601285", - "ledgerClosedAt": "2022-11-16T16:10:41Z", - "contractId": "CCR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OTE2", - "id": "0164090849041387521-0000000003", - "pagingToken": "164090849041387521-3", - "topic": [ - "AAAABQAAAAh0cmFuc2Zlcg==", - "AAAAAQB6Mcc=" - ], - "value": "AAAABQAAAApHaWJNb255UGxzAAA=" - }] -} \ No newline at end of file diff --git a/cmd/soroban-cli/src/rpc/mod.rs b/cmd/soroban-cli/src/rpc/mod.rs deleted file mode 100644 index 53542629..00000000 --- a/cmd/soroban-cli/src/rpc/mod.rs +++ /dev/null @@ -1,1141 +0,0 @@ -use http::{uri::Authority, Uri}; -use itertools::Itertools; -use jsonrpsee_core::params::ObjectParams; -use jsonrpsee_core::{self, client::ClientT, rpc_params}; -use jsonrpsee_http_client::{HeaderMap, HttpClient, HttpClientBuilder}; -use serde_aux::prelude::{ - deserialize_default_from_null, deserialize_number_from_string, - deserialize_option_number_from_string, -}; -use soroban_env_host::xdr::{ - self, AccountEntry, AccountId, ContractDataEntry, DiagnosticEvent, Error as XdrError, - LedgerEntryData, LedgerFootprint, LedgerKey, LedgerKeyAccount, Limited, PublicKey, ReadXdr, - SorobanAuthorizationEntry, SorobanResources, SorobanTransactionData, Transaction, - TransactionEnvelope, TransactionMeta, TransactionMetaV3, TransactionResult, Uint256, VecM, - WriteXdr, -}; -use soroban_sdk::token; -use soroban_sdk::xdr::Limits; -use std::{ - fmt::Display, - str::FromStr, - time::{Duration, Instant}, -}; -use termcolor::{Color, ColorChoice, StandardStream, WriteColor}; -use termcolor_output::colored; -use tokio::time::sleep; - -use crate::utils::contract_spec; - -mod txn; - -const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION"); - -pub type LogEvents = fn( - footprint: &LedgerFootprint, - auth: &[VecM], - events: &[DiagnosticEvent], -) -> (); - -pub type LogResources = fn(resources: &SorobanResources) -> (); - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - InvalidAddress(#[from] stellar_strkey::DecodeError), - #[error("invalid response from server")] - InvalidResponse, - #[error("provided network passphrase {expected:?} does not match the server: {server:?}")] - InvalidNetworkPassphrase { expected: String, server: String }, - #[error("xdr processing error: {0}")] - Xdr(#[from] XdrError), - #[error("invalid rpc url: {0}")] - InvalidRpcUrl(http::uri::InvalidUri), - #[error("invalid rpc url: {0}")] - InvalidRpcUrlFromUriParts(http::uri::InvalidUriParts), - #[error("invalid friendbot url: {0}")] - InvalidUrl(String), - #[error("jsonrpc error: {0}")] - JsonRpc(#[from] jsonrpsee_core::Error), - #[error("json decoding error: {0}")] - Serde(#[from] serde_json::Error), - #[error("transaction failed: {0}")] - TransactionFailed(String), - #[error("transaction submission failed: {0}")] - TransactionSubmissionFailed(String), - #[error("expected transaction status: {0}")] - UnexpectedTransactionStatus(String), - #[error("transaction submission timeout")] - TransactionSubmissionTimeout, - #[error("transaction simulation failed: {0}")] - TransactionSimulationFailed(String), - #[error("{0} not found: {1}")] - NotFound(String, String), - #[error("Missing result in successful response")] - MissingResult, - #[error("Failed to read Error response from server")] - MissingError, - #[error("Missing signing key for account {address}")] - MissingSignerForAddress { address: String }, - #[error("cursor is not valid")] - InvalidCursor, - #[error("unexpected ({length}) simulate transaction result length")] - UnexpectedSimulateTransactionResultSize { length: usize }, - #[error("unexpected ({count}) number of operations")] - UnexpectedOperationCount { count: usize }, - #[error("Transaction contains unsupported operation type")] - UnsupportedOperationType, - #[error("unexpected contract code data type: {0:?}")] - UnexpectedContractCodeDataType(LedgerEntryData), - #[error(transparent)] - CouldNotParseContractSpec(#[from] contract_spec::Error), - #[error("unexpected contract code got token")] - UnexpectedToken(ContractDataEntry), - #[error(transparent)] - Spec(#[from] soroban_spec::read::FromWasmError), - #[error(transparent)] - SpecBase64(#[from] soroban_spec::read::ParseSpecBase64Error), - #[error("Fee was too large {0}")] - LargeFee(u64), - #[error("Cannot authorize raw transactions")] - CannotAuthorizeRawTransaction, -} - -#[derive(serde::Deserialize, serde::Serialize, Debug)] -pub struct SendTransactionResponse { - pub hash: String, - pub status: String, - #[serde( - rename = "errorResultXdr", - skip_serializing_if = "Option::is_none", - default - )] - pub error_result_xdr: Option, - #[serde(rename = "latestLedger")] - pub latest_ledger: u32, - #[serde( - rename = "latestLedgerCloseTime", - deserialize_with = "deserialize_number_from_string" - )] - pub latest_ledger_close_time: u32, -} - -#[derive(serde::Deserialize, serde::Serialize, Debug)] -pub struct GetTransactionResponseRaw { - pub status: String, - #[serde( - rename = "envelopeXdr", - skip_serializing_if = "Option::is_none", - default - )] - pub envelope_xdr: Option, - #[serde(rename = "resultXdr", skip_serializing_if = "Option::is_none", default)] - pub result_xdr: Option, - #[serde( - rename = "resultMetaXdr", - skip_serializing_if = "Option::is_none", - default - )] - pub result_meta_xdr: Option, - // TODO: add ledger info and application order -} - -#[derive(serde::Deserialize, serde::Serialize, Debug)] -pub struct GetTransactionResponse { - pub status: String, - pub envelope: Option, - pub result: Option, - pub result_meta: Option, -} - -impl TryInto for GetTransactionResponseRaw { - type Error = xdr::Error; - - fn try_into(self) -> Result { - Ok(GetTransactionResponse { - status: self.status, - envelope: self - .envelope_xdr - .map(|v| ReadXdr::from_xdr_base64(v, Limits::none())) - .transpose()?, - result: self - .result_xdr - .map(|v| ReadXdr::from_xdr_base64(v, Limits::none())) - .transpose()?, - result_meta: self - .result_meta_xdr - .map(|v| ReadXdr::from_xdr_base64(v, Limits::none())) - .transpose()?, - }) - } -} - -#[derive(serde::Deserialize, serde::Serialize, Debug)] -pub struct LedgerEntryResult { - pub key: String, - pub xdr: String, - #[serde(rename = "lastModifiedLedgerSeq")] - pub last_modified_ledger: u32, - #[serde( - rename = "liveUntilLedgerSeq", - skip_serializing_if = "Option::is_none", - deserialize_with = "deserialize_option_number_from_string", - default - )] - pub live_until_ledger_seq_ledger_seq: Option, -} - -#[derive(serde::Deserialize, serde::Serialize, Debug)] -pub struct GetLedgerEntriesResponse { - pub entries: Option>, - #[serde(rename = "latestLedger")] - pub latest_ledger: i64, -} - -#[derive(serde::Deserialize, serde::Serialize, Debug)] -pub struct GetNetworkResponse { - #[serde( - rename = "friendbotUrl", - skip_serializing_if = "Option::is_none", - default - )] - pub friendbot_url: Option, - pub passphrase: String, - #[serde(rename = "protocolVersion")] - pub protocol_version: u32, -} - -#[derive(serde::Deserialize, serde::Serialize, Debug)] -pub struct GetLatestLedgerResponse { - pub id: String, - #[serde(rename = "protocolVersion")] - pub protocol_version: u32, - pub sequence: u32, -} - -#[derive(serde::Deserialize, serde::Serialize, Debug, Default)] -pub struct Cost { - #[serde( - rename = "cpuInsns", - deserialize_with = "deserialize_number_from_string" - )] - pub cpu_insns: u64, - #[serde( - rename = "memBytes", - deserialize_with = "deserialize_number_from_string" - )] - pub mem_bytes: u64, -} - -#[derive(serde::Deserialize, serde::Serialize, Debug)] -pub struct SimulateHostFunctionResultRaw { - #[serde(deserialize_with = "deserialize_default_from_null")] - pub auth: Vec, - pub xdr: String, -} - -#[derive(Debug)] -pub struct SimulateHostFunctionResult { - pub auth: Vec, - pub xdr: xdr::ScVal, -} - -#[derive(serde::Deserialize, serde::Serialize, Debug, Default)] -pub struct SimulateTransactionResponse { - #[serde( - rename = "minResourceFee", - deserialize_with = "deserialize_number_from_string", - default - )] - pub min_resource_fee: u64, - #[serde(default)] - pub cost: Cost, - #[serde(skip_serializing_if = "Vec::is_empty", default)] - pub results: Vec, - #[serde(rename = "transactionData", default)] - pub transaction_data: String, - #[serde( - deserialize_with = "deserialize_default_from_null", - skip_serializing_if = "Vec::is_empty", - default - )] - pub events: Vec, - #[serde( - rename = "restorePreamble", - skip_serializing_if = "Option::is_none", - default - )] - pub restore_preamble: Option, - #[serde(rename = "latestLedger")] - pub latest_ledger: u32, - #[serde(skip_serializing_if = "Option::is_none", default)] - pub error: Option, -} - -impl SimulateTransactionResponse { - pub fn results(&self) -> Result, Error> { - self.results - .iter() - .map(|r| { - Ok(SimulateHostFunctionResult { - auth: r - .auth - .iter() - .map(|a| { - Ok(SorobanAuthorizationEntry::from_xdr_base64( - a, - Limits::none(), - )?) - }) - .collect::>()?, - xdr: xdr::ScVal::from_xdr_base64(&r.xdr, Limits::none())?, - }) - }) - .collect() - } - - pub fn events(&self) -> Result, Error> { - self.events - .iter() - .map(|e| Ok(DiagnosticEvent::from_xdr_base64(e, Limits::none())?)) - .collect() - } - - pub fn transaction_data(&self) -> Result { - Ok(SorobanTransactionData::from_xdr_base64( - &self.transaction_data, - Limits::none(), - )?) - } -} - -#[derive(serde::Deserialize, serde::Serialize, Debug, Default)] -pub struct RestorePreamble { - #[serde(rename = "transactionData")] - pub transaction_data: String, - #[serde( - rename = "minResourceFee", - deserialize_with = "deserialize_number_from_string" - )] - pub min_resource_fee: u64, -} - -#[derive(serde::Deserialize, serde::Serialize, Debug)] -pub struct GetEventsResponse { - #[serde(deserialize_with = "deserialize_default_from_null")] - pub events: Vec, - #[serde(rename = "latestLedger")] - pub latest_ledger: u32, -} - -// Determines whether or not a particular filter matches a topic based on the -// same semantics as the RPC server: -// -// - for an exact segment match, the filter is a base64-encoded ScVal -// - for a wildcard, single-segment match, the string "*" matches exactly one -// segment -// -// The expectation is that a `filter` is a comma-separated list of segments that -// has previously been validated, and `topic` is the list of segments applicable -// for this event. -// -// [API -// Reference](https://docs.google.com/document/d/1TZUDgo_3zPz7TiPMMHVW_mtogjLyPL0plvzGMsxSz6A/edit#bookmark=id.35t97rnag3tx) -// [Code -// Reference](https://github.com/stellar/soroban-tools/blob/bac1be79e8c2590c9c35ad8a0168aab0ae2b4171/cmd/soroban-rpc/internal/methods/get_events.go#L182-L203) -pub fn does_topic_match(topic: &[String], filter: &[String]) -> bool { - filter.len() == topic.len() - && filter - .iter() - .enumerate() - .all(|(i, s)| *s == "*" || topic[i] == *s) -} - -#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] -pub struct Event { - #[serde(rename = "type")] - pub event_type: String, - - pub ledger: u32, - #[serde(rename = "ledgerClosedAt")] - pub ledger_closed_at: String, - - pub id: String, - #[serde(rename = "pagingToken")] - pub paging_token: String, - - #[serde(rename = "contractId")] - pub contract_id: String, - pub topic: Vec, - pub value: String, -} - -impl Display for Event { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - writeln!( - f, - "Event {} [{}]:", - self.paging_token, - self.event_type.to_ascii_uppercase() - )?; - writeln!( - f, - " Ledger: {} (closed at {})", - self.ledger, self.ledger_closed_at - )?; - writeln!(f, " Contract: {}", self.contract_id)?; - writeln!(f, " Topics:")?; - for topic in &self.topic { - let scval = - xdr::ScVal::from_xdr_base64(topic, Limits::none()).map_err(|_| std::fmt::Error)?; - writeln!(f, " {scval:?}")?; - } - let scval = xdr::ScVal::from_xdr_base64(&self.value, Limits::none()) - .map_err(|_| std::fmt::Error)?; - writeln!(f, " Value: {scval:?}") - } -} - -impl Event { - pub fn parse_cursor(&self) -> Result<(u64, i32), Error> { - parse_cursor(&self.id) - } - - pub fn pretty_print(&self) -> Result<(), Box> { - let mut stdout = StandardStream::stdout(ColorChoice::Auto); - if !stdout.supports_color() { - println!("{self}"); - return Ok(()); - } - - let color = match self.event_type.as_str() { - "system" => Color::Yellow, - _ => Color::Blue, - }; - colored!( - stdout, - "{}Event{} {}{}{} [{}{}{}{}]:\n", - bold!(true), - bold!(false), - fg!(Some(Color::Green)), - self.paging_token, - reset!(), - bold!(true), - fg!(Some(color)), - self.event_type.to_ascii_uppercase(), - reset!(), - )?; - - colored!( - stdout, - " Ledger: {}{}{} (closed at {}{}{})\n", - fg!(Some(Color::Green)), - self.ledger, - reset!(), - fg!(Some(Color::Green)), - self.ledger_closed_at, - reset!(), - )?; - - colored!( - stdout, - " Contract: {}{}{}\n", - fg!(Some(Color::Green)), - self.contract_id, - reset!(), - )?; - - colored!(stdout, " Topics:\n")?; - for topic in &self.topic { - let scval = xdr::ScVal::from_xdr_base64(topic, Limits::none())?; - colored!( - stdout, - " {}{:?}{}\n", - fg!(Some(Color::Green)), - scval, - reset!(), - )?; - } - - let scval = xdr::ScVal::from_xdr_base64(&self.value, Limits::none())?; - colored!( - stdout, - " Value: {}{:?}{}\n", - fg!(Some(Color::Green)), - scval, - reset!(), - )?; - - Ok(()) - } -} - -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, clap::ValueEnum)] -pub enum EventType { - All, - Contract, - System, -} - -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -pub enum EventStart { - Ledger(u32), - Cursor(String), -} - -#[derive(Debug)] -pub struct FullLedgerEntry { - pub key: LedgerKey, - pub val: LedgerEntryData, - pub last_modified_ledger: u32, - pub live_until_ledger_seq: u32, -} - -#[derive(Debug)] -pub struct FullLedgerEntries { - pub entries: Vec, - pub latest_ledger: i64, -} - -pub struct Client { - base_url: String, -} - -impl Client { - pub fn new(base_url: &str) -> Result { - // Add the port to the base URL if there is no port explicitly included - // in the URL and the scheme allows us to infer a default port. - // Jsonrpsee requires a port to always be present even if one can be - // inferred. This may change: https://github.com/paritytech/jsonrpsee/issues/1048. - let uri = base_url.parse::().map_err(Error::InvalidRpcUrl)?; - let mut parts = uri.into_parts(); - if let (Some(scheme), Some(authority)) = (&parts.scheme, &parts.authority) { - if authority.port().is_none() { - let port = match scheme.as_str() { - "http" => Some(80), - "https" => Some(443), - _ => None, - }; - if let Some(port) = port { - let host = authority.host(); - parts.authority = Some( - Authority::from_str(&format!("{host}:{port}")) - .map_err(Error::InvalidRpcUrl)?, - ); - } - } - } - let uri = Uri::from_parts(parts).map_err(Error::InvalidRpcUrlFromUriParts)?; - tracing::trace!(?uri); - Ok(Self { - base_url: uri.to_string(), - }) - } - - fn client(&self) -> Result { - let url = self.base_url.clone(); - let mut headers = HeaderMap::new(); - headers.insert("X-Client-Name", "soroban-cli".parse().unwrap()); - let version = VERSION.unwrap_or("devel"); - headers.insert("X-Client-Version", version.parse().unwrap()); - Ok(HttpClientBuilder::default() - .set_headers(headers) - .build(url)?) - } - - pub async fn friendbot_url(&self) -> Result { - let network = self.get_network().await?; - tracing::trace!("{network:#?}"); - network.friendbot_url.ok_or_else(|| { - Error::NotFound( - "Friendbot".to_string(), - "Friendbot is not available on this network".to_string(), - ) - }) - } - - pub async fn verify_network_passphrase(&self, expected: Option<&str>) -> Result { - let server = self.get_network().await?.passphrase; - if let Some(expected) = expected { - if expected != server { - return Err(Error::InvalidNetworkPassphrase { - expected: expected.to_string(), - server, - }); - } - } - Ok(server) - } - - pub async fn get_network(&self) -> Result { - tracing::trace!("Getting network"); - Ok(self.client()?.request("getNetwork", rpc_params![]).await?) - } - - pub async fn get_latest_ledger(&self) -> Result { - tracing::trace!("Getting latest ledger"); - Ok(self - .client()? - .request("getLatestLedger", rpc_params![]) - .await?) - } - - pub async fn get_account(&self, address: &str) -> Result { - tracing::trace!("Getting address {}", address); - let key = LedgerKey::Account(LedgerKeyAccount { - account_id: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256( - stellar_strkey::ed25519::PublicKey::from_string(address)?.0, - ))), - }); - let keys = Vec::from([key]); - let response = self.get_ledger_entries(&keys).await?; - let entries = response.entries.unwrap_or_default(); - if entries.is_empty() { - return Err(Error::NotFound( - "Account".to_string(), - format!( - r#"{address} -Might need to fund account like: -soroban config identity fund {address} --network -soroban config identity fund {address} --helper-url "# - ), - )); - } - let ledger_entry = &entries[0]; - let mut read = Limited::new(ledger_entry.xdr.as_bytes(), Limits::none()); - if let LedgerEntryData::Account(entry) = LedgerEntryData::read_xdr_base64(&mut read)? { - tracing::trace!(account=?entry); - Ok(entry) - } else { - Err(Error::InvalidResponse) - } - } - - pub async fn send_transaction( - &self, - tx: &TransactionEnvelope, - ) -> Result<(TransactionResult, TransactionMeta, Vec), Error> { - let client = self.client()?; - tracing::trace!("Sending:\n{tx:#?}"); - let SendTransactionResponse { - hash, - error_result_xdr, - status, - .. - } = client - .request( - "sendTransaction", - rpc_params![tx.to_xdr_base64(Limits::none())?], - ) - .await - .map_err(|err| { - Error::TransactionSubmissionFailed(format!("No status yet:\n {err:#?}")) - })?; - - if status == "ERROR" { - let error = error_result_xdr - .ok_or(Error::MissingError) - .and_then(|x| { - TransactionResult::read_xdr_base64(&mut Limited::new( - x.as_bytes(), - Limits::none(), - )) - .map_err(|_| Error::InvalidResponse) - }) - .map(|r| r.result); - tracing::error!("TXN failed:\n {error:#?}"); - return Err(Error::TransactionSubmissionFailed(format!("{:#?}", error?))); - } - // even if status == "success" we need to query the transaction status in order to get the result - - // Poll the transaction status - let start = Instant::now(); - loop { - let response: GetTransactionResponse = self.get_transaction(&hash).await?.try_into()?; - match response.status.as_str() { - "SUCCESS" => { - // TODO: the caller should probably be printing this - tracing::trace!("{response:#?}"); - let GetTransactionResponse { - result, - result_meta, - .. - } = response; - let meta = result_meta.ok_or(Error::MissingResult)?; - let events = extract_events(&meta); - return Ok((result.ok_or(Error::MissingResult)?, meta, events)); - } - "FAILED" => { - tracing::error!("{response:#?}"); - // TODO: provide a more elaborate error - return Err(Error::TransactionSubmissionFailed(format!( - "{:#?}", - response.result - ))); - } - "NOT_FOUND" => (), - _ => { - return Err(Error::UnexpectedTransactionStatus(response.status)); - } - }; - let duration = start.elapsed(); - // TODO: parameterize the timeout instead of using a magic constant - if duration.as_secs() > 10 { - return Err(Error::TransactionSubmissionTimeout); - } - sleep(Duration::from_secs(1)).await; - } - } - - pub async fn simulate_transaction( - &self, - tx: &TransactionEnvelope, - ) -> Result { - tracing::trace!("Simulating:\n{tx:#?}"); - let base64_tx = tx.to_xdr_base64(Limits::none())?; - let mut builder = ObjectParams::new(); - builder.insert("transaction", base64_tx)?; - let response: SimulateTransactionResponse = self - .client()? - .request("simulateTransaction", builder) - .await?; - tracing::trace!("Simulation response:\n {response:#?}"); - match response.error { - None => Ok(response), - Some(e) => { - crate::log::diagnostic_events(&response.events, tracing::Level::ERROR); - Err(Error::TransactionSimulationFailed(e)) - } - } - } - - pub async fn prepare_and_send_transaction( - &self, - tx_without_preflight: &Transaction, - source_key: &ed25519_dalek::SigningKey, - signers: &[ed25519_dalek::SigningKey], - network_passphrase: &str, - log_events: Option, - log_resources: Option, - ) -> Result<(TransactionResult, TransactionMeta, Vec), Error> { - let txn = txn::Assembled::new(tx_without_preflight, self).await?; - let seq_num = txn.sim_res().latest_ledger + 60; //5 min; - let authorized = txn - .handle_restore(self, source_key, network_passphrase) - .await? - .authorize(self, source_key, signers, seq_num, network_passphrase) - .await?; - authorized.log(log_events, log_resources)?; - let tx = authorized.sign(source_key, network_passphrase)?; - self.send_transaction(&tx).await - } - - pub async fn get_transaction(&self, tx_id: &str) -> Result { - Ok(self - .client()? - .request("getTransaction", rpc_params![tx_id]) - .await?) - } - - pub async fn get_ledger_entries( - &self, - keys: &[LedgerKey], - ) -> Result { - let mut base64_keys: Vec = vec![]; - for k in keys { - let base64_result = k.to_xdr_base64(Limits::none()); - if base64_result.is_err() { - return Err(Error::Xdr(XdrError::Invalid)); - } - base64_keys.push(k.to_xdr_base64(Limits::none()).unwrap()); - } - Ok(self - .client()? - .request("getLedgerEntries", rpc_params![base64_keys]) - .await?) - } - - pub async fn get_full_ledger_entries( - &self, - ledger_keys: &[LedgerKey], - ) -> Result { - let keys = ledger_keys - .iter() - .filter(|key| !matches!(key, LedgerKey::Ttl(_))) - .map(Clone::clone) - .collect::>(); - tracing::trace!("keys: {keys:#?}"); - let GetLedgerEntriesResponse { - entries, - latest_ledger, - } = self.get_ledger_entries(&keys).await?; - tracing::trace!("raw: {entries:#?}"); - let entries = entries - .unwrap_or_default() - .iter() - .map( - |LedgerEntryResult { - key, - xdr, - last_modified_ledger, - live_until_ledger_seq_ledger_seq, - }| { - Ok(FullLedgerEntry { - key: LedgerKey::from_xdr_base64(key, Limits::none())?, - val: LedgerEntryData::from_xdr_base64(xdr, Limits::none())?, - live_until_ledger_seq: live_until_ledger_seq_ledger_seq.unwrap_or_default(), - last_modified_ledger: *last_modified_ledger, - }) - }, - ) - .collect::, Error>>()?; - tracing::trace!("parsed: {entries:#?}"); - Ok(FullLedgerEntries { - entries, - latest_ledger, - }) - } - - pub async fn get_events( - &self, - start: EventStart, - event_type: Option, - contract_ids: &[String], - topics: &[String], - limit: Option, - ) -> Result { - let mut filters = serde_json::Map::new(); - - event_type - .and_then(|t| match t { - EventType::All => None, // all is the default, so avoid incl. the param - EventType::Contract => Some("contract"), - EventType::System => Some("system"), - }) - .map(|t| filters.insert("type".to_string(), t.into())); - - filters.insert("topics".to_string(), topics.into()); - filters.insert("contractIds".to_string(), contract_ids.into()); - - let mut pagination = serde_json::Map::new(); - if let Some(limit) = limit { - pagination.insert("limit".to_string(), limit.into()); - } - - let mut oparams = ObjectParams::new(); - match start { - EventStart::Ledger(l) => oparams.insert("startLedger", l)?, - EventStart::Cursor(c) => { - pagination.insert("cursor".to_string(), c.into()); - } - }; - oparams.insert("filters", vec![filters])?; - oparams.insert("pagination", pagination)?; - - Ok(self.client()?.request("getEvents", oparams).await?) - } - - pub async fn get_contract_data( - &self, - contract_id: &[u8; 32], - ) -> Result { - // Get the contract from the network - let contract_key = LedgerKey::ContractData(xdr::LedgerKeyContractData { - contract: xdr::ScAddress::Contract(xdr::Hash(*contract_id)), - key: xdr::ScVal::LedgerKeyContractInstance, - durability: xdr::ContractDataDurability::Persistent, - }); - let contract_ref = self.get_ledger_entries(&[contract_key]).await?; - let entries = contract_ref.entries.unwrap_or_default(); - if entries.is_empty() { - let contract_address = stellar_strkey::Contract(*contract_id).to_string(); - return Err(Error::NotFound("Contract".to_string(), contract_address)); - } - let contract_ref_entry = &entries[0]; - match LedgerEntryData::from_xdr_base64(&contract_ref_entry.xdr, Limits::none())? { - LedgerEntryData::ContractData(contract_data) => Ok(contract_data), - scval => Err(Error::UnexpectedContractCodeDataType(scval)), - } - } - - pub async fn get_remote_wasm(&self, contract_id: &[u8; 32]) -> Result, Error> { - match self.get_contract_data(contract_id).await? { - xdr::ContractDataEntry { - val: - xdr::ScVal::ContractInstance(xdr::ScContractInstance { - executable: xdr::ContractExecutable::Wasm(hash), - .. - }), - .. - } => self.get_remote_wasm_from_hash(hash).await, - scval => Err(Error::UnexpectedToken(scval)), - } - } - - pub async fn get_remote_wasm_from_hash(&self, hash: xdr::Hash) -> Result, Error> { - let code_key = LedgerKey::ContractCode(xdr::LedgerKeyContractCode { hash: hash.clone() }); - let contract_data = self.get_ledger_entries(&[code_key]).await?; - let entries = contract_data.entries.unwrap_or_default(); - if entries.is_empty() { - return Err(Error::NotFound( - "Contract Code".to_string(), - hex::encode(hash), - )); - } - let contract_data_entry = &entries[0]; - match LedgerEntryData::from_xdr_base64(&contract_data_entry.xdr, Limits::none())? { - LedgerEntryData::ContractCode(xdr::ContractCodeEntry { code, .. }) => Ok(code.into()), - scval => Err(Error::UnexpectedContractCodeDataType(scval)), - } - } - - pub async fn get_remote_contract_spec( - &self, - contract_id: &[u8; 32], - ) -> Result, Error> { - let contract_data = self.get_contract_data(contract_id).await?; - match contract_data.val { - xdr::ScVal::ContractInstance(xdr::ScContractInstance { - executable: xdr::ContractExecutable::Wasm(hash), - .. - }) => Ok(contract_spec::ContractSpec::new( - &self.get_remote_wasm_from_hash(hash).await?, - ) - .map_err(Error::CouldNotParseContractSpec)? - .spec), - xdr::ScVal::ContractInstance(xdr::ScContractInstance { - executable: xdr::ContractExecutable::StellarAsset, - .. - }) => Ok(soroban_spec::read::parse_raw( - &token::StellarAssetSpec::spec_xdr(), - )?), - _ => Err(Error::Xdr(XdrError::Invalid)), - } - } -} - -fn extract_events(tx_meta: &TransactionMeta) -> Vec { - match tx_meta { - TransactionMeta::V3(TransactionMetaV3 { - soroban_meta: Some(meta), - .. - }) => { - // NOTE: we assume there can only be one operation, since we only send one - if meta.diagnostic_events.len() == 1 { - meta.diagnostic_events.clone().into() - } else if meta.events.len() == 1 { - meta.events - .iter() - .map(|e| DiagnosticEvent { - in_successful_contract_call: true, - event: e.clone(), - }) - .collect() - } else { - Vec::new() - } - } - _ => Vec::new(), - } -} - -pub fn parse_cursor(c: &str) -> Result<(u64, i32), Error> { - let (toid_part, event_index) = c.split('-').collect_tuple().ok_or(Error::InvalidCursor)?; - let toid_part: u64 = toid_part.parse().map_err(|_| Error::InvalidCursor)?; - let start_index: i32 = event_index.parse().map_err(|_| Error::InvalidCursor)?; - Ok((toid_part, start_index)) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn simulation_transaction_response_parsing() { - let s = r#"{ - "minResourceFee": "100000000", - "cost": { "cpuInsns": "1000", "memBytes": "1000" }, - "transactionData": "", - "latestLedger": 1234 - }"#; - - let resp: SimulateTransactionResponse = serde_json::from_str(s).unwrap(); - assert_eq!(resp.min_resource_fee, 100_000_000); - } - - #[test] - fn simulation_transaction_response_parsing_mostly_empty() { - let s = r#"{ - "latestLedger": 1234 - }"#; - - let resp: SimulateTransactionResponse = serde_json::from_str(s).unwrap(); - assert_eq!(resp.latest_ledger, 1_234); - } - - #[test] - fn test_rpc_url_default_ports() { - // Default ports are added. - let client = Client::new("http://example.com").unwrap(); - assert_eq!(client.base_url, "http://example.com:80/"); - let client = Client::new("https://example.com").unwrap(); - assert_eq!(client.base_url, "https://example.com:443/"); - - // Ports are not added when already present. - let client = Client::new("http://example.com:8080").unwrap(); - assert_eq!(client.base_url, "http://example.com:8080/"); - let client = Client::new("https://example.com:8080").unwrap(); - assert_eq!(client.base_url, "https://example.com:8080/"); - - // Paths are not modified. - let client = Client::new("http://example.com/a/b/c").unwrap(); - assert_eq!(client.base_url, "http://example.com:80/a/b/c"); - let client = Client::new("https://example.com/a/b/c").unwrap(); - assert_eq!(client.base_url, "https://example.com:443/a/b/c"); - let client = Client::new("http://example.com/a/b/c/").unwrap(); - assert_eq!(client.base_url, "http://example.com:80/a/b/c/"); - let client = Client::new("https://example.com/a/b/c/").unwrap(); - assert_eq!(client.base_url, "https://example.com:443/a/b/c/"); - let client = Client::new("http://example.com/a/b:80/c/").unwrap(); - assert_eq!(client.base_url, "http://example.com:80/a/b:80/c/"); - let client = Client::new("https://example.com/a/b:80/c/").unwrap(); - assert_eq!(client.base_url, "https://example.com:443/a/b:80/c/"); - } - - #[test] - // Taken from [RPC server - // tests](https://github.com/stellar/soroban-tools/blob/main/cmd/soroban-rpc/internal/methods/get_events_test.go#L21). - fn test_does_topic_match() { - struct TestCase<'a> { - name: &'a str, - filter: Vec<&'a str>, - includes: Vec>, - excludes: Vec>, - } - - let xfer = "AAAABQAAAAh0cmFuc2Zlcg=="; - let number = "AAAAAQB6Mcc="; - let star = "*"; - - for tc in vec![ - // No filter means match nothing. - TestCase { - name: "", - filter: vec![], - includes: vec![], - excludes: vec![vec![xfer]], - }, - // "*" should match "transfer/" but not "transfer/transfer" or - // "transfer/amount", because * is specified as a SINGLE segment - // wildcard. - TestCase { - name: "*", - filter: vec![star], - includes: vec![vec![xfer]], - excludes: vec![vec![xfer, xfer], vec![xfer, number]], - }, - // "*/transfer" should match anything preceding "transfer", but - // nothing that isn't exactly two segments long. - TestCase { - name: "*/transfer", - filter: vec![star, xfer], - includes: vec![vec![number, xfer], vec![xfer, xfer]], - excludes: vec![ - vec![number], - vec![number, number], - vec![number, xfer, number], - vec![xfer], - vec![xfer, number], - vec![xfer, xfer, xfer], - ], - }, - // The inverse case of before: "transfer/*" should match any single - // segment after a segment that is exactly "transfer", but no - // additional segments. - TestCase { - name: "transfer/*", - filter: vec![xfer, star], - includes: vec![vec![xfer, number], vec![xfer, xfer]], - excludes: vec![ - vec![number], - vec![number, number], - vec![number, xfer, number], - vec![xfer], - vec![number, xfer], - vec![xfer, xfer, xfer], - ], - }, - // Here, we extend to exactly two wild segments after transfer. - TestCase { - name: "transfer/*/*", - filter: vec![xfer, star, star], - includes: vec![vec![xfer, number, number], vec![xfer, xfer, xfer]], - excludes: vec![ - vec![number], - vec![number, number], - vec![number, xfer], - vec![number, xfer, number, number], - vec![xfer], - vec![xfer, xfer, xfer, xfer], - ], - }, - // Here, we ensure wildcards can be in the middle of a filter: only - // exact matches happen on the ends, while the middle can be - // anything. - TestCase { - name: "transfer/*/number", - filter: vec![xfer, star, number], - includes: vec![vec![xfer, number, number], vec![xfer, xfer, number]], - excludes: vec![ - vec![number], - vec![number, number], - vec![number, number, number], - vec![number, xfer, number], - vec![xfer], - vec![number, xfer], - vec![xfer, xfer, xfer], - vec![xfer, number, xfer], - ], - }, - ] { - for topic in tc.includes { - assert!( - does_topic_match( - &topic - .iter() - .map(std::string::ToString::to_string) - .collect::>(), - &tc.filter - .iter() - .map(std::string::ToString::to_string) - .collect::>() - ), - "test: {}, topic ({:?}) should be matched by filter ({:?})", - tc.name, - topic, - tc.filter - ); - } - - for topic in tc.excludes { - assert!( - !does_topic_match( - // make deep copies of the vecs - &topic - .iter() - .map(std::string::ToString::to_string) - .collect::>(), - &tc.filter - .iter() - .map(std::string::ToString::to_string) - .collect::>() - ), - "test: {}, topic ({:?}) should NOT be matched by filter ({:?})", - tc.name, - topic, - tc.filter - ); - } - } - } -} diff --git a/cmd/soroban-cli/src/rpc/txn.rs b/cmd/soroban-cli/src/rpc/txn.rs deleted file mode 100644 index 9e36938d..00000000 --- a/cmd/soroban-cli/src/rpc/txn.rs +++ /dev/null @@ -1,610 +0,0 @@ -use ed25519_dalek::Signer; -use sha2::{Digest, Sha256}; -use soroban_env_host::xdr::{ - self, AccountId, DecoratedSignature, ExtensionPoint, Hash, HashIdPreimage, - HashIdPreimageSorobanAuthorization, InvokeHostFunctionOp, Limits, Memo, Operation, - OperationBody, Preconditions, PublicKey, ReadXdr, RestoreFootprintOp, ScAddress, ScMap, - ScSymbol, ScVal, Signature, SignatureHint, SorobanAddressCredentials, - SorobanAuthorizationEntry, SorobanAuthorizedFunction, SorobanCredentials, SorobanResources, - SorobanTransactionData, Transaction, TransactionEnvelope, TransactionExt, - TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, - TransactionV1Envelope, Uint256, VecM, WriteXdr, -}; - -use crate::rpc::{Client, Error, RestorePreamble, SimulateTransactionResponse}; - -use super::{LogEvents, LogResources}; - -pub struct Assembled { - txn: Transaction, - sim_res: SimulateTransactionResponse, -} - -impl Assembled { - pub async fn new(txn: &Transaction, client: &Client) -> Result { - let sim_res = Self::simulate(txn, client).await?; - let txn = assemble(txn, &sim_res)?; - Ok(Self { txn, sim_res }) - } - - pub fn hash(&self, network_passphrase: &str) -> Result<[u8; 32], xdr::Error> { - let signature_payload = TransactionSignaturePayload { - network_id: Hash(Sha256::digest(network_passphrase).into()), - tagged_transaction: TransactionSignaturePayloadTaggedTransaction::Tx(self.txn.clone()), - }; - Ok(Sha256::digest(signature_payload.to_xdr(Limits::none())?).into()) - } - - pub fn sign( - self, - key: &ed25519_dalek::SigningKey, - network_passphrase: &str, - ) -> Result { - let tx = self.txn(); - let tx_hash = self.hash(network_passphrase)?; - let tx_signature = key.sign(&tx_hash); - - let decorated_signature = DecoratedSignature { - hint: SignatureHint(key.verifying_key().to_bytes()[28..].try_into()?), - signature: Signature(tx_signature.to_bytes().try_into()?), - }; - - Ok(TransactionEnvelope::Tx(TransactionV1Envelope { - tx: tx.clone(), - signatures: vec![decorated_signature].try_into()?, - })) - } - - pub async fn simulate( - tx: &Transaction, - client: &Client, - ) -> Result { - client - .simulate_transaction(&TransactionEnvelope::Tx(TransactionV1Envelope { - tx: tx.clone(), - signatures: VecM::default(), - })) - .await - } - - pub async fn handle_restore( - self, - client: &Client, - source_key: &ed25519_dalek::SigningKey, - network_passphrase: &str, - ) -> Result { - if let Some(restore_preamble) = &self.sim_res.restore_preamble { - // Build and submit the restore transaction - client - .send_transaction( - &Assembled::new(&restore(self.txn(), restore_preamble)?, client) - .await? - .sign(source_key, network_passphrase)?, - ) - .await?; - Ok(self.bump_seq_num()) - } else { - Ok(self) - } - } - - pub fn txn(&self) -> &Transaction { - &self.txn - } - - pub fn sim_res(&self) -> &SimulateTransactionResponse { - &self.sim_res - } - - pub async fn authorize( - self, - client: &Client, - source_key: &ed25519_dalek::SigningKey, - signers: &[ed25519_dalek::SigningKey], - seq_num: u32, - network_passphrase: &str, - ) -> Result { - if let Some(txn) = sign_soroban_authorizations( - self.txn(), - source_key, - signers, - seq_num, - network_passphrase, - )? { - Self::new(&txn, client).await - } else { - Ok(self) - } - } - - pub fn bump_seq_num(mut self) -> Self { - self.txn.seq_num.0 += 1; - self - } - - pub fn auth(&self) -> VecM { - self.txn - .operations - .first() - .and_then(|op| match op.body { - OperationBody::InvokeHostFunction(ref body) => (matches!( - body.auth.first().map(|x| &x.root_invocation.function), - Some(&SorobanAuthorizedFunction::ContractFn(_)) - )) - .then_some(body.auth.clone()), - _ => None, - }) - .unwrap_or_default() - } - - pub fn log( - &self, - log_events: Option, - log_resources: Option, - ) -> Result<(), Error> { - if let TransactionExt::V1(SorobanTransactionData { - resources: resources @ SorobanResources { footprint, .. }, - .. - }) = &self.txn.ext - { - if let Some(log) = log_resources { - log(resources); - } - if let Some(log) = log_events { - log(footprint, &[self.auth()], &self.sim_res.events()?); - }; - } - Ok(()) - } -} - -// Apply the result of a simulateTransaction onto a transaction envelope, preparing it for -// submission to the network. -pub fn assemble( - raw: &Transaction, - simulation: &SimulateTransactionResponse, -) -> Result { - let mut tx = raw.clone(); - - // Right now simulate.results is one-result-per-function, and assumes there is only one - // operation in the txn, so we need to enforce that here. I (Paul) think that is a bug - // in soroban-rpc.simulateTransaction design, and we should fix it there. - // TODO: We should to better handling so non-soroban txns can be a passthrough here. - if tx.operations.len() != 1 { - return Err(Error::UnexpectedOperationCount { - count: tx.operations.len(), - }); - } - - let transaction_data = simulation.transaction_data()?; - - let mut op = tx.operations[0].clone(); - if let OperationBody::InvokeHostFunction(ref mut body) = &mut op.body { - if body.auth.is_empty() { - if simulation.results.len() != 1 { - return Err(Error::UnexpectedSimulateTransactionResultSize { - length: simulation.results.len(), - }); - } - - let auths = simulation - .results - .iter() - .map(|r| { - VecM::try_from( - r.auth - .iter() - .map(|v| SorobanAuthorizationEntry::from_xdr_base64(v, Limits::none())) - .collect::, _>>()?, - ) - }) - .collect::, _>>()?; - if !auths.is_empty() { - body.auth = auths[0].clone(); - } - } - } - - // update the fees of the actual transaction to meet the minimum resource fees. - let classic_transaction_fees = crate::fee::Args::default().fee; - // Pad the fees up by 15% for a bit of wiggle room. - tx.fee = (tx.fee.max( - classic_transaction_fees - + u32::try_from(simulation.min_resource_fee) - .map_err(|_| Error::LargeFee(simulation.min_resource_fee))?, - ) * 115) - / 100; - - tx.operations = vec![op].try_into()?; - tx.ext = TransactionExt::V1(transaction_data); - Ok(tx) -} - -// Use the given source_key and signers, to sign all SorobanAuthorizationEntry's in the given -// transaction. If unable to sign, return an error. -fn sign_soroban_authorizations( - raw: &Transaction, - source_key: &ed25519_dalek::SigningKey, - signers: &[ed25519_dalek::SigningKey], - signature_expiration_ledger: u32, - network_passphrase: &str, -) -> Result, Error> { - let mut tx = raw.clone(); - let mut op = match tx.operations.as_slice() { - [op @ Operation { - body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { auth, .. }), - .. - }] if matches!( - auth.first().map(|x| &x.root_invocation.function), - Some(&SorobanAuthorizedFunction::ContractFn(_)) - ) => - { - op.clone() - } - _ => return Ok(None), - }; - - let Operation { - body: OperationBody::InvokeHostFunction(ref mut body), - .. - } = op - else { - return Ok(None); - }; - - let network_id = Hash(Sha256::digest(network_passphrase.as_bytes()).into()); - - let verification_key = source_key.verifying_key(); - let source_address = verification_key.as_bytes(); - - let signed_auths = body - .auth - .as_slice() - .iter() - .map(|raw_auth| { - let mut auth = raw_auth.clone(); - let SorobanAuthorizationEntry { - credentials: SorobanCredentials::Address(ref mut credentials), - .. - } = auth - else { - // Doesn't need special signing - return Ok(auth); - }; - let SorobanAddressCredentials { ref address, .. } = credentials; - - // See if we have a signer for this authorizationEntry - // If not, then we Error - let needle = match address { - ScAddress::Account(AccountId(PublicKey::PublicKeyTypeEd25519(Uint256(ref a)))) => a, - ScAddress::Contract(Hash(c)) => { - // This address is for a contract. This means we're using a custom - // smart-contract account. Currently the CLI doesn't support that yet. - return Err(Error::MissingSignerForAddress { - address: stellar_strkey::Strkey::Contract(stellar_strkey::Contract(*c)) - .to_string(), - }); - } - }; - let signer = if let Some(s) = signers - .iter() - .find(|s| needle == s.verifying_key().as_bytes()) - { - s - } else if needle == source_address { - // This is the source address, so we can sign it - source_key - } else { - // We don't have a signer for this address - return Err(Error::MissingSignerForAddress { - address: stellar_strkey::Strkey::PublicKeyEd25519( - stellar_strkey::ed25519::PublicKey(*needle), - ) - .to_string(), - }); - }; - - sign_soroban_authorization_entry( - raw_auth, - signer, - signature_expiration_ledger, - &network_id, - ) - }) - .collect::, Error>>()?; - - body.auth = signed_auths.try_into()?; - tx.operations = vec![op].try_into()?; - Ok(Some(tx)) -} - -fn sign_soroban_authorization_entry( - raw: &SorobanAuthorizationEntry, - signer: &ed25519_dalek::SigningKey, - signature_expiration_ledger: u32, - network_id: &Hash, -) -> Result { - let mut auth = raw.clone(); - let SorobanAuthorizationEntry { - credentials: SorobanCredentials::Address(ref mut credentials), - .. - } = auth - else { - // Doesn't need special signing - return Ok(auth); - }; - let SorobanAddressCredentials { nonce, .. } = credentials; - - let preimage = HashIdPreimage::SorobanAuthorization(HashIdPreimageSorobanAuthorization { - network_id: network_id.clone(), - invocation: auth.root_invocation.clone(), - nonce: *nonce, - signature_expiration_ledger, - }) - .to_xdr(Limits::none())?; - - let payload = Sha256::digest(preimage); - let signature = signer.sign(&payload); - - let map = ScMap::sorted_from(vec![ - ( - ScVal::Symbol(ScSymbol("public_key".try_into()?)), - ScVal::Bytes( - signer - .verifying_key() - .to_bytes() - .to_vec() - .try_into() - .map_err(Error::Xdr)?, - ), - ), - ( - ScVal::Symbol(ScSymbol("signature".try_into()?)), - ScVal::Bytes( - signature - .to_bytes() - .to_vec() - .try_into() - .map_err(Error::Xdr)?, - ), - ), - ]) - .map_err(Error::Xdr)?; - credentials.signature = ScVal::Vec(Some( - vec![ScVal::Map(Some(map))].try_into().map_err(Error::Xdr)?, - )); - credentials.signature_expiration_ledger = signature_expiration_ledger; - auth.credentials = SorobanCredentials::Address(credentials.clone()); - Ok(auth) -} - -pub fn restore(parent: &Transaction, restore: &RestorePreamble) -> Result { - let transaction_data = - SorobanTransactionData::from_xdr_base64(&restore.transaction_data, Limits::none())?; - let fee = u32::try_from(restore.min_resource_fee) - .map_err(|_| Error::LargeFee(restore.min_resource_fee))?; - Ok(Transaction { - source_account: parent.source_account.clone(), - fee: parent - .fee - .checked_add(fee) - .ok_or(Error::LargeFee(restore.min_resource_fee))?, - seq_num: parent.seq_num.clone(), - cond: Preconditions::None, - memo: Memo::None, - operations: vec![Operation { - source_account: None, - body: OperationBody::RestoreFootprint(RestoreFootprintOp { - ext: ExtensionPoint::V0, - }), - }] - .try_into() - .unwrap(), - ext: TransactionExt::V1(transaction_data), - }) -} - -#[cfg(test)] -mod tests { - use super::*; - - use super::super::SimulateHostFunctionResultRaw; - use soroban_env_host::xdr::{ - self, AccountId, ChangeTrustAsset, ChangeTrustOp, ExtensionPoint, Hash, HostFunction, - InvokeContractArgs, InvokeHostFunctionOp, LedgerFootprint, Memo, MuxedAccount, Operation, - Preconditions, PublicKey, ScAddress, ScSymbol, ScVal, SequenceNumber, - SorobanAuthorizedFunction, SorobanAuthorizedInvocation, SorobanResources, - SorobanTransactionData, Uint256, WriteXdr, - }; - use stellar_strkey::ed25519::PublicKey as Ed25519PublicKey; - - const SOURCE: &str = "GBZXN7PIRZGNMHGA7MUUUF4GWPY5AYPV6LY4UV2GL6VJGIQRXFDNMADI"; - - fn transaction_data() -> SorobanTransactionData { - SorobanTransactionData { - resources: SorobanResources { - footprint: LedgerFootprint { - read_only: VecM::default(), - read_write: VecM::default(), - }, - instructions: 0, - read_bytes: 5, - write_bytes: 0, - }, - resource_fee: 0, - ext: ExtensionPoint::V0, - } - } - - fn simulation_response() -> SimulateTransactionResponse { - let source_bytes = Ed25519PublicKey::from_string(SOURCE).unwrap().0; - let fn_auth = &SorobanAuthorizationEntry { - credentials: xdr::SorobanCredentials::Address(xdr::SorobanAddressCredentials { - address: ScAddress::Account(AccountId(PublicKey::PublicKeyTypeEd25519(Uint256( - source_bytes, - )))), - nonce: 0, - signature_expiration_ledger: 0, - signature: ScVal::Void, - }), - root_invocation: SorobanAuthorizedInvocation { - function: SorobanAuthorizedFunction::ContractFn(InvokeContractArgs { - contract_address: ScAddress::Contract(Hash([0; 32])), - function_name: ScSymbol("fn".try_into().unwrap()), - args: VecM::default(), - }), - sub_invocations: VecM::default(), - }, - }; - - SimulateTransactionResponse { - min_resource_fee: 115, - latest_ledger: 3, - results: vec![SimulateHostFunctionResultRaw { - auth: vec![fn_auth.to_xdr_base64(Limits::none()).unwrap()], - xdr: ScVal::U32(0).to_xdr_base64(Limits::none()).unwrap(), - }], - transaction_data: transaction_data().to_xdr_base64(Limits::none()).unwrap(), - ..Default::default() - } - } - - fn single_contract_fn_transaction() -> Transaction { - let source_bytes = Ed25519PublicKey::from_string(SOURCE).unwrap().0; - Transaction { - source_account: MuxedAccount::Ed25519(Uint256(source_bytes)), - fee: 100, - seq_num: SequenceNumber(0), - cond: Preconditions::None, - memo: Memo::None, - operations: vec![Operation { - source_account: None, - body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { - host_function: HostFunction::InvokeContract(InvokeContractArgs { - contract_address: ScAddress::Contract(Hash([0x0; 32])), - function_name: ScSymbol::default(), - args: VecM::default(), - }), - auth: VecM::default(), - }), - }] - .try_into() - .unwrap(), - ext: TransactionExt::V0, - } - } - - #[test] - fn test_assemble_transaction_updates_tx_data_from_simulation_response() { - let sim = simulation_response(); - let txn = single_contract_fn_transaction(); - let Ok(result) = assemble(&txn, &sim) else { - panic!("assemble failed"); - }; - - // validate it auto updated the tx fees from sim response fees - // since it was greater than tx.fee - assert_eq!(247, result.fee); - - // validate it updated sorobantransactiondata block in the tx ext - assert_eq!(TransactionExt::V1(transaction_data()), result.ext); - } - - #[test] - fn test_assemble_transaction_adds_the_auth_to_the_host_function() { - let sim = simulation_response(); - let txn = single_contract_fn_transaction(); - let Ok(result) = assemble(&txn, &sim) else { - panic!("assemble failed"); - }; - - assert_eq!(1, result.operations.len()); - let OperationBody::InvokeHostFunction(ref op) = result.operations[0].body else { - panic!("unexpected operation type: {:#?}", result.operations[0]); - }; - - assert_eq!(1, op.auth.len()); - let auth = &op.auth[0]; - - let xdr::SorobanAuthorizedFunction::ContractFn(xdr::InvokeContractArgs { - ref function_name, - .. - }) = auth.root_invocation.function - else { - panic!("unexpected function type"); - }; - assert_eq!("fn".to_string(), format!("{}", function_name.0)); - - let xdr::SorobanCredentials::Address(xdr::SorobanAddressCredentials { - address: - xdr::ScAddress::Account(xdr::AccountId(xdr::PublicKey::PublicKeyTypeEd25519(address))), - .. - }) = &auth.credentials - else { - panic!("unexpected credentials type"); - }; - assert_eq!( - SOURCE.to_string(), - stellar_strkey::ed25519::PublicKey(address.0).to_string() - ); - } - - #[test] - fn test_assemble_transaction_errors_for_non_invokehostfn_ops() { - let source_bytes = Ed25519PublicKey::from_string(SOURCE).unwrap().0; - let txn = Transaction { - source_account: MuxedAccount::Ed25519(Uint256(source_bytes)), - fee: 100, - seq_num: SequenceNumber(0), - cond: Preconditions::None, - memo: Memo::None, - operations: vec![Operation { - source_account: None, - body: OperationBody::ChangeTrust(ChangeTrustOp { - line: ChangeTrustAsset::Native, - limit: 0, - }), - }] - .try_into() - .unwrap(), - ext: TransactionExt::V0, - }; - - let result = assemble( - &txn, - &SimulateTransactionResponse { - min_resource_fee: 115, - transaction_data: transaction_data().to_xdr_base64(Limits::none()).unwrap(), - latest_ledger: 3, - ..Default::default() - }, - ); - - match result { - Ok(_) => {} - Err(e) => panic!("expected assembled operation, got: {e:#?}"), - } - } - - #[test] - fn test_assemble_transaction_errors_for_errors_for_mismatched_simulation() { - let txn = single_contract_fn_transaction(); - - let result = assemble( - &txn, - &SimulateTransactionResponse { - min_resource_fee: 115, - transaction_data: transaction_data().to_xdr_base64(Limits::none()).unwrap(), - latest_ledger: 3, - ..Default::default() - }, - ); - - match result { - Err(Error::UnexpectedSimulateTransactionResultSize { length }) => { - assert_eq!(0, length); - } - r => panic!("expected UnexpectedSimulateTransactionResultSize error, got: {r:#?}"), - } - } -} diff --git a/cmd/soroban-cli/src/toid.rs b/cmd/soroban-cli/src/toid.rs deleted file mode 100644 index 55c89049..00000000 --- a/cmd/soroban-cli/src/toid.rs +++ /dev/null @@ -1,69 +0,0 @@ -/// A barebones implementation of Total Order IDs (TOIDs) from -/// [SEP-35](https://stellar.org/protocol/sep-35), using the reference -/// implementation from the Go -/// [`stellar/go/toid`](https://github.com/stellar/go/blob/b4ba6f8e67f274bf84d21b0effb01ea8a914b766/toid/main.go#L8-L56) -/// package. -#[derive(Copy, Clone)] -pub struct Toid { - ledger_sequence: u32, - transaction_order: u32, - operation_order: u32, -} - -const LEDGER_MASK: u64 = (1 << 32) - 1; -const TRANSACTION_MASK: u64 = (1 << 20) - 1; -const OPERATION_MASK: u64 = (1 << 12) - 1; -const LEDGER_SHIFT: u64 = 32; -const TRANSACTION_SHIFT: u64 = 12; -const OPERATION_SHIFT: u64 = 0; - -impl Toid { - pub fn new(ledger: u32, tx_order: u32, op_order: u32) -> Toid { - Toid { - ledger_sequence: ledger, - transaction_order: tx_order, - operation_order: op_order, - } - } - - pub fn to_paging_token(self) -> String { - let u: u64 = self.into(); - format!("{u:019}") - } -} - -impl From for Toid { - fn from(item: u64) -> Self { - let ledger: u32 = ((item & LEDGER_MASK) >> LEDGER_SHIFT).try_into().unwrap(); - let tx_order: u32 = ((item & TRANSACTION_MASK) >> TRANSACTION_SHIFT) - .try_into() - .unwrap(); - let op_order: u32 = ((item & OPERATION_MASK) >> OPERATION_SHIFT) - .try_into() - .unwrap(); - - Toid::new(ledger, tx_order, op_order) - } -} - -impl From for u64 { - fn from(item: Toid) -> Self { - let l: u64 = item.ledger_sequence.into(); - let t: u64 = item.transaction_order.into(); - let o: u64 = item.operation_order.into(); - - let mut result: u64 = 0; - result |= (l & LEDGER_MASK) << LEDGER_SHIFT; - result |= (t & TRANSACTION_MASK) << TRANSACTION_SHIFT; - result |= (o & OPERATION_MASK) << OPERATION_SHIFT; - - result - } -} - -impl ToString for Toid { - fn to_string(&self) -> String { - let u: u64 = (*self).into(); - u.to_string() - } -} diff --git a/cmd/soroban-cli/src/utils.rs b/cmd/soroban-cli/src/utils.rs deleted file mode 100644 index ff0018a9..00000000 --- a/cmd/soroban-cli/src/utils.rs +++ /dev/null @@ -1,244 +0,0 @@ -use ed25519_dalek::Signer; -use sha2::{Digest, Sha256}; -use stellar_strkey::ed25519::PrivateKey; - -use soroban_env_host::xdr::{ - Asset, ContractIdPreimage, DecoratedSignature, Error as XdrError, Hash, HashIdPreimage, - HashIdPreimageContractId, Limits, Signature, SignatureHint, Transaction, TransactionEnvelope, - TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, - TransactionV1Envelope, WriteXdr, -}; - -pub mod contract_spec; - -/// # Errors -/// -/// Might return an error -pub fn contract_hash(contract: &[u8]) -> Result { - Ok(Hash(Sha256::digest(contract).into())) -} - -/// # Errors -/// -/// Might return an error -pub fn transaction_hash(tx: &Transaction, network_passphrase: &str) -> Result<[u8; 32], XdrError> { - let signature_payload = TransactionSignaturePayload { - network_id: Hash(Sha256::digest(network_passphrase).into()), - tagged_transaction: TransactionSignaturePayloadTaggedTransaction::Tx(tx.clone()), - }; - Ok(Sha256::digest(signature_payload.to_xdr(Limits::none())?).into()) -} - -/// # Errors -/// -/// Might return an error -pub fn sign_transaction( - key: &ed25519_dalek::SigningKey, - tx: &Transaction, - network_passphrase: &str, -) -> Result { - let tx_hash = transaction_hash(tx, network_passphrase)?; - let tx_signature = key.sign(&tx_hash); - - let decorated_signature = DecoratedSignature { - hint: SignatureHint(key.verifying_key().to_bytes()[28..].try_into()?), - signature: Signature(tx_signature.to_bytes().try_into()?), - }; - - Ok(TransactionEnvelope::Tx(TransactionV1Envelope { - tx: tx.clone(), - signatures: vec![decorated_signature].try_into()?, - })) -} - -/// # Errors -/// -/// Might return an error -pub fn contract_id_from_str(contract_id: &str) -> Result<[u8; 32], stellar_strkey::DecodeError> { - stellar_strkey::Contract::from_string(contract_id) - .map(|strkey| strkey.0) - .or_else(|_| { - // strkey failed, try to parse it as a hex string, for backwards compatibility. - soroban_spec_tools::utils::padded_hex_from_str(contract_id, 32) - .map_err(|_| stellar_strkey::DecodeError::Invalid)? - .try_into() - .map_err(|_| stellar_strkey::DecodeError::Invalid) - }) - .map_err(|_| stellar_strkey::DecodeError::Invalid) -} - -/// # Errors -/// May not find a config dir -pub fn find_config_dir(mut pwd: std::path::PathBuf) -> std::io::Result { - let soroban_dir = |p: &std::path::Path| p.join(".soroban"); - while !soroban_dir(&pwd).exists() { - if !pwd.pop() { - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - "soroban directory not found", - )); - } - } - Ok(soroban_dir(&pwd)) -} - -pub(crate) fn into_signing_key(key: &PrivateKey) -> ed25519_dalek::SigningKey { - let secret: ed25519_dalek::SecretKey = key.0; - ed25519_dalek::SigningKey::from_bytes(&secret) -} - -/// Used in tests -#[allow(unused)] -pub(crate) fn parse_secret_key( - s: &str, -) -> Result { - Ok(into_signing_key(&PrivateKey::from_string(s)?)) -} - -pub fn is_hex_string(s: &str) -> bool { - s.chars().all(|s| s.is_ascii_hexdigit()) -} - -pub fn contract_id_hash_from_asset( - asset: &Asset, - network_passphrase: &str, -) -> Result { - let network_id = Hash(Sha256::digest(network_passphrase.as_bytes()).into()); - let preimage = HashIdPreimage::ContractId(HashIdPreimageContractId { - network_id, - contract_id_preimage: ContractIdPreimage::Asset(asset.clone()), - }); - let preimage_xdr = preimage.to_xdr(Limits::none())?; - Ok(Hash(Sha256::digest(preimage_xdr).into())) -} - -pub mod parsing { - - use regex::Regex; - use soroban_env_host::xdr::{ - AccountId, AlphaNum12, AlphaNum4, Asset, AssetCode12, AssetCode4, PublicKey, - }; - - #[derive(thiserror::Error, Debug)] - pub enum Error { - #[error("invalid asset code: {asset}")] - InvalidAssetCode { asset: String }, - #[error("cannot parse account id: {account_id}")] - CannotParseAccountId { account_id: String }, - #[error("cannot parse asset: {asset}")] - CannotParseAsset { asset: String }, - #[error(transparent)] - Regex(#[from] regex::Error), - } - - pub fn parse_asset(str: &str) -> Result { - if str == "native" { - return Ok(Asset::Native); - } - let split: Vec<&str> = str.splitn(2, ':').collect(); - if split.len() != 2 { - return Err(Error::CannotParseAsset { - asset: str.to_string(), - }); - } - let code = split[0]; - let issuer = split[1]; - let re = Regex::new("^[[:alnum:]]{1,12}$")?; - if !re.is_match(code) { - return Err(Error::InvalidAssetCode { - asset: str.to_string(), - }); - } - if code.len() <= 4 { - let mut asset_code: [u8; 4] = [0; 4]; - for (i, b) in code.as_bytes().iter().enumerate() { - asset_code[i] = *b; - } - Ok(Asset::CreditAlphanum4(AlphaNum4 { - asset_code: AssetCode4(asset_code), - issuer: parse_account_id(issuer)?, - })) - } else { - let mut asset_code: [u8; 12] = [0; 12]; - for (i, b) in code.as_bytes().iter().enumerate() { - asset_code[i] = *b; - } - Ok(Asset::CreditAlphanum12(AlphaNum12 { - asset_code: AssetCode12(asset_code), - issuer: parse_account_id(issuer)?, - })) - } - } - - pub fn parse_account_id(str: &str) -> Result { - let pk_bytes = stellar_strkey::ed25519::PublicKey::from_string(str) - .map_err(|_| Error::CannotParseAccountId { - account_id: str.to_string(), - })? - .0; - Ok(AccountId(PublicKey::PublicKeyTypeEd25519(pk_bytes.into()))) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_contract_id_from_str() { - // strkey - match contract_id_from_str("CA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQGAXE") { - Ok(contract_id) => assert_eq!( - contract_id, - [ - 0x36, 0x3e, 0xaa, 0x38, 0x67, 0x84, 0x1f, 0xba, 0xd0, 0xf4, 0xed, 0x88, 0xc7, - 0x79, 0xe4, 0xfe, 0x66, 0xe5, 0x6a, 0x24, 0x70, 0xdc, 0x98, 0xc0, 0xec, 0x9c, - 0x07, 0x3d, 0x05, 0xc7, 0xb1, 0x03, - ] - ), - Err(err) => panic!("Failed to parse contract id: {err}"), - } - - // hex - match contract_id_from_str( - "363eaa3867841fbad0f4ed88c779e4fe66e56a2470dc98c0ec9c073d05c7b103", - ) { - Ok(contract_id) => assert_eq!( - contract_id, - [ - 0x36, 0x3e, 0xaa, 0x38, 0x67, 0x84, 0x1f, 0xba, 0xd0, 0xf4, 0xed, 0x88, 0xc7, - 0x79, 0xe4, 0xfe, 0x66, 0xe5, 0x6a, 0x24, 0x70, 0xdc, 0x98, 0xc0, 0xec, 0x9c, - 0x07, 0x3d, 0x05, 0xc7, 0xb1, 0x03, - ] - ), - Err(err) => panic!("Failed to parse contract id: {err}"), - } - - // unpadded-hex - match contract_id_from_str("1") { - Ok(contract_id) => assert_eq!( - contract_id, - [ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - ] - ), - Err(err) => panic!("Failed to parse contract id: {err}"), - } - - // invalid hex - match contract_id_from_str("foobar") { - Ok(_) => panic!("Expected parsing to fail"), - Err(err) => assert_eq!(err, stellar_strkey::DecodeError::Invalid), - } - - // hex too long (33 bytes) - match contract_id_from_str( - "000000000000000000000000000000000000000000000000000000000000000000", - ) { - Ok(_) => panic!("Expected parsing to fail"), - Err(err) => assert_eq!(err, stellar_strkey::DecodeError::Invalid), - } - } -} diff --git a/cmd/soroban-cli/src/utils/contract_spec.rs b/cmd/soroban-cli/src/utils/contract_spec.rs deleted file mode 100644 index b4f24abe..00000000 --- a/cmd/soroban-cli/src/utils/contract_spec.rs +++ /dev/null @@ -1,276 +0,0 @@ -use base64::{engine::general_purpose::STANDARD as base64, Engine as _}; -use std::{ - fmt::Display, - io::{self, Cursor}, -}; - -use soroban_env_host::xdr::{ - self, Limited, Limits, ReadXdr, ScEnvMetaEntry, ScMetaEntry, ScMetaV0, ScSpecEntry, - ScSpecFunctionV0, ScSpecUdtEnumV0, ScSpecUdtErrorEnumV0, ScSpecUdtStructV0, ScSpecUdtUnionV0, - StringM, WriteXdr, -}; - -pub struct ContractSpec { - pub env_meta_base64: Option, - pub env_meta: Vec, - pub meta_base64: Option, - pub meta: Vec, - pub spec_base64: Option, - pub spec: Vec, -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("reading file {filepath}: {error}")] - CannotReadContractFile { - filepath: std::path::PathBuf, - error: io::Error, - }, - #[error("cannot parse wasm file {file}: {error}")] - CannotParseWasm { - file: std::path::PathBuf, - error: wasmparser::BinaryReaderError, - }, - #[error("xdr processing error: {0}")] - Xdr(#[from] xdr::Error), - - #[error(transparent)] - Parser(#[from] wasmparser::BinaryReaderError), -} - -impl ContractSpec { - pub fn new(bytes: &[u8]) -> Result { - let mut env_meta: Option<&[u8]> = None; - let mut meta: Option<&[u8]> = None; - let mut spec: Option<&[u8]> = None; - for payload in wasmparser::Parser::new(0).parse_all(bytes) { - let payload = payload?; - if let wasmparser::Payload::CustomSection(section) = payload { - let out = match section.name() { - "contractenvmetav0" => &mut env_meta, - "contractmetav0" => &mut meta, - "contractspecv0" => &mut spec, - _ => continue, - }; - *out = Some(section.data()); - }; - } - - let mut env_meta_base64 = None; - let env_meta = if let Some(env_meta) = env_meta { - env_meta_base64 = Some(base64.encode(env_meta)); - let cursor = Cursor::new(env_meta); - let mut read = Limited::new(cursor, Limits::none()); - ScEnvMetaEntry::read_xdr_iter(&mut read).collect::, xdr::Error>>()? - } else { - vec![] - }; - - let mut meta_base64 = None; - let meta = if let Some(meta) = meta { - meta_base64 = Some(base64.encode(meta)); - let cursor = Cursor::new(meta); - let mut depth_limit_read = Limited::new(cursor, Limits::none()); - ScMetaEntry::read_xdr_iter(&mut depth_limit_read) - .collect::, xdr::Error>>()? - } else { - vec![] - }; - - let mut spec_base64 = None; - let spec = if let Some(spec) = spec { - spec_base64 = Some(base64.encode(spec)); - let cursor = Cursor::new(spec); - let mut read = Limited::new(cursor, Limits::none()); - ScSpecEntry::read_xdr_iter(&mut read).collect::, xdr::Error>>()? - } else { - vec![] - }; - - Ok(ContractSpec { - env_meta_base64, - env_meta, - meta_base64, - meta, - spec_base64, - spec, - }) - } - - pub fn spec_as_json_array(&self) -> Result { - let spec = self - .spec - .iter() - .map(|e| Ok(format!("\"{}\"", e.to_xdr_base64(Limits::none())?))) - .collect::, Error>>()? - .join(",\n"); - Ok(format!("[{spec}]")) - } -} - -impl Display for ContractSpec { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if let Some(env_meta) = &self.env_meta_base64 { - writeln!(f, "Env Meta: {env_meta}")?; - for env_meta_entry in &self.env_meta { - match env_meta_entry { - ScEnvMetaEntry::ScEnvMetaKindInterfaceVersion(v) => { - writeln!(f, " • Interface Version: {v}")?; - } - } - } - writeln!(f)?; - } else { - writeln!(f, "Env Meta: None\n")?; - } - - if let Some(_meta) = &self.meta_base64 { - writeln!(f, "Contract Meta:")?; - for meta_entry in &self.meta { - match meta_entry { - ScMetaEntry::ScMetaV0(ScMetaV0 { key, val }) => { - writeln!(f, " • {key}: {val}")?; - } - } - } - writeln!(f)?; - } else { - writeln!(f, "Contract Meta: None\n")?; - } - - if let Some(_spec_base64) = &self.spec_base64 { - writeln!(f, "Contract Spec:")?; - for spec_entry in &self.spec { - match spec_entry { - ScSpecEntry::FunctionV0(func) => write_func(f, func)?, - ScSpecEntry::UdtUnionV0(udt) => write_union(f, udt)?, - ScSpecEntry::UdtStructV0(udt) => write_struct(f, udt)?, - ScSpecEntry::UdtEnumV0(udt) => write_enum(f, udt)?, - ScSpecEntry::UdtErrorEnumV0(udt) => write_error(f, udt)?, - } - } - } else { - writeln!(f, "Contract Spec: None")?; - } - Ok(()) - } -} - -fn write_func(f: &mut std::fmt::Formatter<'_>, func: &ScSpecFunctionV0) -> std::fmt::Result { - writeln!(f, " • Function: {}", func.name.to_utf8_string_lossy())?; - if func.doc.len() > 0 { - writeln!( - f, - " Docs: {}", - &indent(&func.doc.to_utf8_string_lossy(), 11).trim() - )?; - } - writeln!( - f, - " Inputs: {}", - indent(&format!("{:#?}", func.inputs), 5).trim() - )?; - writeln!( - f, - " Output: {}", - indent(&format!("{:#?}", func.outputs), 5).trim() - )?; - writeln!(f)?; - Ok(()) -} - -fn write_union(f: &mut std::fmt::Formatter<'_>, udt: &ScSpecUdtUnionV0) -> std::fmt::Result { - writeln!(f, " • Union: {}", format_name(&udt.lib, &udt.name))?; - if udt.doc.len() > 0 { - writeln!( - f, - " Docs: {}", - indent(&udt.doc.to_utf8_string_lossy(), 10).trim() - )?; - } - writeln!(f, " Cases:")?; - for case in udt.cases.iter() { - writeln!(f, " • {}", indent(&format!("{case:#?}"), 8).trim())?; - } - writeln!(f)?; - Ok(()) -} - -fn write_struct(f: &mut std::fmt::Formatter<'_>, udt: &ScSpecUdtStructV0) -> std::fmt::Result { - writeln!(f, " • Struct: {}", format_name(&udt.lib, &udt.name))?; - if udt.doc.len() > 0 { - writeln!( - f, - " Docs: {}", - indent(&udt.doc.to_utf8_string_lossy(), 10).trim() - )?; - } - writeln!(f, " Fields:")?; - for field in udt.fields.iter() { - writeln!( - f, - " • {}: {}", - field.name.to_utf8_string_lossy(), - indent(&format!("{:#?}", field.type_), 8).trim() - )?; - if field.doc.len() > 0 { - writeln!(f, "{}", indent(&format!("{:#?}", field.doc), 8))?; - } - } - writeln!(f)?; - Ok(()) -} - -fn write_enum(f: &mut std::fmt::Formatter<'_>, udt: &ScSpecUdtEnumV0) -> std::fmt::Result { - writeln!(f, " • Enum: {}", format_name(&udt.lib, &udt.name))?; - if udt.doc.len() > 0 { - writeln!( - f, - " Docs: {}", - indent(&udt.doc.to_utf8_string_lossy(), 10).trim() - )?; - } - writeln!(f, " Cases:")?; - for case in udt.cases.iter() { - writeln!(f, " • {}", indent(&format!("{case:#?}"), 8).trim())?; - } - writeln!(f)?; - Ok(()) -} - -fn write_error(f: &mut std::fmt::Formatter<'_>, udt: &ScSpecUdtErrorEnumV0) -> std::fmt::Result { - writeln!(f, " • Error: {}", format_name(&udt.lib, &udt.name))?; - if udt.doc.len() > 0 { - writeln!( - f, - " Docs: {}", - indent(&udt.doc.to_utf8_string_lossy(), 10).trim() - )?; - } - writeln!(f, " Cases:")?; - for case in udt.cases.iter() { - writeln!(f, " • {}", indent(&format!("{case:#?}"), 8).trim())?; - } - writeln!(f)?; - Ok(()) -} - -fn indent(s: &str, n: usize) -> String { - let pad = " ".repeat(n); - s.lines() - .map(|line| format!("{pad}{line}")) - .collect::>() - .join("\n") -} - -fn format_name(lib: &StringM<80>, name: &StringM<60>) -> String { - if lib.len() > 0 { - format!( - "{}::{}", - lib.to_utf8_string_lossy(), - name.to_utf8_string_lossy() - ) - } else { - name.to_utf8_string_lossy() - } -} diff --git a/cmd/soroban-cli/src/wasm.rs b/cmd/soroban-cli/src/wasm.rs deleted file mode 100644 index fce44c7c..00000000 --- a/cmd/soroban-cli/src/wasm.rs +++ /dev/null @@ -1,93 +0,0 @@ -use clap::arg; -use soroban_env_host::xdr::{self, LedgerKey, LedgerKeyContractCode}; -use std::{ - fs, io, - path::{Path, PathBuf}, -}; - -use crate::utils::{self, contract_spec::ContractSpec}; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("reading file {filepath}: {error}")] - CannotReadContractFile { - filepath: std::path::PathBuf, - error: io::Error, - }, - #[error("cannot parse wasm file {file}: {error}")] - CannotParseWasm { - file: std::path::PathBuf, - error: wasmparser::BinaryReaderError, - }, - #[error("xdr processing error: {0}")] - Xdr(#[from] xdr::Error), - - #[error(transparent)] - Parser(#[from] wasmparser::BinaryReaderError), - #[error(transparent)] - ContractSpec(#[from] crate::utils::contract_spec::Error), -} - -#[derive(Debug, clap::Args, Clone)] -#[group(skip)] -pub struct Args { - /// Path to wasm binary - #[arg(long)] - pub wasm: PathBuf, -} - -impl Args { - /// # Errors - /// May fail to read wasm file - pub fn read(&self) -> Result, Error> { - fs::read(&self.wasm).map_err(|e| Error::CannotReadContractFile { - filepath: self.wasm.clone(), - error: e, - }) - } - - /// # Errors - /// May fail to read wasm file - pub fn len(&self) -> Result { - len(&self.wasm) - } - - /// # Errors - /// May fail to read wasm file - pub fn is_empty(&self) -> Result { - self.len().map(|len| len == 0) - } - - /// # Errors - /// May fail to read wasm file or parse xdr section - pub fn parse(&self) -> Result { - let contents = self.read()?; - Ok(ContractSpec::new(&contents)?) - } -} - -impl From<&PathBuf> for Args { - fn from(wasm: &PathBuf) -> Self { - Self { wasm: wasm.clone() } - } -} - -impl TryInto for Args { - type Error = Error; - fn try_into(self) -> Result { - Ok(LedgerKey::ContractCode(LedgerKeyContractCode { - hash: utils::contract_hash(&self.read()?)?, - })) - } -} - -/// # Errors -/// May fail to read wasm file -pub fn len(p: &Path) -> Result { - Ok(std::fs::metadata(p) - .map_err(|e| Error::CannotReadContractFile { - filepath: p.to_path_buf(), - error: e, - })? - .len()) -}