From 2423332c9dbb85e0ecadf3b053bbe6b1f4747414 Mon Sep 17 00:00:00 2001 From: "Tyler.S" Date: Tue, 16 Jan 2024 16:42:57 -0800 Subject: [PATCH] Add hello world test wasm --- Cargo.lock | 655 ++++++++++++++++-- Cargo.toml | 18 + Makefile | 13 +- cmd/crates/soroban-test/Cargo.toml | 42 ++ cmd/crates/soroban-test/README.md | 54 ++ cmd/crates/soroban-test/src/lib.rs | 229 ++++++ cmd/crates/soroban-test/src/wasm.rs | 61 ++ .../soroban-test/tests/fixtures/args/world | 1 + .../tests/fixtures/hello/Cargo.lock | 7 + .../tests/fixtures/hello/Cargo.toml | 9 + .../tests/fixtures/hello/src/main.rs | 3 + .../tests/fixtures/test-jsons/get-events.json | 57 ++ .../test-wasms/hello_world/Cargo.toml | 18 + .../test-wasms/hello_world/src/lib.rs | 86 +++ .../soroban-test/tests/it/arg_parsing.rs | 215 ++++++ cmd/crates/soroban-test/tests/it/config.rs | 223 ++++++ .../soroban-test/tests/it/hello_world.rs | 22 + cmd/crates/soroban-test/tests/it/help.rs | 97 +++ .../soroban-test/tests/it/integration.rs | 5 + .../tests/it/integration/custom_types.rs | 419 +++++++++++ .../tests/it/integration/dotenv.rs | 64 ++ .../tests/it/integration/hello_world.rs | 290 ++++++++ .../soroban-test/tests/it/integration/util.rs | 119 ++++ .../soroban-test/tests/it/integration/wrap.rs | 97 +++ .../it/lab_test_transaction_envelope.txt | 54 ++ cmd/crates/soroban-test/tests/it/main.rs | 8 + cmd/crates/soroban-test/tests/it/plugin.rs | 77 ++ cmd/crates/soroban-test/tests/it/util.rs | 70 ++ cmd/crates/soroban-test/tests/it/version.rs | 12 + 29 files changed, 2980 insertions(+), 45 deletions(-) create mode 100644 cmd/crates/soroban-test/Cargo.toml create mode 100644 cmd/crates/soroban-test/README.md create mode 100644 cmd/crates/soroban-test/src/lib.rs create mode 100644 cmd/crates/soroban-test/src/wasm.rs create mode 100644 cmd/crates/soroban-test/tests/fixtures/args/world create mode 100644 cmd/crates/soroban-test/tests/fixtures/hello/Cargo.lock create mode 100644 cmd/crates/soroban-test/tests/fixtures/hello/Cargo.toml create mode 100644 cmd/crates/soroban-test/tests/fixtures/hello/src/main.rs create mode 100644 cmd/crates/soroban-test/tests/fixtures/test-jsons/get-events.json create mode 100644 cmd/crates/soroban-test/tests/fixtures/test-wasms/hello_world/Cargo.toml create mode 100644 cmd/crates/soroban-test/tests/fixtures/test-wasms/hello_world/src/lib.rs create mode 100644 cmd/crates/soroban-test/tests/it/arg_parsing.rs create mode 100644 cmd/crates/soroban-test/tests/it/config.rs create mode 100644 cmd/crates/soroban-test/tests/it/hello_world.rs create mode 100644 cmd/crates/soroban-test/tests/it/help.rs create mode 100644 cmd/crates/soroban-test/tests/it/integration.rs create mode 100644 cmd/crates/soroban-test/tests/it/integration/custom_types.rs create mode 100644 cmd/crates/soroban-test/tests/it/integration/dotenv.rs create mode 100644 cmd/crates/soroban-test/tests/it/integration/hello_world.rs create mode 100644 cmd/crates/soroban-test/tests/it/integration/util.rs create mode 100644 cmd/crates/soroban-test/tests/it/integration/wrap.rs create mode 100644 cmd/crates/soroban-test/tests/it/lab_test_transaction_envelope.txt create mode 100644 cmd/crates/soroban-test/tests/it/main.rs create mode 100644 cmd/crates/soroban-test/tests/it/plugin.rs create mode 100644 cmd/crates/soroban-test/tests/it/util.rs create mode 100644 cmd/crates/soroban-test/tests/it/version.rs diff --git a/Cargo.lock b/Cargo.lock index 640a07e2..802d40d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,11 +17,26 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" -version = "1.0.75" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" [[package]] name = "arbitrary" @@ -73,9 +88,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.5" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64ct" @@ -98,6 +113,18 @@ version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +[[package]] +name = "bytes-lit" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0adabf37211a5276e46335feabcbb1530c95eb3fdf85f324c7db942770aa025d" +dependencies = [ + "num-bigint", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "cc" version = "1.0.83" @@ -113,17 +140,36 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-targets 0.48.5", +] + [[package]] name = "const-oid" -version = "0.9.5" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] @@ -161,6 +207,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "ctor" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d2b3721e861707777e3195b0158f950ae6dc4a27e4d02ff9f67e3eb3de199e" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "curve25519-dalek" version = "4.1.1" @@ -189,6 +245,41 @@ dependencies = [ "syn", ] +[[package]] +name = "darling" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "der" version = "0.7.8" @@ -199,6 +290,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", +] + [[package]] name = "derive_arbitrary" version = "1.3.2" @@ -291,6 +392,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "escape-bytes" version = "0.1.1" @@ -319,6 +426,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27573eac26f4dd11e2b1916c3fe1baa56407c83c71a773a8ba17ec0bca03b6b7" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "generic-array" version = "0.14.7" @@ -360,11 +473,26 @@ dependencies = [ "subtle", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] [[package]] name = "hex-literal" @@ -381,6 +509,57 @@ dependencies = [ "digest", ] +[[package]] +name = "iana-time-zone" +version = "0.1.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown 0.14.3", + "serde", +] + [[package]] name = "indexmap-nostd" version = "0.4.0" @@ -404,9 +583,9 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" -version = "0.3.66" +version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" dependencies = [ "wasm-bindgen", ] @@ -427,18 +606,18 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" dependencies = [ "cpufeatures", ] [[package]] name = "libc" -version = "0.2.151" +version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "libm" @@ -454,9 +633,9 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "miniz_oxide" @@ -467,6 +646,17 @@ dependencies = [ "adler", ] +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-derive" version = "0.4.1" @@ -499,9 +689,9 @@ dependencies = [ [[package]] name = "object" -version = "0.32.1" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] @@ -530,9 +720,15 @@ dependencies = [ [[package]] name = "platforms" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14e6ab3f592e6fb464fc9712d8d6e6912de6473954635fd76a589d832cffcbb0" +checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" @@ -545,14 +741,24 @@ name = "preflight" version = "20.2.0" dependencies = [ "anyhow", - "base64 0.21.5", + "base64 0.21.7", "libc", "rand", "sha2", - "soroban-env-host", + "soroban-env-host 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=c1b238b65bfd13666be4ac14e0e390c31b549caf)", "thiserror", ] +[[package]] +name = "prettyplease" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.69" @@ -648,9 +854,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" [[package]] name = "serde" @@ -683,6 +889,35 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23" +dependencies = [ + "base64 0.21.7", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.1.0", + "serde", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sha2" version = "0.10.8" @@ -716,9 +951,20 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.2" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +checksum = "2593d31f82ead8df961d8bd23a64c2ccf2eb5dd34b0a34bfb4dd54011c72009e" + +[[package]] +name = "soroban-builtin-sdk-macros" +version = "20.1.0" +source = "git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4#36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4" +dependencies = [ + "itertools", + "proc-macro2", + "quote", + "syn", +] [[package]] name = "soroban-builtin-sdk-macros" @@ -731,6 +977,23 @@ dependencies = [ "syn", ] +[[package]] +name = "soroban-env-common" +version = "20.1.0" +source = "git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4#36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4" +dependencies = [ + "arbitrary", + "crate-git-revision", + "ethnum", + "num-derive", + "num-traits", + "serde", + "soroban-env-macros 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", + "soroban-wasmi", + "static_assertions", + "stellar-xdr", +] + [[package]] name = "soroban-env-common" version = "20.1.0" @@ -741,12 +1004,47 @@ dependencies = [ "ethnum", "num-derive", "num-traits", - "soroban-env-macros", + "soroban-env-macros 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=c1b238b65bfd13666be4ac14e0e390c31b549caf)", "soroban-wasmi", "static_assertions", "stellar-xdr", ] +[[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 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 = "git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4#36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4" +dependencies = [ + "backtrace", + "curve25519-dalek", + "ed25519-dalek", + "getrandom", + "hex-literal", + "hmac", + "k256", + "num-derive", + "num-integer", + "num-traits", + "rand", + "rand_chacha", + "sha2", + "sha3", + "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", + "static_assertions", + "stellar-strkey", +] + [[package]] name = "soroban-env-host" version = "20.1.0" @@ -766,13 +1064,27 @@ dependencies = [ "rand_chacha", "sha2", "sha3", - "soroban-builtin-sdk-macros", - "soroban-env-common", + "soroban-builtin-sdk-macros 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=c1b238b65bfd13666be4ac14e0e390c31b549caf)", + "soroban-env-common 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=c1b238b65bfd13666be4ac14e0e390c31b549caf)", "soroban-wasmi", "static_assertions", "stellar-strkey", ] +[[package]] +name = "soroban-env-macros" +version = "20.1.0" +source = "git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4#36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4" +dependencies = [ + "itertools", + "proc-macro2", + "quote", + "serde", + "serde_json", + "stellar-xdr", + "syn", +] + [[package]] name = "soroban-env-macros" version = "20.1.0" @@ -787,6 +1099,87 @@ dependencies = [ "syn", ] +[[package]] +name = "soroban-hello" +version = "20.2.0" + +[[package]] +name = "soroban-ledger-snapshot" +version = "20.1.0" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb#e6c2c900ab82b5f6eec48f69cb2cb519e19819cb" +dependencies = [ + "serde", + "serde_json", + "serde_with", + "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 = "git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb#e6c2c900ab82b5f6eec48f69cb2cb519e19819cb" +dependencies = [ + "arbitrary", + "bytes-lit", + "ctor", + "ed25519-dalek", + "rand", + "serde", + "serde_json", + "soroban-env-guest", + "soroban-env-host 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", + "soroban-ledger-snapshot", + "soroban-sdk-macros", + "stellar-strkey", +] + +[[package]] +name = "soroban-sdk-macros" +version = "20.1.0" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb#e6c2c900ab82b5f6eec48f69cb2cb519e19819cb" +dependencies = [ + "crate-git-revision", + "darling", + "itertools", + "proc-macro2", + "quote", + "rustc_version", + "sha2", + "soroban-env-common 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", + "soroban-spec", + "soroban-spec-rust", + "stellar-xdr", + "syn", +] + +[[package]] +name = "soroban-spec" +version = "20.1.0" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb#e6c2c900ab82b5f6eec48f69cb2cb519e19819cb" +dependencies = [ + "base64 0.13.1", + "stellar-xdr", + "thiserror", + "wasmparser", +] + +[[package]] +name = "soroban-spec-rust" +version = "20.1.0" +source = "git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb#e6c2c900ab82b5f6eec48f69cb2cb519e19819cb" +dependencies = [ + "prettyplease", + "proc-macro2", + "quote", + "sha2", + "soroban-spec", + "stellar-xdr", + "syn", + "thiserror", +] + [[package]] name = "soroban-wasmi" version = "0.31.1-soroban.20.0.0" @@ -843,9 +1236,17 @@ dependencies = [ "crate-git-revision", "escape-bytes", "hex", + "serde", + "serde_with", "stellar-strkey", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "subtle" version = "2.5.0" @@ -863,26 +1264,62 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "test_hello_world" +version = "20.2.0" +dependencies = [ + "soroban-sdk", +] + [[package]] name = "thiserror" -version = "1.0.50" +version = "1.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "6e3de26b0965292219b4287ff031fcba86837900fe9cd2b34ea8ad893c0953d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.50" +version = "1.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "268026685b2be38d7103e9e507c938a1fcb3d7e6eb15e87870b617bf37b6d581" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "time" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" +dependencies = [ + "deranged", + "itoa", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f" +dependencies = [ + "time-core", +] + [[package]] name = "typenum" version = "1.17.0" @@ -909,9 +1346,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.89" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -919,9 +1356,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.89" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" dependencies = [ "bumpalo", "log", @@ -934,9 +1371,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.89" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -944,9 +1381,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.89" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" dependencies = [ "proc-macro2", "quote", @@ -957,9 +1394,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.89" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" +checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" [[package]] name = "wasmi_arena" @@ -977,6 +1414,15 @@ dependencies = [ "paste", ] +[[package]] +name = "wasmparser" +version = "0.88.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb8cf7dd82407fe68161bedcd57fde15596f32ebf6e9b3bdbf3ae1da20e38e5e" +dependencies = [ + "indexmap 1.9.3", +] + [[package]] name = "wasmparser-nostd" version = "0.100.1" @@ -986,6 +1432,129 @@ dependencies = [ "indexmap-nostd", ] +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "zeroize" version = "1.7.0" diff --git a/Cargo.toml b/Cargo.toml index 45c7e65b..b535ac9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,8 @@ resolver = "2" members = [ "cmd/soroban-rpc/lib/preflight", + "cmd/crates/soroban-test/tests/fixtures/test-wasms/*", + "cmd/crates/soroban-test/tests/fixtures/hello", ] [workspace.package] @@ -29,6 +31,22 @@ tracing-appender = "0.2.2" which = "4.4.0" wasmparser = "0.90.0" +[workspace.dependencies.soroban-sdk] +version = "=20.1.0" +git = "https://github.com/stellar/rs-soroban-sdk" +rev = "e6c2c900ab82b5f6eec48f69cb2cb519e19819cb" + +[profile.test-wasms] +inherits = "release" +opt-level = "z" +overflow-checks = true +debug = 0 +strip = "symbols" +debug-assertions = true +panic = "abort" +codegen-units = 1 +lto = true + [profile.release-with-panic-unwind] inherits = 'release' panic = 'unwind' \ No newline at end of file diff --git a/Makefile b/Makefile index 50553c55..1b851da5 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,10 @@ CARGO_BUILD_TARGET ?= $(shell rustc -vV | sed -n 's|host: ||p') Cargo.lock: Cargo.toml cargo update --workspace -install: build-libpreflight +install_rust: + cargo install --path ./cmd/crates/soroban-test/tests/fixtures/hello --root ./target --debug --quiet + +install: install_rust build-libpreflight go install -ldflags="${GOLDFLAGS}" ${MACOS_MIN_VER} ./... build_rust: Cargo.lock @@ -54,7 +57,13 @@ build: build_rust build_go build-libpreflight: Cargo.lock cd cmd/soroban-rpc/lib/preflight && cargo build --target $(CARGO_BUILD_TARGET) --profile release-with-panic-unwind -test: cargo test +build-test-wasms: Cargo.lock + cargo build --package 'test_*' --profile test-wasms --target wasm32-unknown-unknown + +build-test: install_rust + +test: build-test + cargo test check: Cargo.lock cargo clippy --all-targets diff --git a/cmd/crates/soroban-test/Cargo.toml b/cmd/crates/soroban-test/Cargo.toml new file mode 100644 index 00000000..649a37e5 --- /dev/null +++ b/cmd/crates/soroban-test/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "soroban-test" +description = "Soroban Test Framework" +homepage = "https://github.com/stellar/soroban-test" +repository = "https://github.com/stellar/soroban-test" +authors = ["Stellar Development Foundation "] +license = "Apache-2.0" +readme = "README.md" +version = "20.2.0" +edition = "2021" +rust-version.workspace = true +autobins = false + + +[lib] +crate-type = ["rlib", "cdylib"] + + +[dependencies] +soroban-env-host = { workspace = true } +soroban-spec = { workspace = true } +soroban-spec-tools = { workspace = true } +soroban-ledger-snapshot = { workspace = true } +stellar-strkey = { workspace = true } +soroban-sdk = { workspace = true } +sep5 = { workspace = true } +soroban-cli = { workspace = true } + +thiserror = "1.0.31" +sha2 = "0.10.6" +assert_cmd = "2.0.4" +assert_fs = "1.0.7" +predicates = "2.1.5" +fs_extra = "1.3.0" + +[dev-dependencies] +serde_json = "1.0.93" +which = { workspace = true } +tokio = "1.28.1" + +[features] +integration = [] diff --git a/cmd/crates/soroban-test/README.md b/cmd/crates/soroban-test/README.md new file mode 100644 index 00000000..3f8cdc9f --- /dev/null +++ b/cmd/crates/soroban-test/README.md @@ -0,0 +1,54 @@ +Soroban Test +============ + +Test framework wrapping Soroban CLI. + +Provides a way to run tests against a local sandbox; running against RPC endpoint _coming soon_. + + +Overview +======== + +- `TestEnv` is a test environment for running tests isolated from each other. +- `TestEnv::with_default` invokes a closure, which is passed a reference to a random `TestEnv`. +- `TestEnv::new_assert_cmd` creates an `assert_cmd::Command` for a given subcommand and sets the current + directory to be the same as `TestEnv`. +- `TestEnv::cmd` is a generic function which parses a command from a string. + Note, however, that it uses `shlex` to tokenize the string. This can cause issues + for commands which contain strings with `"`s. For example, `{"hello": "world"}` becomes + `{hello:world}`. For that reason it's recommended to use `TestEnv::cmd_arr` instead. +- `TestEnv::cmd_arr` is a generic function which takes an array of `&str` which is passed directly to clap. + This is the preferred way since it ensures no string parsing footguns. +- `TestEnv::invoke` a convenience function for using the invoke command. + + +Example +======= + +```rs +use soroban_test::{TestEnv, Wasm}; + +const WASM: &Wasm = &Wasm::Release("soroban_hello_world_contract"); +const FRIEND: &str = "friend"; + +#[test] +fn invoke() { + TestEnv::with_default(|workspace| { + assert_eq!( + format!("[\"Hello\",\"{FRIEND}\"]"), + workspace + .invoke(&[ + "--id", + "1", + "--wasm", + &WASM.path().to_string_lossy(), + "--", + "hello", + "--to", + FRIEND, + ]) + .unwrap() + ); + }); +} +``` diff --git a/cmd/crates/soroban-test/src/lib.rs b/cmd/crates/soroban-test/src/lib.rs new file mode 100644 index 00000000..bda6ec42 --- /dev/null +++ b/cmd/crates/soroban-test/src/lib.rs @@ -0,0 +1,229 @@ +//! **Soroban Test** - Test framework for invoking Soroban externally. +//! +//! Currently soroban provides a mock test environment for writing unit tets. +//! +//! However, it does not provide a way to run tests against a local sandbox or rpc endpoint. +//! +//! ## Overview +//! +//! - `TestEnv` is a test environment for running tests isolated from each other. +//! - `TestEnv::with_default` invokes a closure, which is passed a reference to a random `TestEnv`. +//! - `TestEnv::new_assert_cmd` creates an `assert_cmd::Command` for a given subcommand and sets the current +//! directory to be the same as `TestEnv`. +//! - `TestEnv::cmd` is a generic function which parses a command from a string. +//! Note, however, that it uses `shlex` to tokenize the string. This can cause issues +//! for commands which contain strings with `"`s. For example, `{"hello": "world"}` becomes +//! `{hello:world}`. For that reason it's recommended to use `TestEnv::cmd_arr` instead. +//! - `TestEnv::cmd_arr` is a generic function which takes an array of `&str` which is passed directly to clap. +//! This is the preferred way since it ensures no string parsing footguns. +//! - `TestEnv::invoke` a convenience function for using the invoke command. +//! +#![allow( + clippy::missing_errors_doc, + clippy::must_use_candidate, + clippy::missing_panics_doc +)] +use std::{ffi::OsString, fmt::Display, path::Path}; + +use assert_cmd::{assert::Assert, Command}; +use assert_fs::{fixture::FixtureError, prelude::PathChild, TempDir}; +use fs_extra::dir::CopyOptions; + +use soroban_cli::{ + commands::{config, contract, contract::invoke, global, keys}, + CommandParser, Pwd, +}; + +mod wasm; +pub use wasm::Wasm; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + TempDir(#[from] FixtureError), + + #[error(transparent)] + FsError(#[from] fs_extra::error::Error), + + #[error(transparent)] + Invoke(#[from] invoke::Error), +} + +/// A `TestEnv` is a contained process for a specific test, with its own ENV and +/// its own `TempDir` where it will save test-specific configuration. +pub struct TestEnv { + pub temp_dir: TempDir, +} + +impl Default for TestEnv { + fn default() -> Self { + Self::new().unwrap() + } +} + +impl TestEnv { + /// Execute a closure which is passed a reference to the `TestEnv`. + /// `TempDir` implements the `Drop` trait ensuring that the temporary directory + /// it creates is deleted when the `TestEnv` is dropped. This pattern ensures + /// that the `TestEnv` cannot be dropped by the closure. For this reason, it's + /// recommended to use `TempDir::with_default` instead of `new` or `default`. + /// + /// ```rust,no_run + /// use soroban_test::TestEnv; + /// TestEnv::with_default(|env| { + /// env.new_assert_cmd("contract").args(&["invoke", "--id", "1", "--", "hello", "--world=world"]).assert().success(); + /// }); + /// ``` + /// + pub fn with_default(f: F) { + let test_env = TestEnv::default(); + f(&test_env); + } + pub fn new() -> Result { + let this = TempDir::new().map(|temp_dir| TestEnv { temp_dir })?; + std::env::set_var("XDG_CONFIG_HOME", this.temp_dir.as_os_str()); + this.new_assert_cmd("keys") + .arg("generate") + .arg("test") + .arg("-d") + .arg("--no-fund") + .assert(); + std::env::set_var("SOROBAN_ACCOUNT", "test"); + Ok(this) + } + + /// Create a new `assert_cmd::Command` for a given subcommand and set's the current directory + /// to be the internal `temp_dir`. + pub fn new_assert_cmd(&self, subcommand: &str) -> Command { + let mut this = Command::cargo_bin("soroban").unwrap_or_else(|_| Command::new("soroban")); + this.arg("-q"); + this.arg(subcommand); + this.current_dir(&self.temp_dir); + this + } + + /// Parses a `&str` into a command and sets the pwd to be the same as the current `TestEnv`. + /// Uses shlex under the hood and thus has issues parsing strings with embedded `"`s. + /// Thus `TestEnv::cmd_arr` is recommended to instead. + pub fn cmd>(&self, args: &str) -> T { + Self::cmd_with_pwd(args, self.dir()) + } + + /// Same as `TestEnv::cmd` but sets the pwd can be used instead of the current `TestEnv`. + pub fn cmd_with_pwd>(args: &str, pwd: &Path) -> T { + let args = format!("--config-dir={pwd:?} {args}"); + T::parse(&args).unwrap() + } + + /// Same as `TestEnv::cmd_arr` but sets the pwd can be used instead of the current `TestEnv`. + pub fn cmd_arr_with_pwd>(args: &[&str], pwd: &Path) -> T { + let mut cmds = vec!["--config-dir", pwd.to_str().unwrap()]; + cmds.extend_from_slice(args); + T::parse_arg_vec(&cmds).unwrap() + } + + /// Parse a command using an array of `&str`s, which passes the strings directly to clap + /// avoiding some issues `cmd` has with shlex. Use the current `TestEnv` pwd. + pub fn cmd_arr>(&self, args: &[&str]) -> T { + Self::cmd_arr_with_pwd(args, self.dir()) + } + + /// A convenience method for using the invoke command. + pub async fn invoke>(&self, command_str: &[I]) -> Result { + let cmd = contract::invoke::Cmd::parse_arg_vec( + &command_str + .iter() + .map(AsRef::as_ref) + .filter(|s| !s.is_empty()) + .collect::>(), + ) + .unwrap(); + self.invoke_cmd(cmd).await + } + + /// Invoke an already parsed invoke command + pub async fn invoke_cmd(&self, mut cmd: invoke::Cmd) -> Result { + cmd.set_pwd(self.dir()); + cmd.run_against_rpc_server(&global::Args { + locator: config::locator::Args { + global: false, + config_dir: None, + }, + filter_logs: Vec::default(), + quiet: false, + verbose: false, + very_verbose: false, + list: false, + }) + .await + } + + /// Reference to current directory of the `TestEnv`. + pub fn dir(&self) -> &TempDir { + &self.temp_dir + } + + /// Returns the public key corresponding to the test keys's `hd_path` + pub fn test_address(&self, hd_path: usize) -> String { + self.cmd::(&format!("--hd-path={hd_path}")) + .public_key() + .unwrap() + .to_string() + } + + /// Returns the private key corresponding to the test keys's `hd_path` + pub fn test_show(&self, hd_path: usize) -> String { + self.cmd::(&format!("--hd-path={hd_path}")) + .private_key() + .unwrap() + .to_string() + } + + /// Copy the contents of the current `TestEnv` to another `TestEnv` + pub fn fork(&self) -> Result { + let this = TestEnv::new()?; + self.save(&this.temp_dir)?; + Ok(this) + } + + /// Save the current state of the `TestEnv` to the given directory. + pub fn save(&self, dst: &Path) -> Result<(), Error> { + fs_extra::dir::copy(&self.temp_dir, dst, &CopyOptions::new())?; + Ok(()) + } +} + +pub fn temp_ledger_file() -> OsString { + TempDir::new() + .unwrap() + .child("ledger.json") + .as_os_str() + .into() +} + +pub trait AssertExt { + fn stdout_as_str(&self) -> String; +} + +impl AssertExt for Assert { + fn stdout_as_str(&self) -> String { + String::from_utf8(self.get_output().stdout.clone()) + .expect("failed to make str") + .trim() + .to_owned() + } +} +pub trait CommandExt { + fn json_arg(&mut self, j: A) -> &mut Self + where + A: Display; +} + +impl CommandExt for Command { + fn json_arg(&mut self, j: A) -> &mut Self + where + A: Display, + { + self.arg(OsString::from(j.to_string())) + } +} diff --git a/cmd/crates/soroban-test/src/wasm.rs b/cmd/crates/soroban-test/src/wasm.rs new file mode 100644 index 00000000..d03114b9 --- /dev/null +++ b/cmd/crates/soroban-test/src/wasm.rs @@ -0,0 +1,61 @@ +use std::{fmt::Display, fs, path::PathBuf}; + +use sha2::{Digest, Sha256}; +use soroban_env_host::xdr; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Xdr(#[from] xdr::Error), +} + +pub enum Wasm<'a> { + Release(&'a str), + Custom(&'a str, &'a str), +} + +fn find_target_dir() -> Option { + let path = std::env::current_dir().unwrap(); + for parent in path.ancestors() { + let path = parent.join("target"); + if path.is_dir() { + return Some(path); + } + } + None +} + +impl Wasm<'_> { + /// # Panics + /// + /// # if not found + pub fn path(&self) -> PathBuf { + let path = find_target_dir().unwrap().join("wasm32-unknown-unknown"); + let mut path = match self { + Wasm::Release(name) => path.join("release").join(name), + Wasm::Custom(profile, name) => path.join(profile).join(name), + }; + path.set_extension("wasm"); + assert!(path.is_file(), "File not found: {}. run 'make build-test-wasms' to generate .wasm files before running this test", path.display()); + std::env::current_dir().unwrap().join(path) + } + + /// # Panics + /// + /// # if not found + pub fn bytes(&self) -> Vec { + fs::read(self.path()).unwrap() + } + + /// # Errors + /// + pub fn hash(&self) -> Result { + Ok(xdr::Hash(Sha256::digest(self.bytes()).into())) + } +} + +impl Display for Wasm<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.path().display()) + } +} diff --git a/cmd/crates/soroban-test/tests/fixtures/args/world b/cmd/crates/soroban-test/tests/fixtures/args/world new file mode 100644 index 00000000..04fea064 --- /dev/null +++ b/cmd/crates/soroban-test/tests/fixtures/args/world @@ -0,0 +1 @@ +world \ No newline at end of file diff --git a/cmd/crates/soroban-test/tests/fixtures/hello/Cargo.lock b/cmd/crates/soroban-test/tests/fixtures/hello/Cargo.lock new file mode 100644 index 00000000..dee1dea7 --- /dev/null +++ b/cmd/crates/soroban-test/tests/fixtures/hello/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "soroban-hello" +version = "0.1.0" diff --git a/cmd/crates/soroban-test/tests/fixtures/hello/Cargo.toml b/cmd/crates/soroban-test/tests/fixtures/hello/Cargo.toml new file mode 100644 index 00000000..01b80b0f --- /dev/null +++ b/cmd/crates/soroban-test/tests/fixtures/hello/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "soroban-hello" +version = "20.2.0" +edition = "2021" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/cmd/crates/soroban-test/tests/fixtures/hello/src/main.rs b/cmd/crates/soroban-test/tests/fixtures/hello/src/main.rs new file mode 100644 index 00000000..e7a11a96 --- /dev/null +++ b/cmd/crates/soroban-test/tests/fixtures/hello/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/cmd/crates/soroban-test/tests/fixtures/test-jsons/get-events.json b/cmd/crates/soroban-test/tests/fixtures/test-jsons/get-events.json new file mode 100644 index 00000000..1fe8e42f --- /dev/null +++ b/cmd/crates/soroban-test/tests/fixtures/test-jsons/get-events.json @@ -0,0 +1,57 @@ +{ + "latestLedger": 43601285, + "events": [ + { + "type": "contract", + "ledger": "40", + "ledgerClosedAt": "2022-12-14T01:01:20Z", + "contractId": "CBXL4AIUVYK7OLYYP4C5A3OLM2ZCXWLSDB2VZG2GI2YDJK4WD7A5LTHT", + "id": "0000000171798695937-0000000001", + "pagingToken": "0000000171798695937-0000000001", + "topic": [ + "AAAABQAAAAdDT1VOVEVSAA==", + "AAAABQAAAAlpbmNyZW1lbnQAAAA=" + ], + "value": "AAAAAQAAAAE=" + }, + { + "type": "system", + "ledger": "43601283", + "ledgerClosedAt": "2022-11-16T16:10:41Z", + "contractId": "CDR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OO5Z", + "id": "0187266084548644865-0000000003", + "pagingToken": "187266084548644865-3", + "topic": [ + "AAAABQAAAAh0cmFuc2Zlcg==", + "AAAAAQB6Mcc=" + ], + "value": "AAAABQAAAApHaWJNb255UGxzAAA=" + }, + { + "type": "contract", + "ledger": "43601284", + "ledgerClosedAt": "2022-11-16T16:10:46Z", + "contractId": "CDR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OO5Z", + "id": "0187266088843612161-0000000003", + "pagingToken": "187266088843612161-3", + "topic": [ + "AAAABQAAAAh0cmFuc2Zlcg==", + "AAAAAQB6Mcc=" + ], + "value": "AAAABQAAAApHaWJNb255UGxzAAA=" + }, + { + "type": "system", + "ledger": "43601285", + "ledgerClosedAt": "2022-11-16T16:10:51Z", + "contractId": "CCR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OTE2", + "id": "0187266093138579457-0000000003", + "pagingToken": "187266093138579457-3", + "topic": [ + "AAAABQAAAAh0cmFuc2Zlcg==", + "AAAAAQB6Mcc=" + ], + "value": "AAAABQAAAApHaWJNb255UGxzAAA=" + } + ] +} diff --git a/cmd/crates/soroban-test/tests/fixtures/test-wasms/hello_world/Cargo.toml b/cmd/crates/soroban-test/tests/fixtures/test-wasms/hello_world/Cargo.toml new file mode 100644 index 00000000..e5ced55f --- /dev/null +++ b/cmd/crates/soroban-test/tests/fixtures/test-wasms/hello_world/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "test_hello_world" +version = "20.2.0" +authors = ["Stellar Development Foundation "] +license = "Apache-2.0" +edition = "2021" +publish = false +rust-version.workspace = true + +[lib] +crate-type = ["cdylib", "rlib"] +doctest = false + +[dependencies] +soroban-sdk = { workspace = true } + +[dev-dependencies] +soroban-sdk = { workspace = true, features = ["testutils"]} diff --git a/cmd/crates/soroban-test/tests/fixtures/test-wasms/hello_world/src/lib.rs b/cmd/crates/soroban-test/tests/fixtures/test-wasms/hello_world/src/lib.rs new file mode 100644 index 00000000..40006a1b --- /dev/null +++ b/cmd/crates/soroban-test/tests/fixtures/test-wasms/hello_world/src/lib.rs @@ -0,0 +1,86 @@ +#![no_std] +use soroban_sdk::{ + contract, contractimpl, log, symbol_short, vec, Address, BytesN, Env, String, Symbol, Vec, +}; + +const COUNTER: Symbol = symbol_short!("COUNTER"); + +#[contract] +pub struct Contract; + +#[contractimpl] +impl Contract { + pub fn hello(env: Env, world: Symbol) -> Vec { + vec![&env, symbol_short!("Hello"), world] + } + + pub fn world(env: Env, hello: Symbol) -> Vec { + vec![&env, symbol_short!("Hello"), hello] + } + + pub fn not(env: Env, boolean: bool) -> Vec { + vec![&env, !boolean] + } + + pub fn auth(env: Env, addr: Address, world: Symbol) -> Address { + addr.require_auth(); + // Emit test event + env.events().publish(("auth",), world); + + addr + } + + // get current count + pub fn get_count(env: Env) -> u32 { + env.storage().persistent().get(&COUNTER).unwrap_or(0) + } + + // increment count and return new one + pub fn inc(env: Env) -> u32 { + let mut count: u32 = env.storage().persistent().get(&COUNTER).unwrap_or(0); // Panic if the value of COUNTER is not u32. + log!(&env, "count: {}", count); + + // Increment the count. + count += 1; + + // Save the count. + env.storage().persistent().set(&COUNTER, &count); + count + } + + pub fn prng_u64_in_range(env: Env, low: u64, high: u64) -> u64 { + env.prng().gen_range(low..=high) + } + + pub fn upgrade_contract(env: Env, hash: BytesN<32>) { + env.deployer().update_current_contract_wasm(hash); + } + + #[allow(unused_variables)] + pub fn multi_word_cmd(env: Env, contract_owner: String) {} + /// Logs a string with `hello ` in front. + pub fn log(env: Env, str: Symbol) { + env.events().publish( + (Symbol::new(&env, "hello"), Symbol::new(&env, "")), + str.clone(), + ); + log!(&env, "hello {}", str); + } +} + +#[cfg(test)] +mod test { + use soroban_sdk::{symbol_short, vec, Env}; + + use crate::{Contract, ContractClient}; + + #[test] + fn test_hello() { + let env = Env::default(); + let contract_id = env.register_contract(None, Contract); + let client = ContractClient::new(&env, &contract_id); + let world = symbol_short!("world"); + let res = client.hello(&world); + assert_eq!(res, vec![&env, symbol_short!("Hello"), world]); + } +} diff --git a/cmd/crates/soroban-test/tests/it/arg_parsing.rs b/cmd/crates/soroban-test/tests/it/arg_parsing.rs new file mode 100644 index 00000000..c245fd8c --- /dev/null +++ b/cmd/crates/soroban-test/tests/it/arg_parsing.rs @@ -0,0 +1,215 @@ +use crate::util::CUSTOM_TYPES; +use serde_json::json; +use soroban_env_host::xdr::{ + ScBytes, ScSpecTypeBytesN, ScSpecTypeDef, ScSpecTypeOption, ScSpecTypeUdt, ScVal, +}; +use soroban_spec_tools::{from_string_primitive, Spec}; + +#[test] +fn parse_bool() { + println!( + "{:#?}", + from_string_primitive("true", &ScSpecTypeDef::Bool,).unwrap() + ); +} + +#[test] +fn parse_null() { + let parsed = from_string_primitive( + "null", + &ScSpecTypeDef::Option(Box::new(ScSpecTypeOption { + value_type: Box::new(ScSpecTypeDef::Bool), + })), + ) + .unwrap(); + println!("{parsed:#?}"); + assert!(parsed == ScVal::Void); +} + +#[test] +fn parse_u32() { + let u32_ = 42u32; + let res = &format!("{u32_}"); + println!( + "{:#?}", + from_string_primitive(res, &ScSpecTypeDef::U32,).unwrap() + ); +} + +#[test] +fn parse_i32() { + let i32_ = -42_i32; + let res = &format!("{i32_}"); + println!( + "{:#?}", + from_string_primitive(res, &ScSpecTypeDef::I32,).unwrap() + ); +} + +#[test] +fn parse_u64() { + let b = 42_000_000_000u64; + let res = &format!("{b}"); + println!( + "{:#?}", + from_string_primitive(res, &ScSpecTypeDef::U64,).unwrap() + ); +} + +#[test] +fn parse_u128() { + let b = 340_000_000_000_000_000_000_000_000_000_000_000_000u128; + let res = &format!("{b}"); + println!( + "{:#?}", + from_string_primitive(res, &ScSpecTypeDef::U128,).unwrap() + ); +} + +#[test] +fn parse_i128() { + let b = -170_000_000_000_000_000_000_000_000_000_000_000_000i128; + let res = &format!("{b}"); + println!( + "{:#?}", + from_string_primitive(res, &ScSpecTypeDef::I128,).unwrap() + ); +} + +#[test] +fn parse_i256() { + let b = -170_000_000_000_000_000_000_000_000_000_000_000_000i128; + let res = &format!("{b}"); + let entries = get_spec(); + entries.from_string(res, &ScSpecTypeDef::I256).unwrap(); + println!( + "{:#?}", + from_string_primitive(res, &ScSpecTypeDef::I256,).unwrap() + ); +} + +#[test] +fn parse_bytes() { + let b = from_string_primitive(r"beefface", &ScSpecTypeDef::Bytes).unwrap(); + assert_eq!( + b, + ScVal::Bytes(ScBytes(vec![0xbe, 0xef, 0xfa, 0xce].try_into().unwrap())) + ); + println!("{b:#?}"); +} + +#[test] +fn parse_bytes_when_hex_is_all_numbers() { + let b = from_string_primitive(r"4554", &ScSpecTypeDef::Bytes).unwrap(); + assert_eq!( + b, + ScVal::Bytes(ScBytes(vec![0x45, 0x54].try_into().unwrap())) + ); + println!("{b:#?}"); +} + +#[test] +fn parse_bytesn() { + let b = from_string_primitive( + r"beefface", + &ScSpecTypeDef::BytesN(ScSpecTypeBytesN { n: 4 }), + ) + .unwrap(); + assert_eq!( + b, + ScVal::Bytes(ScBytes(vec![0xbe, 0xef, 0xfa, 0xce].try_into().unwrap())) + ); + println!("{b:#?}"); +} + +#[test] +fn parse_bytesn_when_hex_is_all_numbers() { + let b = + from_string_primitive(r"4554", &ScSpecTypeDef::BytesN(ScSpecTypeBytesN { n: 2 })).unwrap(); + assert_eq!( + b, + ScVal::Bytes(ScBytes(vec![0x45, 0x54].try_into().unwrap())) + ); + println!("{b:#?}",); +} + +#[test] +fn parse_symbol() { + // let b = "hello"; + // let res = &parse_json(&HashMap::new(), &ScSpecTypeDef::Symbol, &json! {b}).unwrap(); + // println!("{res}"); + println!( + "{:#?}", + from_string_primitive(r#""hello""#, &ScSpecTypeDef::Symbol).unwrap() + ); +} + +#[test] +fn parse_symbol_with_no_quotation_marks() { + // let b = "hello"; + // let res = &parse_json(&HashMap::new(), &ScSpecTypeDef::Symbol, &json! {b}).unwrap(); + // println!("{res}"); + println!( + "{:#?}", + from_string_primitive("hello", &ScSpecTypeDef::Symbol).unwrap() + ); +} + +#[test] +fn parse_optional_symbol_with_no_quotation_marks() { + let parsed = from_string_primitive( + "hello", + &ScSpecTypeDef::Option(Box::new(ScSpecTypeOption { + value_type: Box::new(ScSpecTypeDef::Symbol), + })), + ) + .unwrap(); + println!("{parsed:#?}"); + assert!(parsed == ScVal::Symbol("hello".try_into().unwrap())); +} + +#[test] +fn parse_optional_bool_with_no_quotation_marks() { + let parsed = from_string_primitive( + "true", + &ScSpecTypeDef::Option(Box::new(ScSpecTypeOption { + value_type: Box::new(ScSpecTypeDef::Bool), + })), + ) + .unwrap(); + println!("{parsed:#?}"); + assert!(parsed == ScVal::Bool(true)); +} + +#[test] +fn parse_obj() { + let type_ = &ScSpecTypeDef::Udt(ScSpecTypeUdt { + name: "Test".parse().unwrap(), + }); + let entries = get_spec(); + let val = &json!({"a": 42, "b": false, "c": "world"}); + println!("{:#?}", entries.from_json(val, type_)); +} + +#[test] +fn parse_enum() { + let entries = get_spec(); + let func = entries.find_function("simple").unwrap(); + println!("{func:#?}"); + let type_ = &func.inputs.as_slice()[0].type_; + println!("{:#?}", entries.from_json(&json!("First"), type_)); +} + +#[test] +fn parse_enum_const() { + let entries = get_spec(); + let func = entries.find_function("card").unwrap(); + println!("{func:#?}"); + let type_ = &func.inputs.as_slice()[0].type_; + println!("{:#?}", entries.from_json(&json!(11), type_)); +} + +fn get_spec() -> Spec { + let res = soroban_spec::read::from_wasm(&CUSTOM_TYPES.bytes()).unwrap(); + Spec(Some(res)) +} diff --git a/cmd/crates/soroban-test/tests/it/config.rs b/cmd/crates/soroban-test/tests/it/config.rs new file mode 100644 index 00000000..5912b2cf --- /dev/null +++ b/cmd/crates/soroban-test/tests/it/config.rs @@ -0,0 +1,223 @@ +use assert_fs::TempDir; +use soroban_test::TestEnv; +use std::{fs, path::Path}; + +use crate::util::{add_key, add_test_id, SecretKind, DEFAULT_SEED_PHRASE}; +use soroban_cli::commands::network; + +const NETWORK_PASSPHRASE: &str = "Local Sandbox Stellar Network ; September 2022"; + +#[test] +fn set_and_remove_network() { + TestEnv::with_default(|sandbox| { + add_network(sandbox, "local"); + let dir = sandbox.dir().join(".soroban").join("network"); + let read_dir = std::fs::read_dir(dir); + println!("{read_dir:#?}"); + let file = read_dir.unwrap().next().unwrap().unwrap(); + assert_eq!(file.file_name().to_str().unwrap(), "local.toml"); + + let res = sandbox.cmd::(""); + let res = res.ls().unwrap(); + assert_eq!(res.len(), 1); + assert_eq!(&res[0], "local"); + + sandbox.cmd::("local").run().unwrap(); + + // sandbox + // .new_assert_cmd("config") + // .arg("network") + // .arg("rm") + // .arg("local") + // .assert() + // .stdout(""); + sandbox + .new_assert_cmd("network") + .arg("ls") + .assert() + .stdout("\n"); + }); +} + +fn add_network(sandbox: &TestEnv, name: &str) { + sandbox + .new_assert_cmd("network") + .arg("add") + .args([ + "--rpc-url=https://127.0.0.1", + "--network-passphrase", + NETWORK_PASSPHRASE, + name, + ]) + .assert() + .success() + .stderr("") + .stdout(""); +} + +fn add_network_global(sandbox: &TestEnv, dir: &Path, name: &str) { + sandbox + .new_assert_cmd("network") + .env("XDG_CONFIG_HOME", dir.to_str().unwrap()) + .arg("add") + .arg("--global") + .arg("--rpc-url") + .arg("https://127.0.0.1") + .arg("--network-passphrase") + .arg("Local Sandbox Stellar Network ; September 2022") + .arg(name) + .assert() + .success(); +} + +#[test] +fn set_and_remove_global_network() { + let sandbox = TestEnv::default(); + let dir = TempDir::new().unwrap(); + + add_network_global(&sandbox, &dir, "global"); + + sandbox + .new_assert_cmd("network") + .env("XDG_CONFIG_HOME", dir.to_str().unwrap()) + .arg("ls") + .arg("--global") + .assert() + .stdout("global\n"); + + sandbox + .new_assert_cmd("network") + .env("XDG_CONFIG_HOME", dir.to_str().unwrap()) + .arg("rm") + .arg("--global") + .arg("global") + .assert() + .stdout(""); + + sandbox + .new_assert_cmd("network") + .env("XDG_CONFIG_HOME", dir.to_str().unwrap()) + .arg("ls") + .assert() + .stdout("\n"); +} + +#[test] +fn multiple_networks() { + let sandbox = TestEnv::default(); + let ls = || -> Vec { sandbox.cmd::("").ls().unwrap() }; + + add_network(&sandbox, "local"); + println!("{:#?}", ls()); + add_network(&sandbox, "local2"); + + assert_eq!(ls().as_slice(), ["local".to_owned(), "local2".to_owned()]); + + sandbox.cmd::("local").run().unwrap(); + + assert_eq!(ls().as_slice(), ["local2".to_owned()]); + + let sub_dir = sandbox.dir().join("sub_directory"); + fs::create_dir(&sub_dir).unwrap(); + + TestEnv::cmd_arr_with_pwd::( + &[ + "--rpc-url", + "https://127.0.0.1", + "--network-passphrase", + "Local Sandbox Stellar Network ; September 2022", + "local3", + ], + &sub_dir, + ) + .run() + .unwrap(); + + assert_eq!(ls().as_slice(), ["local2".to_owned(), "local3".to_owned()]); +} + +#[test] +fn read_key() { + let sandbox = TestEnv::default(); + let dir = sandbox.dir().as_ref(); + add_test_id(dir); + let ident_dir = dir.join(".soroban/identity"); + assert!(ident_dir.exists()); + sandbox + .new_assert_cmd("keys") + .arg("ls") + .assert() + .stdout(predicates::str::contains("test_id\n")); +} + +#[test] +fn generate_key() { + let sandbox = TestEnv::default(); + sandbox + .new_assert_cmd("keys") + .arg("generate") + .arg("--network=futurenet") + .arg("--no-fund") + .arg("--seed") + .arg("0000000000000000") + .arg("test_2") + .assert() + .stdout("") + .success(); + + sandbox + .new_assert_cmd("keys") + .arg("ls") + .assert() + .stdout(predicates::str::contains("test_2\n")); + let file_contents = + fs::read_to_string(sandbox.dir().join(".soroban/identity/test_2.toml")).unwrap(); + assert_eq!( + file_contents, + format!("seed_phrase = \"{DEFAULT_SEED_PHRASE}\"\n") + ); +} + +#[test] +fn seed_phrase() { + let sandbox = TestEnv::default(); + let dir = sandbox.dir(); + add_key( + dir, + "test_seed", + SecretKind::Seed, + "one two three four five six seven eight nine ten eleven twelve", + ); + + sandbox + .new_assert_cmd("keys") + .current_dir(dir) + .arg("ls") + .assert() + .stdout(predicates::str::contains("test_seed\n")); +} + +#[test] +fn use_env() { + let sandbox = TestEnv::default(); + + sandbox + .new_assert_cmd("keys") + .env( + "SOROBAN_SECRET_KEY", + "SDIY6AQQ75WMD4W46EYB7O6UYMHOCGQHLAQGQTKHDX4J2DYQCHVCQYFD", + ) + .arg("add") + .arg("bob") + .assert() + .stdout("") + .success(); + + sandbox + .new_assert_cmd("keys") + .arg("show") + .arg("bob") + .assert() + .success() + .stdout("SDIY6AQQ75WMD4W46EYB7O6UYMHOCGQHLAQGQTKHDX4J2DYQCHVCQYFD\n"); +} diff --git a/cmd/crates/soroban-test/tests/it/hello_world.rs b/cmd/crates/soroban-test/tests/it/hello_world.rs new file mode 100644 index 00000000..4c45403a --- /dev/null +++ b/cmd/crates/soroban-test/tests/it/hello_world.rs @@ -0,0 +1,22 @@ +use soroban_cli::commands::contract::{self, fetch}; +use soroban_test::TestEnv; +use std::path::PathBuf; + +use crate::util::{ + add_test_seed, is_rpc, network_passphrase, network_passphrase_arg, rpc_url, rpc_url_arg, + DEFAULT_PUB_KEY, DEFAULT_PUB_KEY_1, DEFAULT_SECRET_KEY, DEFAULT_SEED_PHRASE, HELLO_WORLD, + TEST_SALT, +}; + +#[tokio::test] +async fn fetch() { + if !is_rpc() { + return; + } + let e = TestEnv::default(); + let f = e.dir().join("contract.wasm"); + let id = deploy_hello(&e); + let cmd = e.cmd_arr::(&["--id", &id, "--out-file", f.to_str().unwrap()]); + cmd.run().await.unwrap(); + assert!(f.exists()); +} diff --git a/cmd/crates/soroban-test/tests/it/help.rs b/cmd/crates/soroban-test/tests/it/help.rs new file mode 100644 index 00000000..6d4680e7 --- /dev/null +++ b/cmd/crates/soroban-test/tests/it/help.rs @@ -0,0 +1,97 @@ +use soroban_cli::commands::contract; +use soroban_test::TestEnv; + +use crate::util::{invoke_custom as invoke, CUSTOM_TYPES}; + +async fn invoke_custom(func: &str, args: &str) -> Result { + let e = &TestEnv::default(); + invoke(e, "1", func, args, &CUSTOM_TYPES.path()).await +} + +#[tokio::test] +async fn generate_help() { + assert!(invoke_custom("strukt_hel", "--help") + .await + .unwrap() + .contains("Example contract method which takes a struct")); +} + +#[tokio::test] +async fn vec_help() { + assert!(invoke_custom("vec", "--help") + .await + .unwrap() + .contains("Array")); +} + +#[tokio::test] +async fn tuple_help() { + assert!(invoke_custom("tuple", "--help") + .await + .unwrap() + .contains("Tuple")); +} + +#[tokio::test] +async fn strukt_help() { + let output = invoke_custom("strukt", "--help").await.unwrap(); + assert!(output.contains("--strukt '{ \"a\": 1, \"b\": true, \"c\": \"hello\" }'",)); + assert!(output.contains("This is from the rust doc above the struct Test",)); +} + +#[tokio::test] +async fn complex_enum_help() { + let output = invoke_custom("complex", "--help").await.unwrap(); + assert!(output.contains(r#"--complex '{"Struct":{ "a": 1, "b": true, "c": "hello" }}"#,)); + assert!(output.contains(r#"{"Tuple":[{ "a": 1, "b": true, "c": "hello" }"#,)); + assert!(output.contains(r#"{"Enum":"First"|"Second"|"Third"}"#,)); + assert!(output.contains( + r#"{"Asset":["GDIY6AQQ75WMD4W46EYB7O6UYMHOCGQHLAQGQTKHDX4J2DYQCHVCR4W4", "-100"]}"#, + )); + assert!(output.contains(r#""Void"'"#)); +} + +#[tokio::test] +async fn multi_arg_failure() { + assert!(matches!( + invoke_custom("multi_args", "--b").await.unwrap_err(), + contract::invoke::Error::MissingArgument(_) + )); +} + +#[tokio::test] +async fn handle_arg_larger_than_i32_failure() { + let res = invoke_custom("i32_", &format!("--i32_={}", u32::MAX)).await; + assert!(matches!( + res, + Err(contract::invoke::Error::CannotParseArg { .. }) + )); +} + +#[tokio::test] +async fn handle_arg_larger_than_i64_failure() { + let res = invoke_custom("i64_", &format!("--i64_={}", u64::MAX)).await; + assert!(matches!( + res, + Err(contract::invoke::Error::CannotParseArg { .. }) + )); +} + +#[test] +fn build() { + let sandbox = TestEnv::default(); + let cargo_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let hello_world_contract_path = + cargo_dir.join("tests/fixtures/test-wasms/hello_world/Cargo.toml"); + sandbox + .new_assert_cmd("contract") + .arg("build") + .arg("--manifest-path") + .arg(hello_world_contract_path) + .arg("--profile") + .arg("test-wasms") + .arg("--package") + .arg("test_hello_world") + .assert() + .success(); +} diff --git a/cmd/crates/soroban-test/tests/it/integration.rs b/cmd/crates/soroban-test/tests/it/integration.rs new file mode 100644 index 00000000..4e92b931 --- /dev/null +++ b/cmd/crates/soroban-test/tests/it/integration.rs @@ -0,0 +1,5 @@ +mod custom_types; +mod dotenv; +mod hello_world; +mod util; +mod wrap; diff --git a/cmd/crates/soroban-test/tests/it/integration/custom_types.rs b/cmd/crates/soroban-test/tests/it/integration/custom_types.rs new file mode 100644 index 00000000..fda2c1f6 --- /dev/null +++ b/cmd/crates/soroban-test/tests/it/integration/custom_types.rs @@ -0,0 +1,419 @@ +use serde_json::json; + +use soroban_cli::commands; +use soroban_test::TestEnv; + +use crate::integration::util::{deploy_custom, extend_contract, CUSTOM_TYPES}; + +use super::util::invoke_with_roundtrip; + +fn invoke_custom(e: &TestEnv, id: &str, func: &str) -> assert_cmd::Command { + let mut s = e.new_assert_cmd("contract"); + s.arg("invoke").arg("--id").arg(id).arg("--").arg(func); + s +} + +#[tokio::test] +async fn parse() { + let sandbox = &TestEnv::default(); + let id = &deploy_custom(sandbox); + extend_contract(sandbox, id, CUSTOM_TYPES).await; + symbol(sandbox, id); + string_with_quotes(sandbox, id).await; + symbol_with_quotes(sandbox, id).await; + multi_arg_success(sandbox, id); + bytes_as_file(sandbox, id); + map(sandbox, id).await; + vec_(sandbox, id).await; + tuple(sandbox, id).await; + strukt(sandbox, id).await; + tuple_strukt(sandbox, id).await; + enum_2_str(sandbox, id).await; + e_2_s_enum(sandbox, id).await; + asset(sandbox, id).await; + e_2_s_tuple(sandbox, id).await; + e_2_s_strukt(sandbox, id).await; + number_arg(sandbox, id).await; + number_arg_return_err(sandbox, id).await; + i32(sandbox, id).await; + i64(sandbox, id).await; + negative_i32(sandbox, id).await; + negative_i64(sandbox, id).await; + account_address(sandbox, id).await; + contract_address(sandbox, id).await; + bytes(sandbox, id).await; + const_enum(sandbox, id).await; + number_arg_return_ok(sandbox, id); + void(sandbox, id); + val(sandbox, id); + parse_u128(sandbox, id); + parse_i128(sandbox, id); + parse_negative_i128(sandbox, id); + parse_u256(sandbox, id); + parse_i256(sandbox, id); + parse_negative_i256(sandbox, id); + boolean(sandbox, id); + boolean_two(sandbox, id); + boolean_no_flag(sandbox, id); + boolean_false(sandbox, id); + boolean_not(sandbox, id); + boolean_not_no_flag(sandbox, id); + option_none(sandbox, id); + option_some(sandbox, id); +} + +fn symbol(sandbox: &TestEnv, id: &str) { + invoke_custom(sandbox, id, "hello") + .arg("--hello") + .arg("world") + .assert() + .success() + .stdout( + r#""world" +"#, + ); +} + +async fn string_with_quotes(sandbox: &TestEnv, id: &str) { + invoke_with_roundtrip(sandbox, id, "string", json!("hello world")).await; +} + +async fn symbol_with_quotes(sandbox: &TestEnv, id: &str) { + invoke_with_roundtrip(sandbox, id, "hello", json!("world")).await; +} + +fn multi_arg_success(sandbox: &TestEnv, id: &str) { + invoke_custom(sandbox, id, "multi_args") + .arg("--a") + .arg("42") + .arg("--b") + .assert() + .success() + .stdout("42\n"); +} + +fn bytes_as_file(sandbox: &TestEnv, id: &str) { + let env = &TestEnv::default(); + let path = env.temp_dir.join("bytes.txt"); + std::fs::write(&path, 0x0073_7465_6c6c_6172u128.to_be_bytes()).unwrap(); + invoke_custom(sandbox, id, "bytes") + .arg("--bytes-file-path") + .arg(path) + .assert() + .success() + .stdout("\"0000000000000000007374656c6c6172\"\n"); +} + +async fn map(sandbox: &TestEnv, id: &str) { + invoke_with_roundtrip(sandbox, id, "map", json!({"0": true, "1": false})).await; +} + +async fn vec_(sandbox: &TestEnv, id: &str) { + invoke_with_roundtrip(sandbox, id, "vec", json!([0, 1])).await; +} + +async fn tuple(sandbox: &TestEnv, id: &str) { + invoke_with_roundtrip(sandbox, id, "tuple", json!(["hello", 0])).await; +} + +async fn strukt(sandbox: &TestEnv, id: &str) { + invoke_with_roundtrip( + sandbox, + id, + "strukt", + json!({"a": 42, "b": true, "c": "world"}), + ) + .await; +} + +async fn tuple_strukt(sandbox: &TestEnv, id: &str) { + invoke_with_roundtrip( + sandbox, + id, + "tuple_strukt", + json!([{"a": 42, "b": true, "c": "world"}, "First"]), + ) + .await; +} + +async fn enum_2_str(sandbox: &TestEnv, id: &str) { + invoke_with_roundtrip(sandbox, id, "simple", json!("First")).await; +} + +async fn e_2_s_enum(sandbox: &TestEnv, id: &str) { + invoke_with_roundtrip(sandbox, id, "complex", json!({"Enum": "First"})).await; +} + +async fn asset(sandbox: &TestEnv, id: &str) { + invoke_with_roundtrip( + sandbox, + id, + "complex", + json!({"Asset": ["CB64D3G7SM2RTH6JSGG34DDTFTQ5CFDKVDZJZSODMCX4NJ2HV2KN7OHT", "100" ]}), + ) + .await; +} + +fn complex_tuple() -> serde_json::Value { + json!({"Tuple": [{"a": 42, "b": true, "c": "world"}, "First"]}) +} + +async fn e_2_s_tuple(sandbox: &TestEnv, id: &str) { + invoke_with_roundtrip(sandbox, id, "complex", complex_tuple()).await; +} + +async fn e_2_s_strukt(sandbox: &TestEnv, id: &str) { + invoke_with_roundtrip( + sandbox, + id, + "complex", + json!({"Struct": {"a": 42, "b": true, "c": "world"}}), + ) + .await; +} + +async fn number_arg(sandbox: &TestEnv, id: &str) { + invoke_with_roundtrip(sandbox, id, "u32_", 42).await; +} + +fn number_arg_return_ok(sandbox: &TestEnv, id: &str) { + invoke_custom(sandbox, id, "u32_fail_on_even") + .arg("--u32_") + .arg("1") + .assert() + .success() + .stdout("1\n"); +} + +async fn number_arg_return_err(sandbox: &TestEnv, id: &str) { + let res = sandbox + .invoke(&["--id", id, "--", "u32_fail_on_even", "--u32_=2"]) + .await + .unwrap_err(); + if let commands::contract::invoke::Error::ContractInvoke(name, doc) = &res { + assert_eq!(name, "NumberMustBeOdd"); + assert_eq!(doc, "Please provide an odd number"); + }; + println!("{res:#?}"); +} + +fn void(sandbox: &TestEnv, id: &str) { + invoke_custom(sandbox, id, "woid") + .assert() + .success() + .stdout("\n") + .stderr(""); +} + +fn val(sandbox: &TestEnv, id: &str) { + invoke_custom(sandbox, id, "val") + .assert() + .success() + .stdout("null\n") + .stderr(""); +} + +async fn i32(sandbox: &TestEnv, id: &str) { + invoke_with_roundtrip(sandbox, id, "i32_", 42).await; +} + +async fn i64(sandbox: &TestEnv, id: &str) { + invoke_with_roundtrip(sandbox, id, "i64_", i64::MAX).await; +} + +async fn negative_i32(sandbox: &TestEnv, id: &str) { + invoke_with_roundtrip(sandbox, id, "i32_", -42).await; +} + +async fn negative_i64(sandbox: &TestEnv, id: &str) { + invoke_with_roundtrip(sandbox, id, "i64_", i64::MIN).await; +} + +async fn account_address(sandbox: &TestEnv, id: &str) { + invoke_with_roundtrip( + sandbox, + id, + "addresse", + json!("GD5KD2KEZJIGTC63IGW6UMUSMVUVG5IHG64HUTFWCHVZH2N2IBOQN7PS"), + ) + .await; +} + +async fn contract_address(sandbox: &TestEnv, id: &str) { + invoke_with_roundtrip( + sandbox, + id, + "addresse", + json!("CA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQGAXE"), + ) + .await; +} + +async fn bytes(sandbox: &TestEnv, id: &str) { + invoke_with_roundtrip(sandbox, id, "bytes", json!("7374656c6c6172")).await; +} + +async fn const_enum(sandbox: &TestEnv, id: &str) { + invoke_with_roundtrip(sandbox, id, "card", "11").await; +} + +fn parse_u128(sandbox: &TestEnv, id: &str) { + let num = "340000000000000000000000000000000000000"; + invoke_custom(sandbox, id, "u128") + .arg("--u128") + .arg(num) + .assert() + .success() + .stdout(format!( + r#""{num}" +"#, + )); +} + +fn parse_i128(sandbox: &TestEnv, id: &str) { + let num = "170000000000000000000000000000000000000"; + invoke_custom(sandbox, id, "i128") + .arg("--i128") + .arg(num) + .assert() + .success() + .stdout(format!( + r#""{num}" +"#, + )); +} + +fn parse_negative_i128(sandbox: &TestEnv, id: &str) { + let num = "-170000000000000000000000000000000000000"; + invoke_custom(sandbox, id, "i128") + .arg("--i128") + .arg(num) + .assert() + .success() + .stdout(format!( + r#""{num}" +"#, + )); +} + +fn parse_u256(sandbox: &TestEnv, id: &str) { + let num = "340000000000000000000000000000000000000"; + invoke_custom(sandbox, id, "u256") + .arg("--u256") + .arg(num) + .assert() + .success() + .stdout(format!( + r#""{num}" +"#, + )); +} + +fn parse_i256(sandbox: &TestEnv, id: &str) { + let num = "170000000000000000000000000000000000000"; + invoke_custom(sandbox, id, "i256") + .arg("--i256") + .arg(num) + .assert() + .success() + .stdout(format!( + r#""{num}" +"#, + )); +} + +fn parse_negative_i256(sandbox: &TestEnv, id: &str) { + let num = "-170000000000000000000000000000000000000"; + invoke_custom(sandbox, id, "i256") + .arg("--i256") + .arg(num) + .assert() + .success() + .stdout(format!( + r#""{num}" +"#, + )); +} + +fn boolean(sandbox: &TestEnv, id: &str) { + invoke_custom(sandbox, id, "boolean") + .arg("--boolean") + .assert() + .success() + .stdout( + r"true +", + ); +} +fn boolean_two(sandbox: &TestEnv, id: &str) { + invoke_custom(sandbox, id, "boolean") + .arg("--boolean") + .arg("true") + .assert() + .success() + .stdout( + r"true +", + ); +} + +fn boolean_no_flag(sandbox: &TestEnv, id: &str) { + invoke_custom(sandbox, id, "boolean") + .assert() + .success() + .stdout( + r"false +", + ); +} + +fn boolean_false(sandbox: &TestEnv, id: &str) { + invoke_custom(sandbox, id, "boolean") + .arg("--boolean") + .arg("false") + .assert() + .success() + .stdout( + r"false +", + ); +} + +fn boolean_not(sandbox: &TestEnv, id: &str) { + invoke_custom(sandbox, id, "not") + .arg("--boolean") + .assert() + .success() + .stdout( + r"false +", + ); +} + +fn boolean_not_no_flag(sandbox: &TestEnv, id: &str) { + invoke_custom(sandbox, id, "not").assert().success().stdout( + r"true +", + ); +} + +fn option_none(sandbox: &TestEnv, id: &str) { + invoke_custom(sandbox, id, "option") + .assert() + .success() + .stdout( + r"null +", + ); +} + +fn option_some(sandbox: &TestEnv, id: &str) { + invoke_custom(sandbox, id, "option") + .arg("--option=1") + .assert() + .success() + .stdout( + r"1 +", + ); +} diff --git a/cmd/crates/soroban-test/tests/it/integration/dotenv.rs b/cmd/crates/soroban-test/tests/it/integration/dotenv.rs new file mode 100644 index 00000000..d7d56aaf --- /dev/null +++ b/cmd/crates/soroban-test/tests/it/integration/dotenv.rs @@ -0,0 +1,64 @@ +use soroban_test::TestEnv; + +use super::util::{deploy_hello, TEST_CONTRACT_ID}; + +fn write_env_file(e: &TestEnv, contents: &str) { + let env_file = e.dir().join(".env"); + std::fs::write(&env_file, contents).unwrap(); + assert_eq!(contents, std::fs::read_to_string(env_file).unwrap()); +} + +fn contract_id() -> String { + format!("SOROBAN_CONTRACT_ID={TEST_CONTRACT_ID}") +} + +#[test] +fn can_read_file() { + TestEnv::with_default(|e| { + deploy_hello(e); + write_env_file(e, &contract_id()); + e.new_assert_cmd("contract") + .arg("invoke") + .arg("--") + .arg("hello") + .arg("--world=world") + .assert() + .stdout("[\"Hello\",\"world\"]\n") + .success(); + }); +} + +#[test] +fn current_env_not_overwritten() { + TestEnv::with_default(|e| { + deploy_hello(e); + write_env_file(e, &contract_id()); + + e.new_assert_cmd("contract") + .env("SOROBAN_CONTRACT_ID", "2") + .arg("invoke") + .arg("--") + .arg("hello") + .arg("--world=world") + .assert() + .stderr("error: Contract not found: CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4\n"); + }); +} + +#[test] +fn cli_args_have_priority() { + TestEnv::with_default(|e| { + deploy_hello(e); + write_env_file(e, &contract_id()); + e.new_assert_cmd("contract") + .env("SOROBAN_CONTRACT_ID", "2") + .arg("invoke") + .arg("--id") + .arg(TEST_CONTRACT_ID) + .arg("--") + .arg("hello") + .arg("--world=world") + .assert() + .stdout("[\"Hello\",\"world\"]\n"); + }); +} diff --git a/cmd/crates/soroban-test/tests/it/integration/hello_world.rs b/cmd/crates/soroban-test/tests/it/integration/hello_world.rs new file mode 100644 index 00000000..7714f70d --- /dev/null +++ b/cmd/crates/soroban-test/tests/it/integration/hello_world.rs @@ -0,0 +1,290 @@ +use soroban_cli::commands::{ + contract::{self, fetch}, + keys, +}; +use soroban_test::TestEnv; + +use crate::{integration::util::extend_contract, util::DEFAULT_SEED_PHRASE}; + +use super::util::{ + add_test_seed, deploy_hello, extend, network_passphrase, network_passphrase_arg, rpc_url, + rpc_url_arg, DEFAULT_PUB_KEY, DEFAULT_PUB_KEY_1, DEFAULT_SECRET_KEY, HELLO_WORLD, +}; + +#[tokio::test] +#[ignore] +async fn invoke() { + let sandbox = &TestEnv::default(); + let id = &deploy_hello(sandbox); + extend_contract(sandbox, id, HELLO_WORLD).await; + // Note that all functions tested here have no state + invoke_hello_world(sandbox, id); + invoke_hello_world_with_lib(sandbox, id).await; + invoke_hello_world_with_lib_two(sandbox, id).await; + invoke_auth(sandbox, id); + invoke_auth_with_identity(sandbox, id).await; + invoke_auth_with_different_test_account_fail(sandbox, id).await; + // invoke_auth_with_different_test_account(sandbox, id); + contract_data_read_failure(sandbox, id); + invoke_with_seed(sandbox, id).await; + invoke_with_sk(sandbox, id).await; + // This does add an identity to local config + invoke_with_id(sandbox, id).await; + handles_kebab_case(sandbox, id).await; + fetch(sandbox, id).await; + invoke_prng_u64_in_range_test(sandbox, id).await; +} + +fn invoke_hello_world(sandbox: &TestEnv, id: &str) { + sandbox + .new_assert_cmd("contract") + .arg("invoke") + .arg("--id") + .arg(id) + .arg("--") + .arg("hello") + .arg("--world=world") + .assert() + .stdout("[\"Hello\",\"world\"]\n") + .success(); +} + +async fn invoke_hello_world_with_lib(e: &TestEnv, id: &str) { + let mut cmd = contract::invoke::Cmd { + contract_id: id.to_string(), + slop: vec!["hello".into(), "--world=world".into()], + ..Default::default() + }; + + cmd.config.network.rpc_url = rpc_url(); + cmd.config.network.network_passphrase = network_passphrase(); + + let res = e.invoke_cmd(cmd).await.unwrap(); + assert_eq!(res, r#"["Hello","world"]"#); +} + +async fn invoke_hello_world_with_lib_two(e: &TestEnv, id: &str) { + let hello_world = HELLO_WORLD.to_string(); + let mut invoke_args = vec!["--id", id, "--wasm", hello_world.as_str()]; + let args = vec!["--", "hello", "--world=world"]; + let res = + if let (Some(rpc), Some(network_passphrase)) = (rpc_url_arg(), network_passphrase_arg()) { + invoke_args.push(&rpc); + invoke_args.push(&network_passphrase); + e.invoke(&[invoke_args, args].concat()).await.unwrap() + } else { + e.invoke(&[invoke_args, args].concat()).await.unwrap() + }; + assert_eq!(res, r#"["Hello","world"]"#); +} + +fn invoke_auth(sandbox: &TestEnv, id: &str) { + sandbox + .new_assert_cmd("contract") + .arg("invoke") + .arg("--id") + .arg(id) + .arg("--wasm") + .arg(HELLO_WORLD.path()) + .arg("--") + .arg("auth") + .arg(&format!("--addr={DEFAULT_PUB_KEY}")) + .arg("--world=world") + .assert() + .stdout(format!("\"{DEFAULT_PUB_KEY}\"\n")) + .success(); + + // Invoke it again without providing the contract, to exercise the deployment + sandbox + .new_assert_cmd("contract") + .arg("invoke") + .arg("--id") + .arg(id) + .arg("--") + .arg("auth") + .arg(&format!("--addr={DEFAULT_PUB_KEY}")) + .arg("--world=world") + .assert() + .stdout(format!("\"{DEFAULT_PUB_KEY}\"\n")) + .success(); +} + +async fn invoke_auth_with_identity(sandbox: &TestEnv, id: &str) { + sandbox + .cmd::("test -d ") + .run() + .await + .unwrap(); + sandbox + .new_assert_cmd("contract") + .arg("invoke") + .arg("--id") + .arg(id) + .arg("--wasm") + .arg(HELLO_WORLD.path()) + .arg("--") + .arg("auth") + .arg("--addr") + .arg(DEFAULT_PUB_KEY) + .arg("--world=world") + .assert() + .stdout(format!("\"{DEFAULT_PUB_KEY}\"\n")) + .success(); +} + +// fn invoke_auth_with_different_test_account(sandbox: &TestEnv, id: &str) { +// sandbox +// .new_assert_cmd("contract") +// .arg("invoke") +// .arg("--hd-path=1") +// .arg("--id") +// .arg(id) +// .arg("--wasm") +// .arg(HELLO_WORLD.path()) +// .arg("--") +// .arg("auth") +// .arg(&format!("--addr={DEFAULT_PUB_KEY_1}")) +// .arg("--world=world") +// .assert() +// .stdout(format!("\"{DEFAULT_PUB_KEY_1}\"\n")) +// .success(); +// } + +async fn invoke_auth_with_different_test_account_fail(sandbox: &TestEnv, id: &str) { + let res = sandbox + .invoke(&[ + "--hd-path=0", + "--id", + id, + &rpc_url_arg().unwrap_or_default(), + &network_passphrase_arg().unwrap_or_default(), + "--", + "auth", + &format!("--addr={DEFAULT_PUB_KEY_1}"), + "--world=world", + ]) + .await; + let e = res.unwrap_err(); + assert!( + matches!(e, contract::invoke::Error::Rpc(_)), + "Expected rpc error got {e:?}" + ); +} + +fn contract_data_read_failure(sandbox: &TestEnv, id: &str) { + sandbox + .new_assert_cmd("contract") + .arg("read") + .arg("--id") + .arg(id) + .arg("--key=COUNTER") + .arg("--durability=persistent") + .assert() + .failure() + .stderr( + "error: no matching contract data entries were found for the specified contract id\n", + ); +} + +#[tokio::test] +async fn contract_data_read() { + const KEY: &str = "COUNTER"; + let sandbox = &TestEnv::default(); + let id = &deploy_hello(sandbox); + let res = sandbox.invoke(&["--id", id, "--", "inc"]).await.unwrap(); + assert_eq!(res.trim(), "1"); + extend(sandbox, id, Some(KEY)).await; + + sandbox + .new_assert_cmd("contract") + .arg("read") + .arg("--id") + .arg(id) + .arg("--key") + .arg(KEY) + .arg("--durability=persistent") + .assert() + .success() + .stdout(predicates::str::starts_with("COUNTER,1")); + + sandbox + .new_assert_cmd("contract") + .arg("invoke") + .arg("--id") + .arg(id) + .arg("--") + .arg("inc") + .assert() + .success(); + + sandbox + .new_assert_cmd("contract") + .arg("read") + .arg("--id") + .arg(id) + .arg("--key") + .arg(KEY) + .arg("--durability=persistent") + .assert() + .success() + .stdout(predicates::str::starts_with("COUNTER,2")); +} + +async fn invoke_with_seed(sandbox: &TestEnv, id: &str) { + invoke_with_source(sandbox, DEFAULT_SEED_PHRASE, id).await; +} + +async fn invoke_with_sk(sandbox: &TestEnv, id: &str) { + invoke_with_source(sandbox, DEFAULT_SECRET_KEY, id).await; +} + +async fn invoke_with_id(sandbox: &TestEnv, id: &str) { + let identity = add_test_seed(sandbox.dir()); + invoke_with_source(sandbox, &identity, id).await; +} + +async fn invoke_with_source(sandbox: &TestEnv, source: &str, id: &str) { + let cmd = sandbox + .invoke(&[ + "--source-account", + source, + "--id", + id, + "--", + "hello", + "--world=world", + ]) + .await + .unwrap(); + assert_eq!(cmd, "[\"Hello\",\"world\"]"); +} + +async fn handles_kebab_case(e: &TestEnv, id: &str) { + assert!(e + .invoke(&["--id", id, "--", "multi-word-cmd", "--contract-owner=world",]) + .await + .is_ok()); +} + +async fn fetch(sandbox: &TestEnv, id: &str) { + let f = sandbox.dir().join("contract.wasm"); + let cmd = sandbox.cmd_arr::(&["--id", id, "--out-file", f.to_str().unwrap()]); + cmd.run().await.unwrap(); + assert!(f.exists()); +} + +async fn invoke_prng_u64_in_range_test(sandbox: &TestEnv, id: &str) { + assert!(sandbox + .invoke(&[ + "--id", + id, + "--wasm", + HELLO_WORLD.path().to_str().unwrap(), + "--", + "prng_u64_in_range", + "--low=0", + "--high=100", + ]) + .await + .is_ok()); +} diff --git a/cmd/crates/soroban-test/tests/it/integration/util.rs b/cmd/crates/soroban-test/tests/it/integration/util.rs new file mode 100644 index 00000000..ea27680b --- /dev/null +++ b/cmd/crates/soroban-test/tests/it/integration/util.rs @@ -0,0 +1,119 @@ +use soroban_cli::commands::contract; +use soroban_test::{TestEnv, Wasm}; +use std::{fmt::Display, path::Path}; + +use crate::util::{add_key, SecretKind}; + +pub const HELLO_WORLD: &Wasm = &Wasm::Custom("test-wasms", "test_hello_world"); +pub const CUSTOM_TYPES: &Wasm = &Wasm::Custom("test-wasms", "test_custom_types"); + +pub fn add_test_seed(dir: &Path) -> String { + let name = "test_seed"; + add_key( + dir, + name, + SecretKind::Seed, + "coral light army gather adapt blossom school alcohol coral light army giggle", + ); + name.to_owned() +} + +pub async fn invoke_with_roundtrip(e: &TestEnv, id: &str, func: &str, data: D) +where + D: Display, +{ + let data = data.to_string(); + println!("{data}"); + let res = e + .invoke(&["--id", id, "--", func, &format!("--{func}"), &data]) + .await + .unwrap(); + assert_eq!(res, data); +} + +pub const DEFAULT_PUB_KEY: &str = "GDIY6AQQ75WMD4W46EYB7O6UYMHOCGQHLAQGQTKHDX4J2DYQCHVCR4W4"; +pub const DEFAULT_SECRET_KEY: &str = "SC36BWNUOCZAO7DMEJNNKFV6BOTPJP7IG5PSHLUOLT6DZFRU3D3XGIXW"; + +pub const DEFAULT_PUB_KEY_1: &str = "GCKZUJVUNEFGD4HLFBUNVYM2QY2P5WQQZMGRA3DDL4HYVT5MW5KG3ODV"; +pub const TEST_SALT: &str = "f55ff16f66f43360266b95db6f8fec01d76031054306ae4a4b380598f6cfd114"; +pub const TEST_CONTRACT_ID: &str = "CBVTIVBYWAO2HNPNGKDCZW4OZYYESTKNGD7IPRTDGQSFJS4QBDQQJX3T"; + +pub fn rpc_url() -> Option { + std::env::var("SOROBAN_RPC_URL").ok() +} + +pub fn rpc_url_arg() -> Option { + rpc_url().map(|url| format!("--rpc-url={url}")) +} + +pub fn network_passphrase() -> Option { + std::env::var("SOROBAN_NETWORK_PASSPHRASE").ok() +} + +pub fn network_passphrase_arg() -> Option { + network_passphrase().map(|p| format!("--network-passphrase={p}")) +} + +pub fn deploy_hello(sandbox: &TestEnv) -> String { + deploy_contract(sandbox, HELLO_WORLD) +} + +pub fn deploy_custom(sandbox: &TestEnv) -> String { + deploy_contract(sandbox, CUSTOM_TYPES) +} + +pub fn deploy_contract(sandbox: &TestEnv, wasm: &Wasm) -> String { + let hash = wasm.hash().unwrap(); + sandbox + .new_assert_cmd("contract") + .arg("install") + .arg("--wasm") + .arg(wasm.path()) + .arg("--ignore-checks") + .assert() + .success() + .stdout(format!("{hash}\n")); + + sandbox + .new_assert_cmd("contract") + .arg("deploy") + .arg("--wasm-hash") + .arg(&format!("{hash}")) + .arg("--salt") + .arg(TEST_SALT) + .arg("--ignore-checks") + .assert() + .success() + .stdout(format!("{TEST_CONTRACT_ID}\n")); + TEST_CONTRACT_ID.to_string() +} + +pub async fn extend_contract(sandbox: &TestEnv, id: &str, wasm: &Wasm<'_>) { + extend(sandbox, id, None).await; + let cmd: contract::extend::Cmd = sandbox.cmd_arr(&[ + "--wasm-hash", + wasm.hash().unwrap().to_string().as_str(), + "--durability", + "persistent", + "--ledgers-to-extend", + "100000", + ]); + cmd.run().await.unwrap(); +} + +pub async fn extend(sandbox: &TestEnv, id: &str, value: Option<&str>) { + let mut args = vec![ + "--id", + id, + "--durability", + "persistent", + "--ledgers-to-extend", + "100000", + ]; + if let Some(value) = value { + args.push("--key"); + args.push(value); + } + let cmd: contract::extend::Cmd = sandbox.cmd_arr(&args); + cmd.run().await.unwrap(); +} diff --git a/cmd/crates/soroban-test/tests/it/integration/wrap.rs b/cmd/crates/soroban-test/tests/it/integration/wrap.rs new file mode 100644 index 00000000..a69e70c7 --- /dev/null +++ b/cmd/crates/soroban-test/tests/it/integration/wrap.rs @@ -0,0 +1,97 @@ +use soroban_cli::CommandParser; +use soroban_cli::{ + commands::{contract::deploy::asset, keys}, + utils::contract_id_hash_from_asset, +}; +use soroban_test::TestEnv; + +use super::util::network_passphrase; + +#[tokio::test] +#[ignore] +async fn burn() { + let sandbox = &TestEnv::default(); + let network_passphrase = network_passphrase().unwrap(); + println!("NETWORK_PASSPHRASE: {network_passphrase:?}"); + let address = keys::address::Cmd::parse("test") + .unwrap() + .public_key() + .unwrap(); + let asset = format!("native:{address}"); + wrap_cmd(&asset).run().await.unwrap(); + let asset = soroban_cli::utils::parsing::parse_asset(&asset).unwrap(); + let hash = contract_id_hash_from_asset(&asset, &network_passphrase).unwrap(); + let id = stellar_strkey::Contract(hash.0).to_string(); + assert_eq!( + "CAMTHSPKXZJIRTUXQP5QWJIFH3XIDMKLFAWVQOFOXPTKAW5GKV37ZC4N", + id + ); + assert_eq!( + "true", + sandbox + .invoke(&[ + "--id", + &id, + "--source=test", + "--", + "authorized", + "--id", + &address.to_string() + ]) + .await + .unwrap() + ); + assert_eq!( + "\"9223372036854775807\"", + sandbox + .invoke(&[ + "--id", + &id, + "--source", + "test", + "--", + "balance", + "--id", + &address.to_string() + ]) + .await + .unwrap(), + ); + + println!( + "{}", + sandbox + .invoke(&[ + "--id", + &id, + "--source=test", + "--", + "burn", + "--id", + &address.to_string(), + "--amount=100" + ]) + .await + .unwrap() + ); + + assert_eq!( + "\"9223372036854775707\"", + sandbox + .invoke(&[ + "--id", + &id, + "--source=test", + "--", + "balance", + "--id", + &address.to_string() + ]) + .await + .unwrap(), + ); +} + +fn wrap_cmd(asset: &str) -> asset::Cmd { + asset::Cmd::parse_arg_vec(&["--source=test", &format!("--asset={asset}")]).unwrap() +} diff --git a/cmd/crates/soroban-test/tests/it/lab_test_transaction_envelope.txt b/cmd/crates/soroban-test/tests/it/lab_test_transaction_envelope.txt new file mode 100644 index 00000000..6cd59769 --- /dev/null +++ b/cmd/crates/soroban-test/tests/it/lab_test_transaction_envelope.txt @@ -0,0 +1,54 @@ +TransactionEnvelope( + Tx( + TransactionV1Envelope { + tx: Transaction { + source_account: Ed25519( + Uint256(7376fde88e4cd61cc0fb294a1786b3f1d061f5f2f1ca57465faa932211b946d6), + ), + fee: 100, + seq_num: SequenceNumber( + 1, + ), + cond: Time( + TimeBounds { + min_time: TimePoint( + 0, + ), + max_time: TimePoint( + 0, + ), + }, + ), + memo: None, + operations: VecM( + [ + Operation { + source_account: None, + body: CreateAccount( + CreateAccountOp { + destination: AccountId( + PublicKeyTypeEd25519( + Uint256(d18f0210ff6cc1f2dcf1301fbbd4c30ee11a075820684d471df89d0f1011ea28), + ), + ), + starting_balance: 1000000000000, + }, + ), + }, + ], + ), + ext: V0, + }, + signatures: VecM( + [ + DecoratedSignature { + hint: SignatureHint(11b946d6), + signature: Signature( + BytesM(a004a6e9b64c687f3f62b4fde3b1797c35786106e5f97f16dd9afe3ed850df87dd736390501f62726f7e99af4ec358a8fb281cab9f811a43989b8085dd312609), + ), + }, + ], + ), + }, + ), +) diff --git a/cmd/crates/soroban-test/tests/it/main.rs b/cmd/crates/soroban-test/tests/it/main.rs new file mode 100644 index 00000000..a6b18cb2 --- /dev/null +++ b/cmd/crates/soroban-test/tests/it/main.rs @@ -0,0 +1,8 @@ +mod arg_parsing; +mod config; +mod help; +#[cfg(feature = "integration")] +mod integration; +mod plugin; +mod util; +mod version; diff --git a/cmd/crates/soroban-test/tests/it/plugin.rs b/cmd/crates/soroban-test/tests/it/plugin.rs new file mode 100644 index 00000000..7d55d1e5 --- /dev/null +++ b/cmd/crates/soroban-test/tests/it/plugin.rs @@ -0,0 +1,77 @@ +/* +This function calls the soroban executable via cargo and checks that the output +is correct. The PATH environment variable is set to include the target/bin +directory, so that the soroban executable can be found. +*/ + +use std::{ffi::OsString, path::PathBuf}; + +#[test] +fn soroban_hello() { + // Add the target/bin directory to the iterator of paths + let paths = get_paths(); + // Call soroban with the PATH variable set to include the target/bin directory + assert_cmd::Command::cargo_bin("soroban") + .unwrap_or_else(|_| assert_cmd::Command::new("soroban")) + .arg("hello") + .env("PATH", &paths) + .assert() + .stdout("Hello, world!\n"); +} + +#[test] +fn list() { + // Call `soroban --list` with the PATH variable set to include the target/bin directory + assert_cmd::Command::cargo_bin("soroban") + .unwrap_or_else(|_| assert_cmd::Command::new("soroban")) + .arg("--list") + .env("PATH", get_paths()) + .assert() + .stdout(predicates::str::contains("hello")); +} + +#[test] +#[cfg(not(unix))] +fn has_no_path() { + // Call soroban with the PATH variable set to include just target/bin directory + assert_cmd::Command::cargo_bin("soroban") + .unwrap_or_else(|_| assert_cmd::Command::new("soroban")) + .arg("hello") + .env("PATH", &target_bin()) + .assert() + .stdout("Hello, world!\n"); +} + +#[test] +fn has_no_path_failure() { + // Call soroban with the PATH variable set to include just target/bin directory + assert_cmd::Command::cargo_bin("soroban") + .unwrap_or_else(|_| assert_cmd::Command::new("soroban")) + .arg("hello") + .assert() + .stderr(predicates::str::contains("error: no such command: `hello`")); +} + +fn target_bin() -> PathBuf { + // Get the current working directory + let current_dir = std::env::current_dir().unwrap(); + + // Create a path to the target/bin directory + current_dir + .join("../../../target/bin") + .canonicalize() + .unwrap() +} + +fn get_paths() -> OsString { + let target_bin_path = target_bin(); + // Get the current PATH environment variable + let path_key = std::env::var_os("PATH"); + if let Some(path_key) = path_key { + // Create an iterator of paths from the PATH environment variable + let current_paths = std::env::split_paths(&path_key); + std::env::join_paths(current_paths.chain(vec![target_bin_path])).unwrap() + } else { + target_bin_path.into() + } +} diff --git a/cmd/crates/soroban-test/tests/it/util.rs b/cmd/crates/soroban-test/tests/it/util.rs new file mode 100644 index 00000000..6d625101 --- /dev/null +++ b/cmd/crates/soroban-test/tests/it/util.rs @@ -0,0 +1,70 @@ +use std::path::Path; + +use soroban_cli::commands::{ + config::{locator::KeyType, secret::Secret}, + contract, +}; +use soroban_test::{TestEnv, Wasm}; + +pub const CUSTOM_TYPES: &Wasm = &Wasm::Custom("test-wasms", "test_custom_types"); + +#[derive(Clone)] +pub enum SecretKind { + Seed, + Key, +} + +#[allow(clippy::needless_pass_by_value)] +pub fn add_key(dir: &Path, name: &str, kind: SecretKind, data: &str) { + let secret = match kind { + SecretKind::Seed => Secret::SeedPhrase { + seed_phrase: data.to_string(), + }, + SecretKind::Key => Secret::SecretKey { + secret_key: data.to_string(), + }, + }; + + KeyType::Identity + .write(name, &secret, &dir.join(".soroban")) + .unwrap(); +} + +pub fn add_test_id(dir: &Path) -> String { + let name = "test_id"; + add_key( + dir, + name, + SecretKind::Key, + "SBGWSG6BTNCKCOB3DIFBGCVMUPQFYPA2G4O34RMTB343OYPXU5DJDVMN", + ); + name.to_owned() +} + +pub const DEFAULT_SEED_PHRASE: &str = + "coral light army gather adapt blossom school alcohol coral light army giggle"; + +#[allow(dead_code)] +pub async fn invoke_custom( + sandbox: &TestEnv, + id: &str, + func: &str, + arg: &str, + wasm: &Path, +) -> Result { + let mut i: contract::invoke::Cmd = sandbox.cmd_arr(&[ + "--id", + id, + "--network", + "futurenet", + "--source", + "default", + "--", + func, + arg, + ]); + i.wasm = Some(wasm.to_path_buf()); + i.config.network.network = Some("futurenet".to_owned()); + i.invoke(&soroban_cli::commands::global::Args::default()) + .await +} diff --git a/cmd/crates/soroban-test/tests/it/version.rs b/cmd/crates/soroban-test/tests/it/version.rs new file mode 100644 index 00000000..cb7826fa --- /dev/null +++ b/cmd/crates/soroban-test/tests/it/version.rs @@ -0,0 +1,12 @@ +use soroban_cli::commands::version::long; +use soroban_test::TestEnv; + +#[test] +fn version() { + let sandbox = TestEnv::default(); + sandbox + .new_assert_cmd("version") + .assert() + .success() + .stdout(format!("soroban {}\n", long())); +}